在MVVM模式中,通常将值属性和命令声明在ViewModel中,并通过DataBinding在视图上使用。这样可以实现"数据驱动UI"的功能。Metro风格的应用程序虽然使用XAML和WinRT组件,但仍然可以使用MVVM作为其架构。Metro支持DataBinding,可以让"数据"驱动"Metro UI"。
虽然在ViewModel中实现属性和命令相对容易,但将UI控件的事件分配给命令却不太容易。在WPF中,可以使用Blend SDK的行为来将事件分配给命令,或者设计一个AttachedCommand,如下所示: 。 但是,检查Metro的MVVM Light Toolkit,它不能提供EventToCommand行为,Blend 5 Developer Preview版本也不提供该行为。因此,需要为Metro设计一个AttachedCommand。
在AttachedCommand类中,应该声明两个附加属性:Command和RoutedEvent属性。
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached(
"Command",
typeof(Object),
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);
}
public static readonly DependencyProperty RoutedEventProperty =
DependencyProperty.RegisterAttached(
"RoutedEvent",
typeof(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解决方案),对于接口,应该使用"Object"而不是接口。不确定是否会在下一个Windows 8版本中解决,但是对于接口,它会引发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的DependencyObject来获取EventInfo。但在Metro中,反射只能列出当前类型直接声明的成员。它不能列出从基类型继承的所有成员。因此,应该使用一种方法来从其基类型中搜索事件成员:
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)
{
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
请注意,WinRT EventHandler与System.EventHandler不同。Windows.UI.Xaml.EventHandler有两个object参数,第二个参数不是EventArgs。