WPF 裝置輸入事件封裝

唐宋元明清2188發表於2024-08-22

本文主要介紹WPF應用對滑鼠輸入、觸控式螢幕觸筆以及觸控事件的封裝

之前有簡單說明裝置輸入型別 WPF 螢幕點選的裝置型別 - 唐宋元明清2188 - 部落格園 (cnblogs.com)

1、滑鼠 - 透過Mouse相關的事件引數MouseButtonEventArgs中的資料,e.StylusDecice==null表示沒有觸控裝置,所以裝置為滑鼠

2、觸筆 or 觸控 - 根據StylusDown事件引數StylusDownEventArgs,e.StylusDevice.TabletDevice.Type == TabletDeviceType.Stylus,True表示觸控裝置為觸筆,False則為觸控。

透過上面裝置型別的判斷,就可以封裝一套裝置點選事件,DeviceDown、DeviceUp、DeviceMove。事件引數中新增裝置型別DeviceType(滑鼠、觸筆、觸控),然後具體業務中可以透過裝置型別區分相關的互動操作。

部落格園有個小夥伴問我裝置型別具體是如何封裝的,那本文就補充下裝置輸入事件的封裝使用,希望給大家提供一點幫助、省去你們磨程式碼的時間。

除了裝置輸入型別,輸入事件也有多種狀態。簡單介紹下事件區分,具體以滑鼠事件為例:

 1     private void RegisterMouse()
 2     {
 3         //滑鼠-冒泡
 4         if (_element is Button button)
 5         {
 6             //Button型別的冒泡事件,被內部消化處理了,所以需要重新新增路由事件訂閱
 7             button.AddHandler(UIElement.MouseLeftButtonDownEvent, new MouseButtonEventHandler(Button_MouseLeftButtonDown), true);
 8             button.AddHandler(UIElement.MouseRightButtonDownEvent, new MouseButtonEventHandler(Button_MouseRightButtonDown), true);
 9             button.AddHandler(UIElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler(Button_MouseLeftButtonUp), true);
10             button.AddHandler(UIElement.MouseRightButtonUpEvent, new MouseButtonEventHandler(Button_MouseRightButtonUp), true);
11         }
12         else
13         {
14             _element.MouseLeftButtonDown += Button_MouseLeftButtonDown;
15             _element.MouseRightButtonDown += Button_MouseRightButtonDown;
16             _element.MouseLeftButtonUp += Button_MouseLeftButtonUp;
17             _element.MouseRightButtonUp += Button_MouseRightButtonUp;
18         }
19         _element.MouseMove += Element_MouseMove;
20 
21         //滑鼠-隧道事件
22         _element.PreviewMouseLeftButtonDown += Button_PreviewMouseLeftButtonDown;
23         _element.PreviewMouseRightButtonDown += Button_PreviewMouseRightButtonDown;
24         _element.PreviewMouseLeftButtonUp += Button_PreviewMouseLeftButtonUp;
25         _element.PreviewMouseRightButtonUp += Button_PreviewMouseRightButtonUp;
26         _element.PreviewMouseMove += Element_PreviewMouseMove;
27     }

上面區分了按鈕與其它的FrameworkElement的滑鼠事件,因為Button對冒泡事件是做了攔截再暴露Click事件,需要訂閱路由事件來完成滑鼠的監聽。

如上方程式碼,對滑鼠的左右鍵、按下抬起、移動以及冒泡隧道都做了完整的封裝。

滑鼠事件:

 1     private void Element_MouseDown(MouseEventArgs e, int deviceId)
 2     {
 3         if (e.StylusDevice != null) return;
 4         var positionLazy = new Lazy<Point>(() => e.GetPosition(_element));
 5         var deviceInputArgs= new DeviceInputArgs()
 6         {
 7             DeviceId = deviceId,
 8             DeviceType = DeviceType.Mouse,
 9             PositionLazy = positionLazy,
10             PointsLazy = new Lazy<StylusPointCollection>(() =>
11             {
12                 var point = positionLazy.Value;
13                 return new StylusPointCollection(new List<StylusPoint>() { new StylusPoint(point.X, point.Y) });
14             }),
15             GetPositionFunc = (element, args) => e.GetPosition(element),
16             SourceArgs = e
17         };
18         _deviceDown?.Invoke(_element, deviceInputArgs);
19     }

觸控面積獲取:

 1     /// <summary>
 2     /// 獲取含面積的觸控點集合
 3     /// </summary>
 4     /// <param name="stylusEventArgs"></param>
 5     /// <param name="uiElement"></param>
 6     /// <returns></returns>
 7     public static Rect GetTouchPointArea(TouchEventArgs stylusEventArgs, UIElement uiElement)
 8     {
 9         Rect touchArea = Rect.Empty;
10         var touchPoints = stylusEventArgs.GetIntermediateTouchPoints(uiElement);
11         foreach (var stylusPoint in touchPoints)
12         {
13             var stylusPointArea = stylusPoint.Bounds;
14             touchArea.Union(stylusPointArea);
15         }
16         return touchArea;
17     }

返回新的觸筆輸入點集:

 1     /// <summary>
 2     /// 獲取觸控點集
 3     /// </summary>
 4     /// <param name="stylusEventArgs"></param>
 5     /// <param name="element"></param>
 6     /// <returns></returns>
 7     private StylusPointCollection GetStylusPoints(StylusEventArgs stylusEventArgs, FrameworkElement element)
 8     {
 9         // 臨時去除description
10         var pointCollection = new StylusPointCollection();
11         var stylusPointCollection = stylusEventArgs.GetStylusPoints(element);
12         foreach (var stylusPoint in stylusPointCollection)
13         {
14             pointCollection.Add(new StylusPoint(stylusPoint.X, stylusPoint.Y, stylusPoint.PressureFactor));
15         }
16         return pointCollection;
17     }

這裡是直接把StylusPoint.Description捨棄,防止在應用層未能統一好這個裝置描述、導致異常

當然這些獲取資訊根據業務需要來獲取、此處裝置事件封裝不損耗處理延時,所以需要新增Lazy函式如:

PositionLazy = new Lazy<Point>(() => e.GetPosition(_element)),
PointsLazy = new Lazy<StylusPointCollection>(() => GetStylusPoints(e, _element)),

暴露的通用裝置按下事件,可以看如下定義:

1     /// <summary>
2     /// 裝置按下
3     /// </summary>
4     public event EventHandler<DeviceInputArgs> DeviceDown
5     {
6         add => _deviceDown.Add(value, value.Invoke);
7         remove => _deviceDown.Remove(value);
8     }

UI控制元件訂閱DeviceDown、DeviceMove、DeviceUp事件,從事件引數DeviceInputArgs獲取詳細資料:

 1         /// <summary>
 2         /// 裝置ID
 3         /// <para>預設為滑鼠裝置,滑鼠左鍵-1,滑鼠右鍵-2 </para>
 4         /// </summary>
 5         public int DeviceId { get; set; }
 6         /// <summary>
 7         /// 裝置型別
 8         /// </summary>
 9         public DeviceType DeviceType { get; set; }
10 
11         /// <summary>
12         /// 位置
13         /// </summary>
14         public Point Position
15         {
16             get => PositionLazy?.Value ?? default;
17             set => PositionLazy = new Lazy<Point>(() => value);
18         }
19 
20         /// <summary>
21         /// 觸筆點集
22         /// </summary>
23         public StylusPointCollection Points
24         {
25             get => PointsLazy?.Value;
26             set => PointsLazy = new Lazy<StylusPointCollection>(() => value);
27         }
28 
29         /// <summary>
30         /// 獲取相對元素的位置
31         /// </summary>
32         public Func<FrameworkElement, Point> GetPosition
33         {
34             get => relativeElement => GetPositionFunc(relativeElement, SourceArgs);
35             set => GetPositionFunc = (relativeElement, args) => value(relativeElement);
36         }
37 
38         /// <summary>
39         /// 事件觸發源資料
40         /// </summary>
41         public InputEventArgs SourceArgs { get; set; }
42 
43         /// <summary>
44         /// 觸控面積
45         /// </summary>
46         public Rect TouchArea => TouchAreaLazy?.Value ?? Rect.Empty;

DeviceInputArgs繼承Windows路由事件引數類RoutedEventArgs

為何要封裝通用事件?因為2點原因:

1. 大部分業務並不需要區分滑鼠、觸筆、觸控型別

2. 有些業務如白板書寫、多指操作需要區分滑鼠、觸筆、觸控型別,這類場景因為操作事件訂閱太多,業務邏輯搞複雜了

透過將滑鼠、觸筆、觸控事件封裝為通用輸入事件,下游應用業務對裝置輸入事件處理邏輯就簡單化了。

封裝Demo可詳見:kybs00/DeviceInputEventDemo (github.com)

關鍵字:滑鼠、觸筆、觸控

相關文章