Khi phát triển ứng dụng cho màn hình lớn hoặc thiết bị cảm ứng như tablet, việc theo dõi các tương tác chạm bên ngoài phạm vi cửa sổ ứng dụng là yêu cầu phổ biến. Mặc dù WPF cung cấp sẵn cơ chế xử lý touch/stylus/mouse trong phạm vi window, nhưng để thu thập sự kiện chạm từ bất kỳ vị trí nào trên hệ thống — kể cả khi ứng dụng không ở trạng thái focus — cần sử dụng giao diện Raw Input của Windows.
Để đạt được điều này, bạn phải đăng ký thiết bị đầu vào đặc biệt (touchscreen và bút kỹ thuật số) với cờ EXINPUTSINK, cho phép nhận dữ liệu ngay cả khi ứng dụng không hoạt động.
1. Đăng ký thiết bị cảm ứng toàn hệ thống
Thực hiện đăng ký thông qua handle cửa sổ và chỉ định loại thiết bị theo chuẩn HID:
RawInputDevice.RegisterDevice(
HidUsageAndPage.TouchScreen,
RawInputDeviceFlags.ExInputSink | RawInputDeviceFlags.DevNotify,
windowHandle);
RawInputDevice.RegisterDevice(
HidUsageAndPage.Pen,
RawInputDeviceFlags.ExInputSink | RawInputDeviceFlags.DevNotify,
windowHandle);
2. Xử lý luồng dữ liệu HID trong hook
Khi hệ thống gửi tin hiệu WM_INPUT, dữ liệu được phân tích thành đối tượng RawInputHidData. Đối với thiết bị cảm ứng, ta kiểm tra kiểu cụ thể là RawInputDigitizerData để trích xuất danh sách điểm chạm:
case RawInputHidData input:
var device = input.Device;
var log = new StringBuilder()
.AppendLine($"Path: {device.DevicePath}")
.AppendLine($"Vendor ID: 0x{device.VendorId:X4} | Product ID: 0x{device.ProductId:X4}")
.AppendLine($"Raw HID Payload: {BitConverter.ToString(input.Hid)}");
if (input is RawInputDigitizerData digitizer)
{
foreach (var contact in digitizer.Contacts)
{
log.AppendLine($"Contact[{contact.ContactId}]: X={contact.X}, Y={contact.Y}, Pressure={contact.Pressure}");
}
}
Debug.WriteLine(log.ToString());
break;
3. Cài đặt hook toàn cục trong WPF
Dưới đây là phần cốt lõi trong lớp MainWindow, sử dụng HwndSource.AddHook để bắt tin nhắn hệ thống:
public partial class MainWindow : Window
{
private HwndSource _source;
public MainWindow()
{
InitializeComponent();
Loaded += OnWindowLoaded;
}
private void OnWindowLoaded(object sender, RoutedEventArgs e)
{
var helper = new WindowInteropHelper(this);
var hwnd = helper.Handle;
// Đăng ký thiết bị cảm ứng và bút
RawInputDevice.RegisterDevice(HidUsageAndPage.TouchScreen,
RawInputDeviceFlags.ExInputSink | RawInputDeviceFlags.DevNotify, hwnd);
RawInputDevice.RegisterDevice(HidUsageAndPage.Pen,
RawInputDeviceFlags.ExInputSink | RawInputDeviceFlags.DevNotify, hwnd);
_source = HwndSource.FromHwnd(hwnd);
_source.AddHook(WndProc);
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
const int WM_INPUT = 0x00FF;
if (msg == WM_INPUT)
{
var rawData = RawInputData.FromHandle(lParam);
switch (rawData)
{
case RawInputHidData hidData:
ProcessDigitizerInput(hidData);
break;
// Có thể mở rộng xử lý keyboard/mouse nếu cần
}
}
return IntPtr.Zero;
}
private void ProcessDigitizerInput(RawInputHidData data)
{
if (data is RawInputDigitizerData digitizer && digitizer.Contacts.Length > 0)
{
foreach (var point in digitizer.Contacts)
{
// Gửi sự kiện chạm tới logic nghiệp vụ
Dispatcher.Invoke(() => HandleTouchPoint(point));
}
}
}
private void HandleTouchPoint(RawInputDigitizerContact contact)
{
// Ví dụ: cập nhật UI hoặc kích hoạt hành động dựa trên tọa độ và lực nhấn
TouchInfoTextBlock.Text =
$"Tọa độ: ({contact.X}, {contact.Y}), Lực: {contact.Pressure}";
}
}