WPF之事件綁定命令
事件綁定意義
一般事件的處理程式都放在介面後台,通過事件綁定可以把事件處理程式放在ViewModel中,實現介面和邏輯的解耦。
要使用事件綁定需要藉助System.Windows.interactivity(安裝了Blend就有),如果電腦上找不到,可以通過NuGet安裝System.Windows.Interactivity.WPF。
需要引用以下命名空間:
xmlns:i="//schemas.microsoft.com/expression/2010/interactivity"
using System.Windows.Interactivity;
無參數的事件綁定
在Interaction.Triggers裡面添加一個或多個EventTrigger並指定關注的的事件名稱,在EventTrigger中通過InvokeCommandAction來綁定事件對應的命令,在事件觸發後會調用綁定的命令對象的Execute方法執行命令。
命令的實現參考WPF之自定義委託命令,事件觸發後是否能夠真正執行綁定的命令也受到綁定的命令的CanExecute方法的影響。事件綁定過程如下:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
帶EventArgs參數的事件綁定
InvokeCommandAction在未對CommandParameter綁定的情況下給Execute方法傳遞的參數為null,對CommandParameter綁定的情況下給Execute方法傳遞的參數為綁定值(不是EventArgs參數),Execute方法是由Invoke(object parameter)調用的。
其實,TiggerBase調用InvokeCommandAction的Invoke(object parameter)方法時有傳入EventArgs參數,但Invoke調用Execute方法時一直使用的是CommandParameter參數。有一說一,這個程式邏輯有點反人類,這也是網上為什麼有這麼多重新實現InvokeCommandAction資料的原因。
如果需要從事件的EventArgs中獲取數據,正常來說派生InvokeCommandAction然後「重寫」Invoke方法即可。但是,InvokeCommandAction是密封類,我們只能參照源碼重新實現一個EventCommandAction類,程式碼如下:
using System.Windows;
using System.Windows.Input;
using System.Windows.Interactivity;
namespace WpfApp
{
public class EventCommandAction : TriggerAction<DependencyObject>
{
/// <summary>
/// 事件要綁定的命令
/// </summary>
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
// Using a DependencyProperty as the backing store for MsgName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(EventCommandAction), new PropertyMetadata(null));
/// <summary>
/// 綁定命令的參數,保持為空就是事件的參數
/// </summary>
public object CommandParameter
{
get { return (object)GetValue(CommandParateterProperty); }
set { SetValue(CommandParateterProperty, value); }
}
// Using a DependencyProperty as the backing store for CommandParateter. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CommandParateterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(EventCommandAction), new PropertyMetadata(null));
//執行事件
protected override void Invoke(object parameter)
{
if (CommandParameter != null)
parameter = CommandParameter;
var cmd = Command;
if (cmd != null&&cmd.CanExecute(parameter))
cmd.Execute(parameter);
}
}
}
使用事件綁定
創建一個MainViewModel,這裡要使用之前的數據綁定基類BindableBase(參考WPF之數據綁定基類),程式碼如下:
class MainViewModel:BindableBase
{
public bool CanExecute { get; set; }
private string tipText;
public string TipText
{
get { return tipText; }
set { SetProperty(ref tipText, value); }
}
public DelegateCommand LoadedCommand { get; }
public DelegateCommand<MouseEventArgs> MouseMoveCommand { get; }
public MainViewModel()
{
LoadedCommand = new DelegateCommand(() => { MessageBox.Show("程式載入成功");});
MouseMoveCommand = new DelegateCommand<MouseEventArgs>(e =>
{
TipText = "滑鼠當前位置:" + e.GetPosition(e.Device.Target).ToString();
},
e =>CanExecute);
}
}
介面的XAML程式碼如下:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadedCommand}"/>
</i:EventTrigger>
<i:EventTrigger EventName="MouseMove">
<local:EventCommandAction Command="{Binding MouseMoveCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<StackPanel>
<CheckBox Content="命令開關" IsChecked="{Binding CanExecute}"/>
<Label Content="{Binding TipText}" Margin="5"/>
</StackPanel>
在後台程式碼中添加DataContext:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
運行程式,效果如下:
擴展:基於InvokeCommandAction源碼的實現(推薦)
可以在InvokeCommandAction源碼基礎改動一下Invoke方法,實現我們的需求,改動如下:
protected override void Invoke(object parameter)
{
if (base.AssociatedObject != null)
{
if (CommandParameter != null)
parameter = CommandParameter;
ICommand command = ResolveCommand();
if (command != null && command.CanExecute(parameter))
{
command.Execute(parameter);
}
}
}
重寫的RewriteInvokeCommandAction類完全可以替代上面的EventCommandAction,完整版程式碼如下:
public sealed class RewriteInvokeCommandAction : TriggerAction<DependencyObject>
{
private string commandName;
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(RewriteInvokeCommandAction), null);
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(object), typeof(RewriteInvokeCommandAction), null);
public string CommandName
{
get
{
ReadPreamble();
return commandName;
}
set
{
if (CommandName != value)
{
WritePreamble();
commandName = value;
WritePostscript();
}
}
}
public ICommand Command
{
get
{
return (ICommand)GetValue(CommandProperty);
}
set
{
SetValue(CommandProperty, value);
}
}
public object CommandParameter
{
get
{
return GetValue(CommandParameterProperty);
}
set
{
SetValue(CommandParameterProperty, value);
}
}
protected override void Invoke(object parameter)
{
if (base.AssociatedObject != null)
{
if (CommandParameter != null)
parameter = CommandParameter;
ICommand command = ResolveCommand();
if (command != null && command.CanExecute(parameter))
{
command.Execute(parameter);
}
}
}
private ICommand ResolveCommand()
{
ICommand result = null;
if (Command != null)
{
result = Command;
}
else if (base.AssociatedObject != null)
{
Type type = base.AssociatedObject.GetType();
PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
PropertyInfo[] array = properties;
foreach (PropertyInfo propertyInfo in array)
{
if (typeof(ICommand).IsAssignableFrom(propertyInfo.PropertyType) && string.Equals(propertyInfo.Name, CommandName, StringComparison.Ordinal))
{
result = (ICommand)propertyInfo.GetValue(base.AssociatedObject, null);
}
}
}
return result;
}
}
參考資料
MVVM設計模式和在WPF中的實現(四)事件綁定
EventTrigger原理淺談
WPF:MVVM:命令與CallMethodAction?