WPF的MVVM框架Stylet开发文档 5. Actions

5.Actions(行为)

原文地址

你有一个按钮,你想点击它并在你的 ViewModel 上执行一个方法?操作涵盖此用例。

动作和方法

在“传统”WPF 中,您将在 ViewModel 上创建一个实现ICommand接口的属性,并将按钮的 Command 属性绑定到它。这工作得相当好(ViewModel 对 View 一无所知,并且不需要代码隐藏),但它有点混乱 - 你真的想在 ViewModel 上调用一个方法,而不是在某些属性上执行一个方法。

Stylet 通过引入 Actions 解决了这个问题。看这个:

class ViewModel : Screen
{
   public void DoSomething()
   {
      Debug.WriteLine("DoSomething called");
   }
}
<UserControl x:Class="MyNamespace.View"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:s="https://github.com/canton7/Stylet">
   <Button Command="{s:Action DoSomething}">Click meButton>
UserControl>

正如您可能已经猜到的那样,单击要调用的 ViewModel 上的称为 DoSomething方法的按钮。

就是这么简单。

如果您的方法接受单个参数,则将传递按钮的 CommandParameter 属性的值。例如:

class ViewModel : Screen
{
   public void DoSomething(string argument)
   {
      Debug.WriteLine(String.Format("Argument is {0}", argument));
   }
}
<UserControl x:Class="MyNamespace.View"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:s="https://github.com/canton7/Stylet">
   <Button Command="{s:Action DoSomething}" CommandParameter="Hello">Click meButton>
UserControl>

请注意,Actions 也适用于任何ICommand 属性,任何东西(例如KeyBinding)。

保护属性

您还可以使用Guard Properties轻松控制是否启用按钮。给定方法的保护属性是一个名为“Can”的布尔属性,因此如果您的方法称为“DoSomething”,则相应的保护属性称为“CanDoSomething”。

Stylet 将检查是否存在保护属性,如果存在,如果返回 false,则禁用按钮,如果返回 true,则启用它。它还将监视该属性的 PropertyChanged通知,因此您可以更改按钮是否启用。

例如:

class ViewModel : Screen
{
   private bool _canDoSomething;
   public bool CanDoSomething
   {
      get { return this._canDoSomething; }
      set { this.SetAndNotify(ref this._canDoSomething, value); }
   }
   public void DoSomething()
   {
      Debug.WriteLine("DoSomething called");
   }
}

事件Events

但是如果你想在事件发生时调用 ViewModel 方法呢?行动也涵盖了这一点。语法完全相同,尽管这里没有保护属性的概念。

<UserControl x:Class="MyNamespace.View"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:s="https://github.com/canton7/Stylet">
   <Button Click="{s:Action DoSomething}">Click meButton>
UserControl>

被调用的方法必须有零个、一个或两个参数。可能的签名是:

public void HasNoArguments() { }
 
// 这可以接受 EventArgs 或 EventArgs 的子类
public void HasOneSingleArgument(EventArgs e) { }
 
// 同样,EventArgs 的子类是可以的
public void HasTwoArguments(object sender, EventArgs e) { }

方法返回类型

Actions 不关心方法的返回类型,返回值被丢弃。

例外情况是如果返回一个Task(例如,如果被调用的方法是async)。在这种情况下,Task将在async void方法中等待。这意味着如果该方法返回一个Task最终包含异常的方法,则该异常将被重新抛出,并将冒泡到 Dispatcher,在那里它将终止您的应用程序(除非您使用 BootstrapperBase.OnUnhandledException处理它)。效果与被调用的方法相同async void,但意味着更容易对asyncViewModel 方法进行单元测试。

The Action Target行动目标

到目前为止,我一直在撒些善意的谎言。我一直在说 Action 是在 ViewModel 上调用的,但这并不完全正确。让我们更详细一点。

Stylet 定义了一个继承的附加属性,称为 View.ActionTarget。当 View 绑定到其 ViewModel 时,View 中根元素上的 View.ActionTarget 将绑定到 ViewModel,然后由 View 中的每个元素继承。当您调用一个操作时,它会在 View.ActionTarget上调用。

这意味着,默认情况下,无论当前的 DataContext 是什么,都会在 ViewModel 上调用操作,这可能是您想要的。

这是非常重要的一点,也是值得强调的一点。DataContext可能会在整个可视化树中的多个点发生变化。但是, View.ActionTarget 将保持不变(除非您手动更改它)。这意味着操作将始终由您的 ViewModel 处理,而不是由绑定到的任何对象处理,这几乎总是您想要的。

您当然可以更改单个元素的 View.ActionTarget,例如:

class InnerViewModel : Screen
{
   public void DoSomething() { }
}
class ViewModel : Screen
{
   public InnerViewModel InnerViewModel { get; private set; }
   public ViewModel()
   {
      this.InnerViewModel = new InnerViewModel();
   }
}
<UserControl x:Class="MyNamespace.View"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:s="https://github.com/canton7/Stylet">
   <Button s:View.ActionTarget="{Binding InnerViewModel}" Command="{s:Action DoSomething}">Click meButton>
UserControl>

在这里,单击按钮时将调用 InnerViewModel.DoSomething。此外,由于 View.ActionTarget 是继承的,因此 Button 的任何子级也将其 View.ActionTarget 设置为 InnerViewModel

您还可以使用其Target属性覆盖操作的目标。然而,由于WPF的限制,你不能绑定到这一点,你必须使用StaticResourcex:Staticx:Type等。

<Button Command="{s:Action DoSomething, Target={x:Static my:Globals.ButtonTarget}}">Click meButton>

静态方法

如果目标是对象(为此在 XAML 中Type使用),操作也可以调用静态方法。{x:Type ...}您可以同时使用View.ActionTarget和 Action 的Target属性来设置它。

public static class CommonButtonTarget
{
    public static void DoSomething() { }
}

<Button Command="{s:Action DoSomething, Target={x:Type my:CommonButtonTarget}}">Click meButton>

动作和风格(Actions and Styles)

样式设置器无法执行操作。在 WPF 中执行此操作所需的类都是内部的,这意味着无法解决此问题。不幸的是,在这种(罕见的)情况下,您将需要使用老式命令。

陷阱(Gotchas )-上下文菜单和弹出窗口

View.ActionTarget 当然是一个附加属性,它被配置为由它所设置的任何元素的子元素继承。与任何附加属性(实际上是 DataContext)一样,它没有继承某些边界,例如:

  • 使用上下文菜单
  • 使用弹出窗口
  • 使用框架

在这些情况下,Stylet 将尽其所能找到合适的 ActionTarget(例如,它可能会找到与当前 XAML 文件中的根元素相关联的 ActionTarget),但这可能不是您所期望的(例如它可能会忽略s:View.ActionTarget="{Binding ...}"页面中间某处的一行),或者它可能(在极少数情况下)根本找不到 ActionTarget。

在这种情况下,请设置s:View.ActionTarget为合适的值。您可能很难从一个 ContextMenu 内部获取对 ContextMenu 外部任何内容的引用:我建议使用BindingProxy技术。

附加行为

有两种情况会阻止操作正常工作:如果View.ActionTarget为空,或者如果指定的方法View.ActionTarget不存在。每种情况下的默认行为如下:



View.ActionTarget == null
No method on View.ActionTarget
Commands命令 Disable the control
禁用控件
Throw an exception when the control is clicked
单击控件时抛出异常
Events事件 Enable the control
启用控件
Throw an exception when the event is raised
引发事件时抛出异常

这样做的理由是,如果为View.ActionTargetnull,则您必须自己设置它,因此您可能知道自己在做什么。但是,如果指定的方法在 上不存在View.ActionTarget,那可能是一个错误,您应该知道。

当然,此行为是可配置的。

View.ActionTarget要控制为 null时的行为,请NullTarget在标记扩展上设置属性,Action以便、 或。(请注意,当操作链接到事件时无效,因为我们无权禁用任何东西)。Enable``Disable``Throw``Disable

例如:

<Button Command="{s:Action MyMethod, NullTarget=Enable}"/>
<Button Click="{s:Action MyMethod, NullTarget=Throw}"/>

同样,您可以将ActionNotFound属性设置为相同的值:

<Button Command="{s:Action MyMethod, ActionNotFound=Disable}"/>
<Button Click="{s:Action MyMethod, ActionNotFound=Enable}"/>

项目原地址:https://github.com/canton7/Stylet
当前文档原地址:https://github.com/canton7/Stylet/wiki/Actions

上一篇:WPF的MVVM框架Stylet开发文档 4. 视图模型优先ViewModel-first
下一篇:WPF的MVVM框架Stylet开发文档 6. 窗口管理器

你可能感兴趣的:(MVVM,Stylet框架,wpf,microsoft,c#)