本文说明怎样把 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 内填多个组合。