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?

Tags: