通过Attached Property给控件绑定Command(二)
上一篇我们提到希望建立一个通用的Command绑定,本篇就这个问题来和各位进行讨论。也希望各位能指出不足,提出改进的建议。
我希望最终实现的效果如下图所示,可以给任何一个Control绑定Command,通过提供EventName来区分不同的事件,同时由Parameter来绑定需要传递的参数。
同样,这里的local指的是CommandBinder类所在的命名空间。
<Rectangle Width="100" Height="100" Fill="Red" local:CommandBinder.Command="{Binding ViewModel.MouseLeftDownCommand, ElementName=window}" local:CommandBinder.EventName="MouseLeftButtonDown" local:CommandBinder.Parameter="{Binding RelativeSource={RelativeSource Self}}"> </Rectangle>
很明显我们这里定义了3个附加属性。只是绑定的对象不同,EventName直接给了代表事件名的字符串,而Parameter没有绑定到ViewModel中的对象,而是通过ElementName绑定了我们希望传递的参数,这里是被点击的Rectangle。我们来看一下EventName和Parameter的定义。
public static object GetParameter(DependencyObject obj) { return (object)obj.GetValue(ParameterProperty); } public static void SetParameter(DependencyObject obj, object value) { obj.SetValue(ParameterProperty, value); } // Using a DependencyProperty as the backing store for Parameter. This enables animation, styling, binding, etc... public static readonly DependencyProperty ParameterProperty = DependencyProperty.RegisterAttached("Parameter", typeof(object), typeof(CommandBinder), new UIPropertyMetadata(null)); public static string GetEventName(DependencyObject obj) { return (string)obj.GetValue(EventNameProperty); } public static void SetEventName(DependencyObject obj, string value) { obj.SetValue(EventNameProperty, value); } // Using a DependencyProperty as the backing store for EventName. This enables animation, styling, binding, etc... public static readonly DependencyProperty EventNameProperty = DependencyProperty.RegisterAttached("EventName", typeof(string), typeof(CommandBinder), new UIPropertyMetadata(null));
附加属性的代码段快捷键是键入propa,再按2次Tab键,各位一试便知。对EventName和Parameter我们并没有做过多的处理,仅仅是希望他们以附加属性的形式能在XAML出现,并提供绑定的能力。
真正取得绑定数据并关联Command.Execute方法的,仍然是CommandProperty:
public static ICommand GetCommand(DependencyObject obj) { return (ICommand)obj.GetValue(CommandProperty); } public static void SetCommand(DependencyObject obj, ICommand value) { obj.SetValue(CommandProperty, value); } // Using a DependencyProperty as the backing store for CommonCommandBinder. This enables animation, styling, binding, etc... public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(CommandBinder), new PropertyMetadata(ChangedCallback)); private static void ChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) { FrameworkElement element = d as FrameworkElement; if (element != null) { string eventName = element.GetValue(EventNameProperty) as string; if (eventName != null) { EventInfo eventInfo = element.GetType().GetEvent(eventName); var handler = new MouseButtonEventHandler((sender, arg) => { object obj = element.GetValue(ParameterProperty); (e.NewValue as ICommand).Execute(obj); }); var del = handler.GetInvocationList()[0]; eventInfo.AddEventHandler(element, del); } } }
在这里我们可以看到我们通过DependencyObject的GetValue方法,取到了绑定的EventName和Parameter。然后通过反射将EventInfo和具体的MouseButtonEventHandler关联起来。动态的通过字符串将具体的事件注册。
但是这里有一个问题,针对事件不同的委托类型,比如这里的MouseButtonEventHandler,我没有办法创建一个通用的EventHandler。这就造成了针对不同的事件,我仍需要在这里写一个大大的switch case。这是这个本篇比较大的一个败笔。我暂时也没有更好的办法。在此也希望各位能给出好的建议。
看到这里有人会说,WPF是有behavior的,即使是Silverlight也是可以用Blend来绑定Command的,不需要自己费力气去写这种不怎么好用的附加属性。确实是如此。本篇的起由还是对依赖属性的学习。在微软的Win8应用开发马拉松上,我曾苦恼过WinRT没有behavior如何绑定。这是一位胖胖的微软哥挺身而出以神一般的姿态出现,丢给我一个他写的CommandBinder,把依赖属性和数据绑定以全新的姿态展示在我面前,令我茅厕顿开啊!于是我就试图用WPF来重现该神奇的绑定。
但是我却没有办法像Win8里一样实现如下图XAML的绑定,各位有兴趣可以在WPF试一试,或许可以使你对数据绑定有新的认识。
<Rectangle Width="100" Height="100" Fill="Red" > <local:Common.CommonCommand> <local:CommonCommand Command="{Binding Path=MouseLeftDownCommand}" EventName="MouseLeftButtonDown" Parameter="{Binding Path=Title}">
</local:CommonCommand>
</local:Common.CommonCommand>
</Rectangle>
看似相似的绑定却始终无法在WPF中取到Parameter和Command的值。这里主要是期望把联系并不紧密的三个依赖属性EventNameProperty、ParameterProperty和CmmandProperty放置到一个叫做CommonCommand的类中,提高可读性和易用性。
在这里CommonCommandProperty是作为附加属性出现的,所以他可以写成Rectangle的属性,而该属性是CommonCommand类型。EventNameProperty、ParameterProperty和CmmandProperty作为依赖属性存在于CommonCommand类中,而该类继承自DependencyObject。所以才能以上面的语法形式存在于XAML中。
这里说了我没有成功获取到值,那么在通过Attached Property给控件绑定Command(三)中,我会提供一个替代的解决方案,敬请期待。
同时也希望各位能对本篇提出意见和建议。
这里给出本文相关的代码:
3个附加属性的绑定形式:https://files.cnblogs.com/manupstairs/TestDPWpf.7z
CommonCommand:https://files.cnblogs.com/manupstairs/TestDPWithParameter.zip