輕量級MVVM框架Stylet介紹:(5) Actions


有一個按鈕,想要單擊並執行一個ViewModel的方法?Action可以解決這個問題。

Actions與方法

在傳統的WPF中,你需要在ViewModel中創建一個屬性並實現ICommand接口,然后將此屬性綁定到按鈕的Command屬性上,這可以工作(不需要ViewModel與View的緊密聯系,也不需要Code-behind.),但是這有點小麻煩,你只是想要調用ViewModel中的一個方法而已,不是在屬性中調用方法。

Stylet優雅地解決了這個問題:

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 me</Button>
</UserControl>

就是這么簡單,點擊按鈕,執行方法!

如果方法還接受一個單一的參數,可以通過按鈕的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 me</Button>
</UserControl>

注意:Actions也支持ICommand屬性的其他特性,例如KeyBinding.

守護屬性

使用Stylet可以輕松控制是否使能按鈕,我稱之為守護屬性(Guard Properties)。一個給定方法的守護屬性是一個布爾型的屬性,命名為"Can<方法名>",比如方法名為“DoSomething”,相應的守護屬性為"CanDoSomething".

Stylet會檢查守護屬性是否存在,然后監測屬性返回值,如果返回False,將會禁用按鈕,反之使用能按鈕。Stylet同時還會監測屬性的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");
   }
}

事件

當事件發生時想要調用ViewModel的方法應該怎么做呢?Actions同樣可以做到。語法是一樣的,只是沒有守護屬性概念而已:

<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 me</Button>
</UserControl>

相對應的方法必須具有0-2個參數,可能的方法簽名如下:


public void HasNoArguments() { }
 
// This can accept EventArgs, or a subclass of EventArgs
public void HasOneSingleArgument(EventArgs e) { }
 
// Again, a subclass of EventArgs is OK
public void HasTwoArguments(object sender, EventArgs e) { }

方法的返回類型

Action並不關心方法的返回類型,返回值會被丟棄。

例外的情況是如果返回值為Task(即方法是異步觸發的)。這種情況下,Task等待async void方法的返回。如果方法返回Task並且包含異常,該異常會再次throw並且冒泡到Dispather,這會中斷應用程序。除非處理這個異常,例如使用BootstrapperBase.OnUnhandledException。如果方法使用async void方式,但是這不利於單元測試。

Action目標

Action並非只能觸發ViewModel中的方法,下面再詳細討論一下。

Stylet定義了一個繼承的附加屬性:View.ActionTarget。當View與其ViewModel相綁定,View根元素的 View.ActionTarget就被綁定到ViewModel,View中的所有元素也被繼承了這個屬性。當觸發一個action,就觸發相應的的 View.ActionTarget屬性。

這就是說,默認情況下,actions都是由ViewModel的當前DataContext作為觸發對象,這也是大多數我們期望的情況。

這一點很重要。在可視化樹的多個層次里,DataContext可能會發生變化。但是,View.ActionTarget將保持一致(除非手動修改)。這就意味着action總是由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 me</Button>
</UserControl>

在這里,單擊按鈕,InnerViewModel.DoSomething將會被觸發。並且由於View.ActionTarget是繼承屬性,Button的任何子元素也都會將View.ActionTargetr指向InnerViewModel.

你也可以覆寫action的target(利用Target屬性),但是由於WPF的限制,不能進行綁定,只能使用StaticResource, x:Static, x:Type等。

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

靜態方法

Action也可以使用靜態方法,如果Target是一個Type對象(在XAML中使用{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 me</Button>

Action與樣式

Action並不能與style setters一起工作。WPF中進行樣式設置的類都是內部類,沒有辦法進行修改。

ContextMenu和Popup

對於ContextMenu,Popup,Frame,這些元素通常不屬於可視化或邏輯樹的元素,作為附加屬性的View.ActionTarget需要根據要求作出調整,建議使用BindingProxy技術。
http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/

附加行為

有兩種情況會阻止Action正確工作:View.ActionTarget為null或View.ActionTarget指定的方法並不存在,其默認的行為如下:

兩種情況都應該加以解決,以使代碼能夠工作。

當然這種行為也是可配置的。為了控制View.ActionTarget為null的行為,設置NullTarget屬性,如下:

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

為了控制View.ActionTarget指定的方法不存在的行為,設置ActionNotFound屬性,如下所示:

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM