2012年11月10日星期六

通过Attached Property给控件绑定Command(二)

通过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个附加属性的绑定形式:http://files.cnblogs.com/manupstairs/TestDPWpf.7z

  CommonCommand:http://files.cnblogs.com/manupstairs/TestDPWithParameter.zip




TAG: