有一個按鈕,想要單擊並執行一個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}"/>