本文說明怎樣把 DoubleClick 鏈接至 ICommand。方法不少。推薦使用 Attach Property 方式,由於它能把任何 RoutedEvent 接上任何 ICommand。程序員
以前寫過一篇博文關於 MVVM 中雙擊事件觸發 ICommand 的辦法,我說要麼你本身寫 Attached Property,要麼下載別人寫好的,好比支持 Collections 的 CommandBehaviors。我認爲這兩個辦法是比較好的。有網友說我沒有解釋清楚,由於我以爲 Attached Property 有點離題,跟 MVVM 關係不太大。反正有得用就好了。google
下面以 ListView 爲例。spa
1. InputBindings3d
先不說 Attached Property,看看有什麼辦法能夠把雙擊綁定到 ICommand。最簡單的辦法是 InputBindings。code
XAML:orm
<ListView.InputBindings><MouseBinding Gesture="LeftDoubleClick" Command=""/></ListView.InputBindings>
支持 KeyBinding (鍵盤),和 MouseBinding (鼠標)。能作到,若是隻須要管鍵盤或鼠標,這是比較簡單。xml
2. 隱形 Button (不建議)blog
我見過第二個辦法,隱形 Button, (Visibility=」Collapsed」),ICommand 綁定進去,ListView MouseDoubleClick 在視圖創建句柄,由它再觸發 Button 的 Command.Execute(object)。事件
XAML:ip
<Button Name="button1" Visibility="Collapsed" Command=""/><ListView MouseDoubleClick="ListView_MouseDoubleClick"/>
Code:
privatevoid ListView_MouseDoubleClick(object sender, MouseButtonEventArgs e) { button1.Command.Execute(null); }
這比較傻,不建議。
3. Attached Property
MSDN 有介紹怎樣爲控件添加新的屬性,這裏不詳細說了。關鍵是靜態方法 Set,和靜態 DependencyProperty。(MSDN 說 GET SET 都要,但其實寫 XAML 時只用到 SET,後續啓動後,你須要拿回屬性值才須要 GET)。
先看一下,Attached Property 是怎樣寫的,熱熱身:
CODE:
publicstaticclass MyProperty { publicstaticreadonly DependencyProperty ParameterProperty = DependencyProperty.RegisterAttached( "Parameter", typeof(Object), typeof(MyProperty), new FrameworkPropertyMetadata(null) ); publicstatic Object GetParameter(UIElement obj) { return obj.GetValue(ParameterProperty); } publicstaticvoid SetParameter(UIElement obj, Object value) { obj.SetValue(ParameterProperty, value); } }
get、set 參數 UIElement 類型是爲了確保全部控件能用它。這 Parameter 沒有配置CallBack,這個MyProperty不對值變化作什麼動做,也不設置默認值,因此 RegisterAttached 時候 FrameworkPropertyMetadata是 null。
命名規範必須跟從,MSDN 有說明。當你但願在 XAML 這屬性叫作 Parameter 的時候(RegisterAttached 的第一個參數),它的get、set 方法必須命名爲 GetParameter 和 SetParameter。編譯後 XAML 可用。
XAML:
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:y="clr-namespace:WpfApplication1" Title="MainWindow" Height="350" Width="525"><Grid><ListView y:MyProperty.Parameter="ABC"/></Grid></Window>
新手記得加上正確的 XML namespace,xmlns:y="clr-namespace:WpfApplication1" 是由於我把MyProperty類放在這 WpfApplication1 項目的最外層。
知道了怎麼寫 Attached Property 以後,入正題,加入 ICommand。爲靈活性,作法是讓程序員配置要綁的 RoutedEvent ,和對應要觸發的 ICommand 同時做爲 DependencyProperty,讓程序員本身配置哪一個Event 接哪一個 ICommand。(注:handler 那 Dictionary 的作法,和 Detach Attach 是參考某大神的)。爲縮短代碼,只寫 ICommand 和 Event,沒寫 ICommand 的命令參數。
(如下代碼網上其實不少,也有不少版本,大同小異)
CODE:
using System.Collections.Generic; using System.Windows; using System.Windows.Input; namespace WpfApplication1 { publicstaticclass CommandBehavior { // UI,Handler Listprivatestatic Dictionary<UIElement, RoutedEventHandler> handlers =new Dictionary<UIElement, RoutedEventHandler>(); #region Command Propertypublicstaticreadonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached( "Command", typeof(ICommand), typeof(CommandBehavior), new FrameworkPropertyMetadata() { DefaultValue =null, PropertyChangedCallback =new PropertyChangedCallback(OnCommandPropertyChanged) } ); publicstatic ICommand GetCommand(UIElement obj) { return (ICommand)obj.GetValue(CommandProperty); } publicstaticvoid SetCommand(UIElement obj, ICommand value) { obj.SetValue(CommandProperty, value); } #endregion#region Event Propertypublicstaticreadonly DependencyProperty EventProperty = DependencyProperty.RegisterAttached( "Event", typeof(RoutedEvent), typeof(CommandBehavior), new FrameworkPropertyMetadata() { DefaultValue =null, PropertyChangedCallback =new PropertyChangedCallback(OnEventPropertyChanged) } ); publicstatic RoutedEvent GetEvent(DependencyObject obj) { return (RoutedEvent)obj.GetValue(EventProperty); } publicstaticvoid SetEvent(DependencyObject obj, RoutedEvent value) { obj.SetValue(EventProperty, value); } #endregion#region CallBacksprivatestaticvoid OnCommandPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) { UIElement element = obj as UIElement; ICommand oldCommand = args.OldValue as ICommand; ICommand newCommand = args.NewValue as ICommand; RoutedEvent routedEvent = element.GetValue(EventProperty) as RoutedEvent; Detach(element, routedEvent, oldCommand); Attach(element, routedEvent, newCommand); } privatestaticvoid OnEventPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) { UIElement element = obj as UIElement; RoutedEvent oldEvent = args.OldValue as RoutedEvent; RoutedEvent newEvent = args.NewValue as RoutedEvent; ICommand command = element.GetValue(CommandProperty) as ICommand; Detach(element, oldEvent, command); Attach(element, newEvent, command); } #endregionprivatestaticvoid Attach(UIElement element, RoutedEvent Event, ICommand command) { if (Event !=null&& element !=null&& command !=null) { RoutedEventHandler InvokeCommandHandler =new RoutedEventHandler(delegate { command.Execute(null); }); handlers.Add(element, InvokeCommandHandler); element.AddHandler(Event, InvokeCommandHandler); } } privatestaticvoid Detach(UIElement element, RoutedEvent Event, ICommand command) { if (Event !=null&& element !=null&& command !=null) { RoutedEventHandler handler = handlers[element]; if (handler !=null) { element.RemoveHandler(Event, handler); handlers.Remove(element); } } } } }
跟以前那個 Parameter 例子很像,只是同一個靜態類,作了兩個屬性,一個叫作 Event,一個叫作 Command。另外,多了一個 Dictionary,還有,此次 Event 和 Command 的變化,都註冊了 PropertyChangedCallback 的句柄。最下面的 Attach Detach 的 private 幫助方法,只是重構時從PropertyChangedCallBack 的句柄抽出來而已。
控件、事件、命令,三者是一塊兒的組合,某 UIElement 的某 RoutedEvent 觸發到某 ICommand 的 Execute。但RoutedEvent 觸發的是 RoutedEventHandler 句柄,不是 ICommand。因此這個靜態類所作最重要的事,見 private static void Attach(),就是建立新的 RoutedEventHandler,讓它執行委託運行 command 的 Execute,而後把準備好 RoutedEventHandler 以後粘上 UIElement,即 AddHandler(RoutedEvent,RoutedEventHandler)。把這搭配,UIElement 和已作好ICommand委託的 RoutedEventHandler,放在 Dictionary,是爲了 Detach 時候找回。
要作 Detach 是由於,DependencyProperty 的值是能變化的(上例中是 Event和Command這兩個,都能在運行時變),不必定是寫死在 XAML,好比 {Binding Path=XXX} 這狀況。萬一 Command 變了,或者 RoutedEvent 變了,上述作好了的搭配就失效,是須要 RemoveHandler 而後從新組合。因此,PropertyChangedCallBack 所作的,都是先 Detach 舊值(args.OldValue),而後再 Attach 粘上新值(args.NewValue)。無論 Event 變仍是 Command 變,都須要如此。
這靜態類的解釋到此爲止。不復雜。用法以下:
XAML:
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:y="clr-namespace:WpfApplication1" Title="MainWindow" Height="350" Width="525"><Grid><ListView y:CommandBehavior.Command="{Binding Path=TestCommand}" y:CommandBehavior.Event="ListView.MouseDoubleClick"></ListView></Grid></Window>
由於一開始設置了Command 和 Event 的默認值爲 null (RegisterAttached 時候的 FrameworkPropertyMetadata 內,DefaultValue),因此 XAML 運行寫入值時,值變化觸發 CallBack,完成了咱們須要的鏈接。
最後,改一下 CommandBehavior,讓它能接受參數,傳過去 ICommand。由於 ICommand 的命令參數類型是 object,因此寫的 CommandParameter 類型也是 object。
完整版本 CODE:
using System.Collections.Generic; using System.Windows; using System.Windows.Input; namespace WpfApplication1 { publicstaticclass CommandBehavior { // UI,Handler Listprivatestatic Dictionary<UIElement, RoutedEventHandler> handlers =new Dictionary<UIElement, RoutedEventHandler>(); #region Command Propertypublicstaticreadonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached( "Command", typeof(ICommand), typeof(CommandBehavior), new FrameworkPropertyMetadata() { DefaultValue =null, PropertyChangedCallback =new PropertyChangedCallback(OnCommandPropertyChanged) } ); publicstatic ICommand GetCommand(UIElement obj) { return (ICommand)obj.GetValue(CommandProperty); } publicstaticvoid SetCommand(UIElement obj, ICommand value) { obj.SetValue(CommandProperty, value); } #endregion#region Event Propertypublicstaticreadonly DependencyProperty EventProperty = DependencyProperty.RegisterAttached( "Event", typeof(RoutedEvent), typeof(CommandBehavior), new FrameworkPropertyMetadata() { DefaultValue =null, PropertyChangedCallback =new PropertyChangedCallback(OnEventPropertyChanged) } ); publicstatic RoutedEvent GetEvent(DependencyObject obj) { return (RoutedEvent)obj.GetValue(EventProperty); } publicstaticvoid SetEvent(DependencyObject obj, RoutedEvent value) { obj.SetValue(EventProperty, value); } #endregion#region CommandParameter Propertypublicstaticreadonly DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached( "CommandParameter", typeof(object), typeof(CommandBehavior), new FrameworkPropertyMetadata(null) ); publicstaticobject GetCommandParameter(UIElement obj) { return obj.GetValue(CommandParameterProperty); } publicstaticvoid SetCommandParameter(UIElement obj, object value) { obj.SetValue(CommandParameterProperty, value); } #endregion#region CallBacksprivatestaticvoid OnCommandPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) { UIElement element = obj as UIElement; ICommand oldCommand = args.OldValue as ICommand; ICommand newCommand = args.NewValue as ICommand; RoutedEvent routedEvent = element.GetValue(EventProperty) as RoutedEvent; object commandParameter = element.GetValue(CommandParameterProperty); Detach(element, routedEvent, oldCommand); Attach(element, routedEvent, newCommand, commandParameter); } privatestaticvoid OnEventPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) { UIElement element = obj as UIElement; RoutedEvent oldEvent = args.OldValue as RoutedEvent; RoutedEvent newEvent = args.NewValue as RoutedEvent; ICommand command = element.GetValue(CommandProperty) as ICommand; object commandParameter = element.GetValue(CommandParameterProperty); Detach(element, oldEvent, command); Attach(element, newEvent, command, commandParameter); } #endregionprivatestaticvoid Attach(UIElement element, RoutedEvent Event, ICommand command, object commandParameter) { if (Event !=null&& element !=null&& command !=null) { RoutedEventHandler InvokeCommandHandler =new RoutedEventHandler(delegate { command.Execute(commandParameter); }); handlers.Add(element, InvokeCommandHandler); element.AddHandler(Event, InvokeCommandHandler); } } privatestaticvoid Detach(UIElement element, RoutedEvent Event, ICommand command) { if (Event !=null&& element !=null&& command !=null) { RoutedEventHandler handler = handlers[element]; if (handler !=null) { element.RemoveHandler(Event, handler); handlers.Remove(element); } } } } }
完整版本的 CommandBehavior 在 XAML 用法:
XAML:
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:y="clr-namespace:WpfApplication1" Title="MainWindow" Height="350" Width="525"><Grid><ListView y:CommandBehavior.Command="{Binding Path=TestCommand}" y:CommandBehavior.Event="ListView.MouseDoubleClick" y:CommandBehavior.CommandParameter="TestParameter"/></Grid></Window>
Attach Property 方法介紹到此爲止。點擊這裏下載最終版本的代碼。
這類簡單,用來解釋工做原理比較合適。但我以前博文沒用這個類,由於以上代碼,有一個明顯缺陷。源於 Dictionary<UIElement, RoutedEventHandler> 這樣的簡單搭配,UIElement 做爲 Key。並且 CommandBehavior 這靜態類,沒有集合暴露給 XAML。這意味着,一個控件,只能設置一次。好比,當一個控件你有兩個 RoutedEvent 但願綁定到兩個ICommand,這代碼不支持。
爲了解決這問題,網上已經有不少人寫好了一個叫作 CommandBehaviorCollection 的類(懶到搜索都不想搜的,點擊這裏),不少不一樣的版本,功能其實都同樣,讓你在 XAML 內一個控件能同時配置多個 Event 和 Command 的組合。這個類就是我在以前博文上用到的那個。我不打算解釋裏面內容,其工做基本原理,與上述代碼一摸同樣,只是它暴露了集合讓你在 XAML 內填多個組合。