本文主要介紹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)
關鍵字:滑鼠、觸筆、觸控