WPF (3) WPFコマンド

1. WPFコマンドの概念

WPF は WinForm とは異なり、WinForm の使い慣れたイベントおよび委任テクノロジを継承することに加えて、完全なコマンド (Command) システム セットも提供します。簡単に言うと、コマンドはタスクを完全にカプセル化したもので、保存、コピー、切り取りなどの操作は個別のコマンドとして理解できます。一見すると、コマンドと従来のイベント メカニズムは非常に似ているように見えます。どちらもいくつかの目的/動作を実行しますが、コマンドとイベントは競合しません。コマンドとイベントの違いは、コマンドがバインドされていることです。

コマンドは MVVM モードの実現の重要な部分です。コマンド (Command)、テンプレート (Template)、データ バインディング (Binding) が一緒になって、WPF の主要な 3 つのコア要素を構成します。テンプレートは、WPF の柔軟なコントロールのカスタマイズを実現します (UI) 、データ バインディングはフロント エンドとバック エンドのデータ分離を実現し、コマンドはフロント エンドとバック エンドの論理的な分離を実現します。コマンドの特徴は以下の通りです。

  • 再利用:コマンド ロジックを統合し、コードの冗長性を減らし、独立した再利用可能なタスク実行ユニットをカプセル化します。従来のイベントメカニズムでは、多くのビヘイビアーが同じイベントロジックを持っていますが、異なるインターフェイス/コントロールで使用する場合は個別に書き直す必要があり、コードが増えるとプロジェクトの保守がますます困難になります。しかし、コマンドを使用すると、各タイプのユーザー アクションを同じロジックにバインドできます。たとえば、多くのアプリケーションで一般的な編集操作「コピー」、「カット」、「ペースト」はコマンドを使用して実現できるため、複数の異なるソースで同じコマンド ロジックを呼び出し、異なるユーザー アクションを使用できます。彼らに電話するために。
  • 分離:コマンド ソースとコマンド ターゲットをコマンドを通じて分離して、コードの結合を減らすことができます。コマンドは独立してカプセル化されたオブジェクトであるため、タスクの実行中、コマンド ソースとコマンド ターゲットは相互に直接参照せず、コマンド オブジェクトを介して接続として分離されます。データ バインディングと同様に、ここでも動作バインディングを実装します。
  • 状態の同期:コマンドは、コントロールの有効な状態を対応するコマンドの状態と同期させ、アクションが利用可能かどうかを示します。つまり、コマンドが無効になると、その時点でコマンドにバインドされているコントロールも無効になります。

2. WPFの基本コマンド体系

WPF は、いくつかの一般的な操作と動作を完了するための基本的な組み込みコマンド システムのセットを提供します。まず、WPF の基本的なコマンド システム モデルと実行原理を紹介してから、次のカスタム コマンドを紹介します。WPF コマンド システム モデルは、主に次の 4 つの要素で構成されます。

  • コマンド (Command): コマンドはタスク単位を表し、タスクのステータスを追跡できます。実際には、ICommand インターフェイスを実装するクラスです。ただし、コマンドには実際にタスク実行用のロジック コードが含まれる場合と含まれない場合があり、コマンドのソースとコマンドのターゲットをリンクする仲介として機能するだけです。たとえば、WPF の既定のインターフェイス実装クラス RoutedCommand には、内部に実際の実行コードが含まれておらず、「用事の実行」のみを担当し、操作は担当しません。通常、カスタム コマンドはコマンド内でタスク操作を実行します。
  • コマンド ソース (CommandSource): コマンドの送信側/動作トリガーは、実際にはICommandSource インターフェイスを実装するクラスです。WPF の多くの UI 要素 (InputBinding、MenuItem、ButtonBase (ボタン ファミリー) など) は、デフォルトでこのインターフェイスを実装します。Click をクリックすると Button がバインドされたコマンドを呼び出すなど、WPF コマンド システムの既定のコマンド呼び出し動作を使用します。通常、コマンド ソースは、それに関連付けられたコマンドが現在のコマンド ターゲットで実行できない場合、それ自体を無効にします (コマンド ステータスはコマンド ソースのステータスに影響します)。コマンドソースには、Command属性(コマンドのバインド)、CommandTarget属性(コマンドターゲットの指定)、CommandParameter属性(コマンドパラメータの受け渡し)があります。
  • コマンド ターゲット (CommandTarget): コマンドの受信者/コマンドのオブジェクト。コマンド ターゲットは IInputElement インターフェイスを実装するクラスである必要がありますが、幸いなことに、WPF のほとんどすべての UI 要素がこのインターフェイスを実装しています。注意すべき点が 3 つあります: 1 つ目は、コマンド ターゲットはコマンドの属性ではなく、コマンド ソースの属性です (コマンドの送信先を示すためにコマンド ソースに設定されます)、2 つ目は、コマンド ターゲットが存在しない場合です。コマンド ソースに指定されている場合、デフォルトの現在のフォーカス オブジェクトはコマンド ターゲットです (ただし、効果的ではない可能性があります)。3つ目は、設定された CommandTarget は、ICommandSource が ICommand コマンドにバインドされ、組み込みコマンドとして実装された場合にのみ有効になることです。 RoutedCommand でそれ以外の場合、コマンド ターゲットは無視され、デフォルトの動作が使用されます。
  • コマンド バインディング: 一部の周辺ロジックをコマンドに関連付ける役割を果たし、コマンドが実行前後の操作を実行できるかどうか、コマンド実行前後の操作など、コマンド ロジックをコマンドにマップするオブジェクトです。CommandBinding は通常、コマンド ターゲットのペリフェラル コントロールに設定され、コマンド ターゲットによって送信されたルーティング イベントを上から下までリッスンして、コマンド ステータスをフィードバックしたり、RoutedCommand などの特定のタスク ロジック実装を持たない「用事の実行」コマンドをフィードバックしたりします。これは、関連する CommandBinding Purpose タスク実行ロジックに実装されます。コマンド関連付けは、Command 属性(バインディング リスニング コマンド)、CanExecute 属性(バインディング コマンド CanExecute ルーティング イベント処理メソッド)、Executed 属性(バインディング コマンド Execute ルーティング イベント処理メソッド)を持ちます。

2.1 コマンドインターフェースとその構造的関係

(1) ICommandのソースコード

ExecuteメソッドはPreviewExecutedおよびExecutedイベントをトリガーし、メソッドCanExecute はPreviewCanExecuteおよびCanExecuteイベントをトリガーします

public interface ICommand
{
    
    
    //1.Events: 当命令的可执行状态发生改变时,触发此事件
    event EventHandler CanExecuteChanged;

    //2.CanExecute方法: 返回当前命令的状态,指示命令是否可执行(true则可执行,false则不可执行)
    //	- 方法实现:实现该方法时,方法内常定义一些命定是否可执行状态的判断逻辑。
    //	- 举例:例如某文本框中没有选择任何文本,此时其Copy命令是不可用的,CanExecute则应该返回为false。
    bool CanExecute(object parameter);
    
    //3.Execute方法: 命令执行的逻辑代码,指示命令执行的行为
    void Execute(object parameter);
}

(2)Iコマンドソース

属性:
- Command(ICommand):获取在调用命令源时执行的命令。
- CommandParameter(object):表示可在执行命令时传递给该命令的用户定义的数据值。
- CommandTarget(IInputElement):在其上执行该命令的对象。仅限于RoutedCommand时生效,若不指定则默认为具有焦点的元素

(3)コマンドバインディング

- 介绍:将 RoutedCommand 绑定到实现该命令的事件处理程序,仅与 RoutedCommand 命令配合使用,关联侦听其激发的路由事件。该类通过实现 PreviewExecuted/Executed 和 PreviewCanExecute/CanExecute 事件处理方法,侦听关联命令的状态改变。
- 属性:
	- Command: 获取或设置与此 CommandBinding 关联的 ICommand。
- 事件:
	- CanExecute: 在与此 CommandBinding 关联的命令开始检查能否对命令目标执行该命令时发生。(RoutedCommand 上的 CanExecute 方法执行后引发),执行频率较高
	- Executed: 执行与此 CommandBinding 相关联的命令时发生。(RoutedCommand 上的 Execute 方法执行后引发)
	
- CommandBinding 事件处理参数:
	- CanExecuteRoutedEventArgs e:
		- e.CanExecute : 关联控件的可用状态,标识命令是否可执行
		- e.Handled: 指示针对路由事件(在其经过路由时)的事件处理的当前状态。默认值是 false。如果事件将标记为已处理,则设置为 true。事件将不再继续向上传递,提高效率
		- e.Command: 获取与此事件关联的命令。
		- e.Parameter: 获取命令传递的参数。object,默认值是 null。
		- e.Source: 获取或设置对引发事件的对象的引用。
		
	- ExecutedRoutedEventArgs e:
		- e.Command: 获取调用过的命令。
		- e.Handled: 该值指示针对路由事件(在其经过路由时)的事件处理的当前状态。
		- e.Parameter: 获取命令传递的数据参数。object,默认值是 null。
		- e.Source: 获取或设置对引发事件的对象的引用。

(4) WPFコマンドの継承関係
ここに画像の説明を挿入

このコマンドには「1 か所で宣言し、どこでも使用できる」という特徴があるため、上記のことから、RoutedCommand 一連のコマンド (RoutedUICommand を含む) には特定のビジネス ロジックが含まれておらず、配信のトリガーのみを担うことがわかります。ルーティングイベントの機能を実行し、「用事を実行する」機能を果たします。したがって、そのようなコマンドは比較的一般的であり、多くの場合、これらのコマンドによって定義された意味論的な関係のみが必要になります。そのため、Microsoft は WPF コマンド ライブラリで定義された便利なコマンドをいくつか用意しました。これらはすべて静的クラスであり、コマンドはこれらのクラスの静的な読み取り専用プロパティを使用してシングルトン パターンで公開されます。これらのコマンド ライブラリには次のものが含まれます。

  • アプリケーションコマンド
  • コンポーネントコマンド
  • ナビゲーションコマンド
  • メディアコマンド
  • 編集コマンド
public static class ApplicationCommands
{
    
    
        
        public static RoutedUICommand Cut
        {
    
    
            get {
    
     return _EnsureCommand(CommandId.Cut); }
        }
 
       
        public static RoutedUICommand Copy
        {
    
    
            get {
    
     return _EnsureCommand(CommandId.Copy); }
        }
 
        
        public static RoutedUICommand Paste
        {
    
    
            get {
    
     return _EnsureCommand(CommandId.Paste); }
        }
        
        ...
 }

2.2 コマンドの簡単な使い方

要件の背景: テキスト入力ボックスとクリア ボタンを実装します。ボタン ボタンは、テキスト ボックスにテキストがある場合にのみ使用できます。ボタンをクリックすると、テキスト入力ボックスをクリアするために使用されます。

(1)XAMLのバージョン

<Window x:Class="WPF_Demo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPF_Demo"
        mc:Ignorable="d" 
        Title="MainWindow" Height="175" Width="260">
    <!--CommandBinding放置于命令目标的外围控件上,进行激发路由事件的监听-->
    <Window.CommandBindings>
        <CommandBinding Command="ApplicationCommands.Delete" Executed="CommandBinding_Executed" CanExecute="CommandBinding_CanExecute"/>
    </Window.CommandBindings>

    <StackPanel Background="LightBlue">
        <!--WPF内置命令都可以采用其缩写形式-->
        <Button x:Name="button_clear" Content="Clear" Margin="5" 
                Command="Delete" CommandTarget="{Binding ElementName=textbox_info}"/>
        <TextBox x:Name="textbox_info" Margin="5,0" Height="100"/>
    </StackPanel>
    
</Window>
namespace WPF_Demo
{
    
    
    public partial class MainWindow : Window
    {
    
    
        public MainWindow()
        {
    
    
            InitializeComponent();
        }

        //侦听到 命令Execute 执行后路由事件的处理方法Executed
        private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
        {
    
    
            this.textbox_c.Clear();
            //避免继续向上传递而降低程序性能
            e.Handled = true;
        }

        //CanExecute方法: 命令是否可执行,关联命令源状态是否可用
        //  - sender:事件源对象
        //  - e: 事件参数
        private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
    
    
            if (this.textbox_info==null || string.IsNullOrEmpty(this.textbox_info.Text))
            {
    
    
                e.CanExecute = false;
            }
            else
            {
    
    
                e.CanExecute = true;
            }
            //避免继续向上传递而降低程序性能
            e.Handled = true;
        }
    }

}

(2) コードバージョン

<Window x:Class="WPF_Demo.ControlWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPF_Demo"
        mc:Ignorable="d"
        Title="ControlWindow" Height="175" Width="300">

    <StackPanel Background="LightBlue">
        <Button x:Name="button_clear" Content="Clear" Margin="5" />
        <TextBox x:Name="textbox_info" Margin="5,0" Height="100"/>
    </StackPanel>
</Window>
namespace WPF_Demo
{
    
    
    public partial class ControlWindow : Window
    {
    
    
        public ControlWindow()
        {
    
    
            InitializeComponent();
            //后台代码创建命令绑定
            CommandBinding bindingNew = new CommandBinding(ApplicationCommands.Delete);
            bindingNew.Executed += CommandBinding_Executed;
            bindingNew.CanExecute += CommandBinding_CanExecute;
            //将创建的命令绑定添加到窗口的CommandBindings集合中
            this.CommandBindings.Add(bindingNew);
            //命令源配置
            this.button_clear.Command = ApplicationCommands.Delete;
            this.button_clear.CommandTarget = this.textbox_info;
        }


        void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
    
    
            if (this.textbox_info==null || string.IsNullOrEmpty(this.textbox_info.Text))
            {
    
    
                e.CanExecute = false;
            }
            else
            {
    
    
                e.CanExecute = true;
            }
            //避免继续向上传递而降低程序性能
            e.Handled = true;
        }

        void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
        {
    
    
            //e.Source 事件源对象
            this.textbox_info.Clear();
            //避免继续向上传递而降低程序性能
            e.Handled = true;
        }
    }
}

2.3 コマンド実行の原理

これを見ると、ボタンをクリックするとなぜコマンドがトリガーされるのかなど、多くの混乱が生じるかもしれません。コマンドのロジックが CommandBinding によって処理されるのはなぜですか? 彼らはどのようにして相互に通信するのでしょうか? コマンドはどのように機能するのでしょうか? 次に、WPF コマンド システムの動作原理とワークフローについて簡単に説明します。つまり、RoutedCommandで Execute メソッドを実行すると、コマンド ターゲットで PreviewExecuted および Executed イベントが発生します。RoutedCommand で CanExecute メソッドを実行すると、コマンド ターゲットで CanExecute および PreviewCanExecute イベントが発生します。これらのイベントは、その特定のコマンドをバインド/リッスンする CommandBinding オブジェクトに遭遇するまで、要素ツリーをトンネリングしてバブルダウンし、その後 CommandBinding によって捕捉されて処理されます。これにより、RoutedCommand コマンド ロジックが独立した汎用的なものになります。ここでは、ボタンのクリックトリガーで分析を開始します。

(1) ボタンクリック時にコマンドを送信

ButtonBase は、Click イベントが発生するとコマンドを送信し、Click イベントは OnClick メソッドでトリガーされます。ButtonBase の OnClick メソッドのソース コードは次のとおりです。

 public abstract class ButtonBase : ContentControl, ICommandSource
{
    
    
		//激发Click路由事件,然后发送命令
 		protected virtual void OnClick()
        {
    
    	//触发Click事件
            RoutedEventArgs newEvent = new RoutedEventArgs(ButtonBase.ClickEvent, this);
            RaiseEvent(newEvent);
 			//发送命令,调用内部类CommandHelpers的ExecuteCommandSource方法,将自己作为参数
            MS.Internal.Commands.CommandHelpers.ExecuteCommandSource(this);
        }
 }

CommandHelpers の ExecuteCommandSource メソッド (このクラスは外部に公開されません) は、実際には受信パラメーターをコマンド ソースとして受け取り、コマンド ソースの ExecuteCore メソッドを呼び出します (基本的にその ExecuteImpl メソッドを呼び出します)。コマンドソース (コマンドターゲット) を指定し、コマンドをコマンドターゲット上で動作させます。

internal static class CommandHelpers
{
    
    
		
		internal static void ExecuteCommandSource(ICommandSource commandSource)
        {
    
    
            CriticalExecuteCommandSource(commandSource, false);
        }
 
        internal static void CriticalExecuteCommandSource(ICommandSource commandSource, bool userInitiated)
        {
    
    
        	//1.获取命令源上绑定的命令
            ICommand command = commandSource.Command;
            if (command != null)
            {
    
    
            	//2.获取命令参数CommandParameter、命令目标CommandTarget
                object parameter = commandSource.CommandParameter;
                IInputElement target = commandSource.CommandTarget;
 				//3.判断命令是否是RoutedCommand系列的路由命令
                RoutedCommand routed = command as RoutedCommand;
                if (routed != null)
                {
    
    
                    if (target == null)
                    {
    
    
                        target = commandSource as IInputElement;
                    }
                    //3.1 在命令目标上执行CanExecute方法
                    if (routed.CanExecute(parameter, target))
                    {
    
    
                    	//3.2 在命令目标上执行ExecuteCore方法
                        routed.ExecuteCore(parameter, target, userInitiated);
                    }
                }
                //4.不是路由命令的话,则直接调用命令的CanExecute和Execute方法
                else if (command.CanExecute(parameter))
                {
    
    
                    command.Execute(parameter);
                }
            }
        }
}

次に、RoutedCommand のソース コード、コマンド ロジックの実行方法を見てみましょう: RoutedCommand が ICommand インターフェイスから継承した Execute メソッドは非推奨と言え、実際の実行内容は ExecuteCore と ExecuteImpl に置かれます。内容は簡単です。最初はコマンド ターゲットの RaiseEvent を借用して RoutedEvent を送信し、残りは周辺機器の CommandBinding によってキャプチャされて処理されます。

public class RoutedCommand : ICommand
{
    
    
		//由ICommand继承而来,仅由内部调用
		void ICommand.Execute(object parameter)
        {
    
    
            Execute(parameter, FilterInputElement(Keyboard.FocusedElement));
        }
 
		//由ICommand继承而来,仅由内部调用
        bool ICommand.CanExecute(object parameter)
        {
    
    
            bool unused;
            return CanExecuteImpl(parameter, FilterInputElement(Keyboard.FocusedElement), false, out unused);
        }
        
        //新定义的Execute方法,可由外部调用
        public void Execute(object parameter, IInputElement target)
        {
    
    
        	//若命令目标为空,则选定当前焦点对象
            if (target == null)
            {
    
    
                target = FilterInputElement(Keyboard.FocusedElement);
            }
 			//真正执行命令逻辑的方法--ExecuteImpl
            ExecuteImpl(parameter, target, false);
        }
		//新定义的canExecute方法,可由外部调用
        public bool CanExecute(object parameter, IInputElement target)
        {
    
    
            bool unused;
            return CriticalCanExecute(parameter, target, false, out unused);
        }
        
        //*canExecute方法的真正执行:递归Raise Event,仅内部可用
        private bool CanExecuteImpl(object parameter, IInputElement target, bool trusted, out bool continueRouting)
        {
    
    
            if ((target != null) && !IsBlockedByRM)
            {
    
    
                CanExecuteRoutedEventArgs args = new CanExecuteRoutedEventArgs(this, parameter);
                args.RoutedEvent = CommandManager.PreviewCanExecuteEvent;
                //CriticalCanExecuteWrapper方法将Event包装后,由Target Raise出去
                CriticalCanExecuteWrapper(parameter, target, trusted, args);
                //如果RoutedEvent没有处理完成
                if (!args.Handled)
                {
    
    
                	//不断递归Raise出去(达到不断向外发送Event的效果)
                    args.RoutedEvent = CommandManager.CanExecuteEvent;
                    CriticalCanExecuteWrapper(parameter, target, trusted, args);
                }
 
                continueRouting = args.ContinueRouting;
                return args.CanExecute;
            }
            //...
        }
        
        //另一个调用ExecuteImpl方法的函数,依序集级别可用
        internal bool ExecuteCore(object parameter, IInputElement target, bool userInitiated)
        {
    
    
            if (target == null)
            {
    
    
                target = FilterInputElement(Keyboard.FocusedElement);
            }
 
            return ExecuteImpl(parameter, target, userInitiated);
        }
 
		//*Execute方法的真正执行:命令逻辑执行,仅内部可用
        private bool ExecuteImpl(object parameter, IInputElement target, bool userInitiated)
        {
    
    
            if ((target != null) && !IsBlockedByRM)
            {
    
    
            	//获取命令目标对象
                UIElement targetUIElement = target as UIElement;
               
                //...
                ExecutedRoutedEventArgs args = new ExecutedRoutedEventArgs(this, parameter);
                args.RoutedEvent = CommandManager.PreviewExecutedEvent;
                
                if (targetUIElement != null)
                {
    
    
                	//由命令目标将RoutedEvent Raise出去
                    targetUIElement.RaiseEvent(args, userInitiated);
                }
               //...
 
            return false;
        }
 
}

3. カスタムコマンドシステム

上記の実践後、WPF の基本的なコマンド システムには CommandSource、Command、CommandTarget、および CommandBinding の 4 つの部分が含まれており、それらは互いに非常に密接に関連していることがわかります。メソッドはオーバーライドするために virtual として宣言されており、まだ公開されていないロジックがたくさんあります。つまり、WPF に付属するコマンド ソースと CommandBinding は、RoutedCommand 専用に作成されています。コマンド システムをカスタマイズする場合は、独自のコマンド システムを最初から再実装する必要がある場合があります。

3.1 コマンドをカスタマイズするいくつかの方法

(1) カスタム RoutedCommand

このメソッドは引き続き RoutedCommand シリーズのネイティブ コマンドを使用しています。これは、システムの事前定義コマンド (ApplicationCommands...) を使用していない点を除き、WPF の基本的なコマンド システムの動作原理/メカニズムと同じです。これはカスタマイズと同等です。コマンドのセマンティクスはまさにそのとおりです。コマンドのビジネスロジックを実装するにはまだCommandBindingを使用する必要があり、カスタマイズの度合いは高くありません~

public class DataCommands
{
    
    
        private static RoutedUICommand requery;
        static DataCommands()
        {
    
    
        	//输入笔势/快捷方式
            InputGestureCollection inputs = new InputGestureCollection();
            inputs.Add(new KeyGesture(Key.R, ModifierKeys.Control, "Ctrl+R"));
            //new Command
            requery = new RoutedUICommand(
              "Requery", "Requery", typeof(DataCommands), inputs);
        }
         
        public static RoutedUICommand Requery
        {
    
    
            get {
    
     return requery; }
        }
}
<!--要使用自定义命令,首先需要将.NET命名空间映射为XAML名称空间,这里映射的命名空间为local-->
<Window x:Class="WPFCommand.CustomCommand"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
       
        xmlns:local="clr-namespace:WPFCommand" 
        Title="CustomCommand" Height="300" Width="300" >
       
    <Window.CommandBindings>
        <!--定义命令绑定-->
        <CommandBinding Command="local:CustomCommands.Requery" Executed="RequeryCommand_Execute"/>
    </Window.CommandBindings>
    <StackPanel>
        <!--应用命令-->
        <Button Margin="5" Command="local:CustomCommands.Requery" Content="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"></Button>
    </StackPanel>
</Window>

(2) ICommand インターフェースの実装をカスタマイズする

上記の Command 定義方法はいずれも CommandBinding を使用してコマンド ロジックの実現を支援する必要があり、非常に複雑で面倒ですが、CommandBinding を放棄して Command を直接使用してロジックを実行する方法はありますか? Button の Click ソース コードでキー コードを見つけることができます。

//4.不是路由命令的话,则直接调用命令的CanExecute和Execute方法
else if (command.CanExecute(parameter))
{
    
    
	command.Execute(parameter);
}

非 RoutedCommand の場合、Click は RoutedCommand の RoutedEvent を渡す代わりに、コマンドの Execute メソッドを直接呼び出します。そして、カスタム Command は独自の実行ロジックによって正確に処理されます。これは CommandBinding の周囲を放棄していませんか? ただし、1 つの問題は、ICommand の Execute で UI 要素を処理する方法です。Execute メソッドにはコマンド パラメーター CommandParameter が 1 つしかないため、ここではデータ駆動型の Binding メカニズムについて考えます。バックエンド コードはフロントエンドと直接対話しません。 UI ですが、データはバインドされているため、コードは最終的に次のように記述されます (MVVM の匂いがするでしょうか?)。

//1.自定义命令,实现ICommand接口
namespace WPF_Demo.Commands
{
    
    
    public class MyCommand : ICommand
    {
    
    
        public event EventHandler CanExecuteChanged;

        public bool CanExecute(object parameter)
        {
    
    
            return true;
        }
		//Execute方法中实现命令处理逻辑
        public void Execute(object parameter)
        {
    
    
            //清空数据,UI元素的Binding数据
            MainWindowStatus.Instance.TextboxContent = "";
        }
    }
}
namespace WPF_Demo.Status
{
    
    
	//2.与UI绑定的ViewModel,存储MainWindow窗口UI相关的数据
    class MainWindowStatus: ViewModelBase
    {
    
    

        private MainWindowStatus(){
    
    }
		
		//2.1 TextBox的绑定文本数据
        private string textboxContent = "";
        public string TextboxContent
        {
    
    
            get {
    
     return textboxContent; }
            set
            {
    
    
                if(value != textboxContent)
                {
    
    
                    textboxContent = value;
                    RaisePropertyChanged("TextboxContent");
                }
            }
        }

		//2.2 单例静态模式--单例对象
        private static MainWindowStatus instance;

        public static MainWindowStatus Instance
        {
    
    
            get
            {
    
    
                if (instance == null)
                {
    
    
                    instance = new MainWindowStatus();
                }
                return instance;
            }
            private set {
    
     instance = value; }
        }
	}
}
<!-- UI界面
1.设置MainWindow上下文绑定DataContext="{x:Static vm:MainWindowStatus.Instance}"
2.声明自定义命令实例 <cm:MyCommand x:Key="myCm"/>
-->
<Window x:Class="WPF_Demo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPF_Demo"
        xmlns:vm="clr-namespace:WPF_Demo.Status"
        xmlns:cm="clr-namespace:WPF_Demo.Commands"
        mc:Ignorable="d" 
        DataContext="{x:Static vm:MainWindowStatus.Instance}"
        Title="MainWindow" Height="175" Width="260">

    <Window.Resources>
        <cm:MyCommand x:Key="myCm"/>
    </Window.Resources>

    <StackPanel Background="LightBlue">
        <Button x:Name="button_clear" Content="Clear" Margin="5"  Command="{StaticResource myCm}"/>
        <TextBox x:Name="textbox_c" Margin="5,0" Height="100" Text="{Binding TextboxContent}"/>
    </StackPanel>
    
</Window>

3.2 MVVMにおけるコマンドの定義仕様

上記のカスタム コマンドの例では、データ バインディング、データ駆動型、コマンドの独立した使用など、MVVM の影がすでに確認できます。ただし、上記の例には、まだいくつかの不完全な点があります。UI でコマンドを使用する方法が比較的不完全で (データをバインドするなど、コンテキストを使用してコマンドをバインドできますか?)、コマンドの使用を拡張するのは簡単ではありません (コマンド内のロジックはハードコーディングされているなど)ため、MVVM テンプレートのコマンド定義仕様のセットは次のとおりです。

//1.定义ICommand 自定义通用实现类
namespace ClientNew.Commands
{
    
    
    public class RelayCommand : RelayCommand<object>
    {
    
    
        public RelayCommand(Action<object> action) : base(action) {
    
     }
    }

	//泛型<T>的ICommand接口实现类
    public class RelayCommand<T> : ICommand
    {
    
    
        #region Private Members

        /// <summary>
        /// The _action to run 要执行的委托Action
        /// </summary>
        private Action<T> _action;

        #endregion

        #region Constructor

        /// <summary>
        /// Default constructor
        /// </summary>
        public RelayCommand(Action<T> action)
        {
    
    
            _action = action;
        }

        #endregion

        #region Command Methods

        /// <summary>
        /// A relay command can always execute
        /// </summary>
        /// <param name="parameter"></param>
        /// <returns></returns>
        public bool CanExecute(object parameter)
        {
    
    
            return true;
        }

        /// <summary>
        /// Executes the commands Action 执行Command => 执行传入委托
        /// </summary>
        /// <param name="parameter"></param>
        public void Execute(object parameter)
        {
    
    
            _action((T)parameter);
        }

        #endregion

        #region Public Events

        /// <summary>
        /// The event thats fired when the <see cref="CanExecute(object)"/> value has changed
        /// </summary>
        public event EventHandler CanExecuteChanged = (sender, e) => {
    
     };

        #endregion

    }

}

(1) ViewModel内のバインディングコマンドのロジック(バインディングデータなど)の取得

namespace WPF_Demo.Status
{
    
    
    class MainWindowStatus: ViewModelBase
    {
    
    

        private MainWindowStatus(){
    
     }

        private string textboxContent = "";
        public string TextboxContent
        {
    
    
            get {
    
     return textboxContent; }
            set
            {
    
    
                if(value != textboxContent)
                {
    
    
                    textboxContent = value;
                    RaisePropertyChanged("TextboxContent");
                }
            }
        }


        private static MainWindowStatus instance;

        public static MainWindowStatus Instance
        {
    
    
            get
            {
    
    
                if (instance == null)
                {
    
    
                    instance = new MainWindowStatus();
                }
                return instance;
            }
            private set {
    
     instance = value; }
        }


        public ICommand ACommand
        {
    
    
            get
            {
    
    	//获取Command: 传入委托Action参数,这样就不会把命令逻辑限制死
                return new RelayCommand((parameter) =>
                {
    
    
                    SliderMarker sliderMarker = (parameter as SliderMarker);
                    if(sliderMarker != null)
                    {
    
    
                        //...
                    }  
                });
            }
        }

        public ICommand BCommand
        {
    
    
            get
            {
    
    
                return new RelayCommand((parameter) =>
                {
    
    
                    SliderMarker sliderMarker = (parameter as SliderMarker);
                    if (sliderMarker != null)
                    {
    
    
                        //...
                    }
                });
            }
        }

    }
}

(2) UIインターフェースバインディングの使用

                <Button
                    Grid.Row="1"
                    Grid.Column="0"
                    Background="#333333"
                    BorderBrush="Gray"
                    BorderThickness="1"
                    Content="ED Label"
                    FontSize="20"
                    Foreground="Pink"
                    Visibility="{Binding FrameIdx, Converter={StaticResource EnableCV}}">
                    <Button.InputBindings>
                        <MouseBinding Command="{Binding Source={x:Static vm:EDESPageStatus.Instance}, Path=ACommand}" MouseAction="LeftClick" />
                    </Button.InputBindings>
                </Button>

3.3 バインディングコマンドの複数の動作入力

上記のさまざまなコマンド動作は、Click がクリックされたときにのみコマンドの送信をトリガーする Button のようなものです。明らかに、これだけでは十分ではありません。クリック動作に加えて、インターフェイスにはマウスなどの他の多くの動作があります。では、コマンドバインディングを他の動作に拡張するにどうすればよいでしょうか? このセクションではこれについて説明します。

(1)InputBindingバインディング入力

InputBinding クラスは、Command、CommandTarget、および CommandParameter の標準パラメータを使用して、コマンド ソース オブジェクト タイプでもある ICommandSource インターフェイスを実装します。他のコマンド ソースとは異なり、InputBinding は、 InputGesture (キーボード、マウスなどの特定のデバイス入力ジェスチャ) とコマンドの間のバインディングを表すため、対応する入力ジェスチャが実行されるとコマンドが呼び出されます。UIElement から継承されたすべてのサブクラス オブジェクトには、デバイス入力のコレクションをバインドするために使用される InputBindings プロパティがあるため、ほとんどの UI 要素がいくつかの一般的な入力操作をサポートしていることに注意してください。InputBinding を紹介する前に、InputGesture について見てみましょう。

  • 入力ジェスチャー

InputGesture は、入力デバイス ジェスチャの抽象クラスです。この抽象クラスには、キーボードの組み合わせのキー入力ジェスチャとマウス入力ジェスチャにそれぞれ対応する KeyGesture と MouseGesture の 2 つの実装があります。

KeyGesture: InputGesture
- 介绍: 定义可用来调用命令的组合键。一个 KeyGesture 必须由一个 Key 和一组(一个或多个) ModifierKeys 组成。比如“CTRL+C”、“CTRL+SHIFT+X”
- 属性:
	- DisplayString:获取此 KeyGesture 的字符串表示形式。
	- Key:获取与此 KeyGesture 关联的键。
	- Modifiers:获取与此 KeyGesture 关联的修改键。
MouseGesture: InputGesture
- 介绍: 定义可用于调用命令的鼠标输入笔势。MouseGesture 由 MouseAction 和一组可选(零个或多个)的 ModifierKeys 组成。
- 属性:
	- Modifiers:获取或设置与此 MouseGesture 关联的修改键。
	- MouseAction:获取或设置与此笔势关联的 MouseAction。
enum Key:指定键盘上可能的键值。
	T	63	T 键。
	Tab	3	Tab 键。
	U	64	U 键。
	Up	24	Up Arrow 键。
	V	65	V 键。
	VolumeDown	130	音量减小键。
	VolumeMute	129	静音键。
	VolumeUp	131	音量增大键。
	W	66	W 键。
	Zoom	168	缩放键。
... ...
enum ModifierKeys: 指定键盘上可能的修改键/系统键。
	Alt	1	Alt 键。
	Control	2	CTRL 键。
	None	0	没有按下任何修饰符。
	Shift	4	Shift 键。
	Windows	8	Windows 徽标键。
enum MouseAction: 指定定义鼠标所执行的操作的常量。
	LeftClick	1	单击鼠标左键。
	LeftDoubleClick	5	双击鼠标左键。
	MiddleClick	3	单击鼠标中键。
	MiddleDoubleClick	7	双击鼠标中键。
	None	0	不执行任何操作。
	RightClick	2	单击鼠标右键。
	RightDoubleClick	6	双击鼠标右键。
	WheelClick	4	旋转鼠标滚轮。
  • 入力バインディング

InputBinding は、InputGestureとコマンドの間のバインディングを表し、デバイス入力ジェスチャのコマンド ソースとして機能します。InputGesture に対応して、InputBinding にもKeyBindingMouseBindingという 2 つの派生クラスがあります

KeyBinding : InputBinding 
- 介绍: 将 KeyGesture 绑定到 Command
- 属性:
	- Command: 获取或设置与此输入绑定关联的 ICommand。
	- CommandParameter: 获取或设置特定命令的命令特定数据。
	- CommandTarget: 获取或设置命令的目标元素。
	- Gesture: 获取或设置与此 KeyBinding 关联的笔势。
	- Key: 获取或设置与此 Key 关联的 KeyGesture 的 KeyBinding。
	- Modifiers: 获取或设置与此 ModifierKeys 关联的 KeyGesture 的 KeyBinding。
- 使用:
	- 定义KeyBinding的Gesture属性:参数为一个单独的字符串,包含所有的keyGesture,使用'+'连接(大小写不敏感)
		- 举例:  <KeyBinding Command="ApplicationCommands.Open" Gesture="CTRL+SHIFT+R"/>
	- 定义KeyBinding的Key属性和Modifiers属性:两种方法都是等效的,但如果同时使用这两种方法,将发生冲突。
		- 举例:<KeyBinding Key="R" Modifiers="CTRL+SHIFT" Command="ApplicationCommands.Open" />
MouseBinding : InputBinding 
- 介绍: 将 MouseGesture 绑定到 Command
- 属性:
	- Command: 获取或设置与此输入绑定关联的 ICommand。
	- CommandParameter: 获取或设置特定命令的命令特定数据。
	- CommandTarget: 获取或设置命令的目标元素。
	- Gesture: 获取或设置与此 MouseBinding 关联的笔势。
	- MouseAction: 获取或设置与此 MouseBinding 关联的 MouseAction。
- 使用:
	- 定义MouseBinding的Gesture属性:这允许语法将鼠标操作和修饰符指定为单个字符串,使用'+'连接(大小写不敏感)
		- 举例:  <MouseBinding Command="ApplicationCommands.Open" Gesture="CTRL+LeftClick"/>
	- 定义MouseBinding的MouseAction属性:两种方法都是等效的,但如果同时使用这两种方法,将发生冲突。
		- 举例:<MouseBinding MouseAction="LeftClick" Modifiers="CTRL" Command="ApplicationCommands.Open" />

(2) Interaction.Triggers インタラクション トリガー バインディング入力

上記のInputBindingsはMicrosoft環境に付属する入力メソッドであり、ほとんどの一般的な入力操作のバインディングをある程度解決できます。ただし、一部の非 UIElement 要素や、環境に付属するイベント バインディング コマンドについては何もできません。これは、サードパーティのインタラクション トリガー Interaction.Triggers を使用するために必要なものです。

  • Behaviors 依存関係パッケージを導入する

NuGet を通じて「Microsoft.Xaml.Behaviors.Wpf」パッケージをインストールします。これは、従来の「Microsoft.Expression.Interactions」および「System.Windows.Interactivity」(非推奨) を置き換えます。

ここに画像の説明を挿入
  • XAML でのリソース参照
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
  • 使い方
1.声明交互触发器
<i:Interaction.Triggers>
	2.声明触发事件
	<i:EventTrigger EventName="xxx">
		3.命令绑定事件
			- Command 绑定命令
			- CommandParameter 传递命令参数
			- PassEventArgsToCommand 是否将事件参数EventArgs传递给命令作为参数(默认为false)
		 <i:InvokeCommandAction Command="{Binding xxx}" CommandParameter="{Binding xxx}" PassEventArgsToCommand="True"/>
	</i:EventTrigger>
</i:Interaction.Triggers>
 <!--例子一: ItemsControl模版内的使用-->
 <ItemsControl ItemsSource="{Binding Scans}">
                        <ItemsControl.Template>
                            <ControlTemplate TargetType="{x:Type ItemsControl}">
                                <ScrollViewer HorizontalAlignment="Stretch" VerticalScrollBarVisibility="Hidden">
                                    <StackPanel IsItemsHost="True" Orientation="Vertical" />
                                </ScrollViewer>
                            </ControlTemplate>
                        </ItemsControl.Template>
                        <ItemsControl.ItemTemplate>
                            <DataTemplate>
                                <ContentControl x:Name="CardPresenter">
                                    <RadioButton
                                        Height="35"
                                        HorizontalContentAlignment="Center"
                                        VerticalContentAlignment="Center"
                                        Content="{Binding ScanName}"
                                        FontSize="20"
                                        Foreground="White"
                                        GroupName="scanbtn">
                                        <RadioButton.Style>
                                            <Style BasedOn="{StaticResource ScanButtonStyle}" TargetType="RadioButton">
                                                <Setter Property="Background" Value="{Binding Status, Converter={StaticResource bgCV}}" />
                                            </Style>
                                        </RadioButton.Style>
                                        <i:Interaction.Triggers>
                                            <i:EventTrigger EventName="Click">
                                                <i:InvokeCommandAction Command="{Binding ScanClickCommand, Source={x:Static vm:EDESPageStatus.Instance}}" PassEventArgsToCommand="True" />
                                            </i:EventTrigger>
                                        </i:Interaction.Triggers>
                                    </RadioButton>
                                </ContentControl>
                            </DataTemplate>
                        </ItemsControl.ItemTemplate>
                    </ItemsControl>
 <!--例子二: 普通控件内的使用-->               
<Button
                    Content="ED Label"
                    FontSize="20"
                    Foreground="Pink">
                    <i:Interaction.Triggers>
                        <i:EventTrigger EventName="xxx">
                            <i:InvokeCommandAction Command="{Binding xxx}" CommandParameter="{Binding xxx}"/>
                        </i:EventTrigger>
                    </i:Interaction.Triggers>
                </Button>

おすすめ

転載: blog.csdn.net/qq_40772692/article/details/126428582