65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 中 Windows 8 Metro 风格的附加命令

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2012年1月27日

Ms-PL

2分钟阅读

viewsIcon

54320

downloadIcon

1577

C# 中 Windows 8 Metro 风格的附加命令

引言

Windows 8 开发者预览版和 Blend 5 开发者预览版还不支持 EventToCommand 行为(MVVM Light Toolkit 可用于 Metrohttp://mvvmlight.codeplex.com 但 Blend 行为不可用)。 许多开发者希望在他们的 MVVM 模式中实现事件到命令的绑定。所以我们需要 Metro 的附加命令(类似于 WPF 中的 AttachedCommand)。

设计代码

好,让我们开始。

通常,在 MVVM 模式中,我们喜欢在 ViewModel 中声明值属性和命令,并在 View 上使用 DataBinding。 这样就可以完成“数据驱动 UI”的工作。 让我们看看 Metro 风格的应用程序。 虽然它使用 XAML 和 WinRT 组件,但它仍然可以使用 MVVM 模式作为其架构。 Metro 仍然支持 DataBinding,我们仍然可以让“数据”驱动“Metro UI”。

然而,在 ViewModel 中实现属性和命令很容易,但将一个 UI 控件事件分配给一个命令并不容易。 在 WPF 中,我们可以使用 Blend SDK 行为将一个事件分配给一个命令,或者我通常建议设计一个 AttachedCommand,就像这样: http://marlongrech.wordpress.com/2008/12/13/attachedcommandbehavior-v2-aka-acb 。但是查看 Metro 的 MVVM Light Toolkit,它不能提供 EventToCommand 行为,并且 Blend 5 开发者预览版也没有提供该行为。 因此,我们只需要为 Metro 设计一个 AttachedCommand。

在我们的 AttachedCommand 类中,我们应该声明两个附加属性,Command 和 RoutedEvent 属性。

    /// <summary>
    /// Command attached property
    /// </summary>
    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.RegisterAttached("Command",
        "Object",  // should be "Object" instead of "ICommand" interface, resolve the null reference exception
        typeof(AttachedCommand).FullName, new PropertyMetadata(DependencyProperty.UnsetValue));
 
    public static ICommand GetCommand(DependencyObject d)
    {
        return (ICommand)d.GetValue(CommandProperty);
    }
    public static void SetCommand(DependencyObject d, ICommand value)
    {
        d.SetValue(CommandProperty, value);
    }




    /// <summary>
    /// RoutedEvent property
    /// </summary>
    public static readonly DependencyProperty RoutedEventProperty =
        DependencyProperty.RegisterAttached("RoutedEvent",
        "String",
        typeof(AttachedCommand).FullName,
        new PropertyMetadata(String.Empty, new PropertyChangedCallback(OnRoutedEventChanged)));
 
    public static String GetRoutedEvent(DependencyObject d)
    {
        return (String)d.GetValue(RoutedEventProperty);
    }
    public static void SetRoutedEvent(DependencyObject d, String value)
    {
        d.SetValue(RoutedEventProperty, value);
    }

我们应该通过类型的字符串在 Metro 中注册附加属性(类似于 Silverlight 解决方案),对于 Interface,我们应该使用“Object”代替。 不确定这是否会在下一个 Windows 8 版本中解决,但对于 interface,它会抛出 NullReferenceException。

下面是 RoutedEvent 属性的属性更改回调

    private static void OnRoutedEventChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        String routedEvent = (String)e.NewValue;
 
        if (!String.IsNullOrEmpty(routedEvent))
        {
            EventHooker eventHooker = new EventHooker();
            eventHooker.AttachedCommandObject = d;
            EventInfo eventInfo = GetEventInfo(d.GetType(), routedEvent);
 
            if (eventInfo != null)
            {
                eventInfo.AddEventHandler(d, eventHooker.GetEventHandler(eventInfo));
            }
 
        }
    }
}

我们需要通过反射 Metro DependecyObject 来获取 EventInfo。 但是在 Metro 中,反射只能列出直接在当前类型中声明的成员。 它无法列出从基类型继承的所有成员。 因此,我们应该使用一种方法从其基类型中搜索事件成员

    /// <summary>
    /// Search the EventInfo from the type and its base types
    /// </summary>
    /// <param name="type"></param>
    /// <param name="eventName"></param>
    /// <returns></returns> <returns />
    private static EventInfo GetEventInfo(Type type, string eventName)
    {
        EventInfo eventInfo = null;
        eventInfo = type.GetTypeInfo().GetDeclaredEvent(eventName);
        if (eventInfo == null)
        {
            Type baseType = type.GetTypeInfo().BaseType;
            if (baseType != null)
                return GetEventInfo(type.GetTypeInfo().BaseType, eventName);
            else
                return eventInfo;
        }
        return eventInfo;
    }

当控件上触发特定事件时,我们应该返回事件处理程序。 因此,有一个 EventHooker 可以返回一个“OnEventRaised”方法,并在其中执行命令

  internal sealed class EventHooker
  {
      public DependencyObject AttachedCommandObject { get; set; }
   
      public Delegate GetEventHandler(EventInfo eventInfo)
      {
          Delegate del = null;
          if (eventInfo == null)
              throw new ArgumentNullException("eventInfo");
   
          if (eventInfo.EventHandlerType == null)
              throw new ArgumentNullException("eventInfo.EventHandlerType");
   
          if (del == null)
              del = this.GetType().GetTypeInfo().GetDeclaredMethod("OnEventRaised").CreateDelegate(eventInfo.EventHandlerType, this);
   
          return del;
      }
   
      private void OnEventRaised(object sender, object e)  // the second parameter in Windows.UI.Xaml.EventHandler is Object
      {
          ICommand command = (ICommand)(sender as DependencyObject).GetValue(AttachedCommand.CommandProperty);
   
          if (command != null)
              command.Execute(null);
      }
  }

如何使用

我们应该为 Metro 添加一个 DelegateCommand 或 RelayCommand(ICommand),它可以帮助我们在 ViewModel 中返回 ICommand。 我们可以将此 ICommand 属性绑定到 AttachedCommand.Command 属性。 下面是 Metro 的一个 DelegateCommand。 MVVM Light Toolkit 也提供了 Metro 可用的 RelayCommand。

    public class DelegateCommand : ICommand
    {
        private readonly Predicate<object> _canExecute;
        private readonly Action<object> _execute;




        public event Windows.UI.Xaml.EventHandler CanExecuteChanged;
        
        public DelegateCommand(Action<object> execute)
            : this(execute, null)
        {
        }




        public DelegateCommand(Action<object> execute,
                       Predicate<object> canExecute)
        {
            _execute = execute;
            _canExecute = canExecute;
        }




        public bool CanExecute(object parameter)
        {
            if (_canExecute == null)
                return true;




            return _canExecute(parameter);
        }




        public void Execute(object parameter)
        {
            _execute(parameter);
        }




        public void RaiseCanExecuteChanged()
        {
            if (CanExecuteChanged != null)
            {
                CanExecuteChanged(this, null);
            }
        }
    } 

请注意,WinRT EventHandler 与 System.EventHandler 不同。 并且 Windows.UI.Xaml.EventHandler 有两个对象参数,第二个不是 EventArgs。

然后在 View 中,我们只需在 Metro 中的任何控件中设置 AttachedCommand

<Button Content="Test Button"
                local:AttachedCommand.RoutedEvent="PointerEntered" local:AttachedCommand.Command="{Binding TestCommand}"/>

参考文献

© . All rights reserved.