当前位置 博文首页 > Dwaynerbing:WPF 之命令(七)

    Dwaynerbing:WPF 之命令(七)

    作者:Dwaynerbing 时间:2021-02-08 20:26

    一、前言

    ? 事件的作用是发布和传播一些消息,消息送达接收者,事件的使命也就完成了,至于消息响应者如何处理发送来的消息并不做规定,每个接收者可以使用自己的行为来响应事件。即事件不具有约束力。

    ? 命令就有约束力,不仅可以约束代码,还可以约束步骤逻辑。

    二、WPF 的 命令系统

    ? WPF 中,命令系统由以下元素构成:

    1. 命令(Command):实现 ICommand 接口。表示一个程序任务,并可跟踪该任务是否完成。

    2. 命令源(Commannd Source):命令的发送者。需实现 ICommandSource 接口。

    3. 命令目标(Command Target):命令的接收者。需实现 IInputElment 接口的类。

    4. 命令关联(Command Binding):负责把外围逻辑和命令关联起来。

      image-20210208165152323

    ?  WPF中提供了一组已定义命令,命令包括以下类:ApplicationCommands、NavigationCommands、MediaCommands、EditingCommands 以及ComponentCommands。 这些类提供诸如 Cut、BrowseBack、BrowseForward、Play、Stop 和 Pause 等命令。下面我们使用 ApplicationCommands 进行以下测试:

            // 命令执行具体操作
            private void CommandBinding_OnExecuted(object sender, ExecutedRoutedEventArgs e)
            {
                MessageBox.Show($"New 命令被触发了,命令源是:{e.Source}");
            }
    
      <Window.CommandBindings>
            <CommandBinding Command="ApplicationCommands.New" Executed="CommandBinding_OnExecuted"></CommandBinding>
        </Window.CommandBindings>
        <StackPanel Margin="20">
            <Button Margin="10" Height="50" Content="New" Command="New"></Button>
        </StackPanel>
    

    ? 点击按钮后,输出结果为:

    New 命令被触发了,命令源是:System.Windows.Controls.Button: New
    

    三、自定义命令

    ? 例如,我们需要增加一个数据的查库与上传数据操作,那么需要实现 Query 和 Insert 命令,为了能够直接供 UI 使用,我们声明 RoutedUICommand 命令,具体如下:

        public class DatabaseCommands
        {
            private static RoutedUICommand _query;
            private static RoutedUICommand _insert;
            static DatabaseCommands()
            {
                InputGestureCollection inputs = new InputGestureCollection();
    
                inputs.Add(new KeyGesture(Key.Q ,ModifierKeys.Control, "Ctrl+R"));
                _query = new RoutedUICommand(
                    "Query", "Query", typeof(DatabaseCommands), inputs);
    
                inputs.Add(new KeyGesture(Key.D, ModifierKeys.Control, "Ctrl+D"));
                _insert = new RoutedUICommand(
                    "Add", "Add", typeof(DatabaseCommands), inputs);
            }
    
            public static RoutedUICommand Query
            {
                get { return _query; }
            }
    
            public static RoutedUICommand Insert
            {
                get { return _insert; }
            }
        }
    

    ? 命令实现与绑定到 UI 上的操作如下:

            // 命令执行具体操作
            private void DatabaseCommandsQuery_OnExecuted(object sender, ExecutedRoutedEventArgs e)
            {
                MessageBox.Show($"Query 命令被触发了,命令源是:{e.Source}");
            }
    
            private void DatabaseCommandsAdd_OnExecuted(object sender, ExecutedRoutedEventArgs e)
            {
                MessageBox.Show($"Add 命令被触发了,命令源是:{e.Source}");
            }
    
     <Window.CommandBindings>
            <CommandBinding Command="local:DatabaseCommands.Query" Executed="DatabaseCommandsQuery_OnExecuted"></CommandBinding>
            <CommandBinding Command="local:DatabaseCommands.Insert" Executed="DatabaseCommandsAdd_OnExecuted">
            </CommandBinding>
        </Window.CommandBindings>
        <StackPanel Margin="20">
            <Button Margin="10" Height="50" Content="New" Command="New"></Button>
            <Button Margin="10" Height="50" Content="Query" Command="local:DatabaseCommands.Query"></Button>
            <Button Margin="10" Height="50" Content="Insert" Command="local:DatabaseCommands.Insert"></Button>
        </StackPanel>
    

    ? 当我们点击 Query 和 Insert 操作时,分别执行对应的操作。上述例子中,Command 的执行代码是在 XAML 中声明的,那我们把命令的执行代码进行代码后置,且为了满足不同的条件,我们声明一个 RelayCommand 基类进行封装,具体如下:

        /// <summary>
        /// The base implementation of a command.
        /// </summary>
        abstract class CommandBase : ICommand
        {
            /// <summary>
            /// Occurs when changes occur that affect whether or not the command should execute.
            /// </summary>
            public event EventHandler CanExecuteChanged
            {
                add { System.Windows.Input.CommandManager.RequerySuggested += value; }
                remove { System.Windows.Input.CommandManager.RequerySuggested -= value; }
            }
    
            /// <summary>
            /// Raises the <see cref="CanExecuteChanged" /> event.
            /// </summary>
            public void OnCanExecuteChanged()
            {
                System.Windows.Input.CommandManager.InvalidateRequerySuggested();
            }
    
            /// <summary>
            /// Defines the method that determines whether the command can execute in its current state.
            /// </summary>
            /// <param name="parameter">Data used by the command.  If the command does not require data to be passed, this object can be set to null.</param>
            /// <returns>
            /// true if this command can be executed; otherwise, false.
            /// </returns>
            public virtual bool CanExecute(object parameter)
            {
                return true;
            }
    
            /// <summary>
            /// Defines the method to be called when the command is invoked.
            /// </summary>
            /// <param name="parameter">Data used by the command.  If the command does not require data to be passed, this object can be set to null.</param>
            public void Execute(object parameter)
            {
                if (!CanExecute(parameter))
                {
                    return;
                }
                OnExecute(parameter);
            }
    
            /// <summary>
            /// Executes the command.
            /// </summary>
            /// <param name="parameter">The parameter.</param>
            protected abstract void OnExecute(object parameter);
        }
    
        /// <summary>
        /// The command that relays its functionality by invoking delegates.
        /// </summary>
        class RelayCommand : CommandBase
        {
            private readonly Action<object> _execute;
            private readonly Func<object, bool> _canExecute;
    
            /// <summary>
            /// Initializes a new instance of the <see cref="RelayCommand"/> class.
            /// </summary>
            /// <param name="execute">The execute.</param>
            /// <param name="canExecute">The can execute.</param>
            public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
            {
                if (canExecute == null)
                {
                    // no can execute provided, then always executable
                    canExecute = (o) => true;
                }
                this._execute = execute;
                this._canExecute = canExecute;
            }
    
            /// <summary>
            /// Defines the method that determines whether the command can execute in its current state.
            /// </summary>
            /// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
            /// <returns>
            /// true if this command can be executed; otherwise, false.
            /// </returns>
            public override bool CanExecute(object parameter)
            {
                return _canExecute == null || _canExecute.Invoke(parameter);
            }
    
            /// <summary>
            /// Executes the command.
            /// </summary>
            /// <param name="parameter">The parameter.</param>
            protected override void OnExecute(object parameter)
            {
                _execute?.Invoke(parameter);
            }
        }
    

    ? 然后,我们实现一个示例,点击 Clear 按钮时,清空 TextBox 中的所有内容,那么需要声明一个 Model 和 ViewModel ,具体实现可以阅读 WPF 之 INotifyPropertyChanged 接口的使用 (一):

        class Student
        {
            public  string Id { get; set; }
            public string Name { get; set; }
            public string Email { get; set; }
            public string Phone { get; set; }
        }
        class MainWindowVM : NotifyProperty
        {
            private Student _student;
    
            public Student Student
            {
                get => _student;
                set => SetProperty(ref _student, value);
            }
            public ICommand Clear { get; set; }
    
            private void ClearCommand(object args)
            {
                Student = new Student();
            }
    
            public MainWindowVM()
            {
                _student = new Student()
                {
                    Id = "01",
                    Name = "Dwayne",
                    Email = "974608610@qq.com",
                    Phone = "180888888888",
                };
                Clear=new RelayCommand(ClearCommand,null);
            }
        }
    

    ? 最后,我们把该 ViewModel 绑定到 XAML 上,具体如下:

      <StackPanel Margin="20">
            <Button Margin="10" Height="50" Content="Clear" Command="{Binding Path=Clear}"></Button>
            <TextBox Text="{Binding Student.Id}" Height="50" VerticalContentAlignment="Center" Margin="10" BorderBrush="Black"></TextBox>
            <TextBox Text="{Binding Student.Name}" Height="50" VerticalContentAlignment="Center" Margin="10" BorderBrush="Black"></TextBox>
            <TextBox Text="{Binding Student.Email}" Height="50" VerticalContentAlignment="Center" Margin="10" BorderBrush="Black"></TextBox>
            <TextBox Text="{Binding Student.Phone}" Height="50" VerticalContentAlignment="Center" Margin="10" BorderBrush="Black"></TextBox>
        </StackPanel>
    

    ? 当我们点击 Clear 按钮时,所有的 TextBox 就被清空了显示。其实,上述这个例子就是一个简单的 MVVM 示例,它让逻辑代码和 UI 完全分离。对于 Command 来说,当我们要执行 TextChanged 事件时,需要添加 “System.Windows.Interactivity” ,然后在 XAML 中添加如下引用:

     xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    
      <TextBox Text="改变内容即可删除" Height="50" VerticalContentAlignment="Center" Margin="10" BorderBrush="Black" >
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="TextChanged">
                        <i:InvokeCommandAction Command="{Binding Clear}" CommandParameter="{Binding Path=Student}"/>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </TextBox>
    

    当我们改变该文本框的内容时,也可执行 Clear 命令。

    bk