通过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

posted @ 2012-11-09 23:10  楼上那个蜀黍  阅读(1774)  评论(2编辑  收藏  举报