rainit2006 / C-Program

C# Program knowledge
0 stars 0 forks source link

WPF-Command Model #17

Open rainit2006 opened 7 years ago

rainit2006 commented 7 years ago

WPF命令模型主要包含以下几个基本元素:

24225754-25d2438fabaa42a9a740ff1a42ac6136


当CanExecute方法返回命令状态是true时,Execute方法得到执行以进行必要的逻辑处理。当命令状态改变时,CanExecuteChanged事件触发,可以根据命令状态相应控制控件(命令源)的状态。
RoutedCommand类和子类RoutedUICommand类继承了ICommand类.

- 命令源(Command Source):
实现了ICommandSource接口的控件才可以发送命令。这样的控件主要有继承自ButtonBase类的Button和CheckBox控件、Hyperlink控件和MenuItem等。
ICommandSource接口定义如下:
         ICommand Command { get; }
        object CommandParameter { get; }
        IInputElement CommandTarget { get; }

- 命令目标(Command Target):
命令目标即命令接受者,或者叫命令的作用者。它实现了IInputElement接口。
在RoutedCommand类中重载的CanExecute方法和Execute方法的第二个参数就是传递命令目标。UIElement类就实现了该接口,也就是说所有的控件都可以作为命令目标。  

- 命令关联(Command Binding):
命令目标发送路由事件(RoutEvent),为了让命令得到恰当的响应,我们需要一个命令关联。
CommandBinding类的定义: 
public ICommand Command { get; set; }
public event CanExecuteRoutedEventHandler CanExecute;
public event ExecutedRoutedEventHandler Executed;
public event CanExecuteRoutedEventHandler PreviewCanExecute;
public event ExecutedRoutedEventHandler PreviewExecuted;

当命令源确定了命令目标后(人为指定或焦点判断),就会不停地向命令目标询问,命令目标就会不停地发送PreviewCanExecute和CanExecute事件,这两个附加事件就会沿着元素树向上传递,然后被命令关联捕获,命令关联将命令能不能发送报告给命令。若可以发送命令,则命令源将发送命令给命令目标,命令目标会发送PreviewExecuted和Executed事件,这两个附加也会沿着元素树向上传递,然后被命令关联捕获,完成一些后续工作。
rainit2006 commented 7 years ago

自定义命令 我们有这么几种方式来自定义命令: 一是直接实现ICommand接口,这是最彻底的方式; 二是继承自RoutedCommand类和RoutedUICommand类,这种方式可以命令路由; 三是使用RoutedCommand类和RoutedUICommand类实例,严格来讲,这种方式只是命令的应用。

cs代码(定义command的代码): public class SayCommand:ICommand

xaml文件里:

<Window x:Class="CommandDemo.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:CommandDemo"
        Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <local:SayCommand x:Key="cmd" />
    </Window.Resources>
    <Grid>
        <StackPanel>
            <Button Content="Say" Command="{StaticResource ResourceKey=cmd}" CommandParameter="Hi,WPF"/>
            <Button Content="AddCommandHandler" Click="Button_Click" />
        </StackPanel>
    </Grid>
</Window>

cs代码(对应xaml的cs代码):

private void Button_Click(object sender, RoutedEventArgs e)
        {
            SayCommand sayCmd = this.FindResource("cmd") as SayCommand;
            if(sayCmd == null)return;
            sayCmd.CanExecuteTargets += () =>
                {
                    return true;
                };
            sayCmd.ExecutedTargets += (p) =>
                {
                    MessageBox.Show(p);
                };
        }    
rainit2006 commented 7 years ago

 WPF中提供了一组已定义命令,命令包括以下类:ApplicationCommands、NavigationCommands、MediaCommands、EditingCommands 以及ComponentCommands。 这些类提供诸如 Cut、BrowseBack、BrowseForward、Play、Stop 和 Pause 等命令。

ApplicationCommands: 提供一组与应用程序相关的标准命令。 https://msdn.microsoft.com/zh-cn/library/system.windows.input.applicationcommands(v=vs.110).aspx

WPF命令是路由的: 实际上,RoutedCommand上Execute和CanExecute方法并没有包含命令的处理逻辑,而是将触发遍历元素树的事件来查找具有CommandBinding的对象。而真正命令的处理程序包含在CommandBinding的事件处理程序中。 http://www.cnblogs.com/zhili/p/WPFCommand.html

下述代码里点击button执行的都是在CommandBinding里指定的Executed方法,即NewCommand函数。 xaml

 <!--定义窗口命令绑定,绑定的命令是New命令,处理程序是NewCommand, -->
    <Window.CommandBindings>
          <CommandBinding Command="ApplicationCommands.New" Executed="NewCommand"/>
     </Window.CommandBindings>

       <!--使用数据绑定,获得正在使用的Command对象,并提取其Text属性-->
         <Button Margin="5" Padding="5" Command="ApplicationCommands.New" Content="{Binding RelativeSource={RelativeSource Self},Path=Command.Text}"/>
         <Button Margin="5" Padding="5" Visibility="Visible" Click="cmdDoCommand_Click" >DoCommand</Button>
     </StackPanel>

cs

private void NewCommand(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show("New 命令被触发了,命令源是:" + e.Source.ToString());
        }

        private void cmdDoCommand_Click(object sender, RoutedEventArgs e)
        {
            // 直接调用命令的两种方式
            ApplicationCommands.New.Execute(null, (Button)sender);

            //this.CommandBindings[0].Command.Execute(null);
        }
rainit2006 commented 7 years ago

自定义命令: xaml里没有明确的command binding声明

<Button  CommandParameter="Teacher"  x:Name="button1" Content="New Teacher" Grid.Row ="1"/>
<Button  CommandParameter="Student"  x:Name="button2" Content="New Student" Grid.Row ="2"/>

在cs里定义command,并binding到目标按钮里

namespace MyCommand
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            //
            InitCmd();
        }

        //声明并定义命令
        private RoutedCommand newCmd=new RoutedCommand ("NewTS",typeof(MainWindow ));

        private void InitCmd()
        {            
            //创建命令关联
            CommandBinding cb = new CommandBinding();
            cb.Command = newCmd;
            cb.CanExecute += new CanExecuteRoutedEventHandler(CanExecute1);
            cb.Executed += new ExecutedRoutedEventHandler(Executed1);
            //把命令关联安置在外围控件上
            mainWindow.CommandBindings.Add(cb);

            //把命令赋值给命令源(发送者)
            button1.Command = newCmd;
            button2.Command = newCmd;
        }

        private void CanExecute1(object sender,CanExecuteRoutedEventArgs e)
        {
            //e.CanExecute = true;
            if (string.IsNullOrWhiteSpace(txtName.Text))
            {
                e.CanExecute = false;
            }
            else
            {
                e.CanExecute = true;
            }
        }

        private void Executed1(object sender, ExecutedRoutedEventArgs e)
        {
            string name = txtName.Text;
            if (e.Parameter.ToString() == "Teacher")
            {
                listBox1.Items.Add("New Teacher: " + name);
            }
            if (e.Parameter.ToString() == "Student")
            {
                listBox1.Items.Add("New Student: " + name);
            }
        }
    }
}
rainit2006 commented 7 years ago

对于Button来说,直接用click事件不好吗?

まぁ、言われてみればボタンのClickイベントに直接処理を記述した場合、 同じ処理を別のボタンから呼び出すときに、 一旦処理をメソッドに括りだして、 両方のボタンからそのメソッドを呼ぶ。 とかやるわけですから、「最初から分けとけばいいじゃん」的な発想にはそこそこ納得できます。

rainit2006 commented 6 years ago

XAMLからViewModelのメソッドにバインドする~RelayCommand~ http://sourcechord.hatenablog.com/entry/2014/01/13/200039

MainWindow.xaml

<Window x:Class="RelayCommandTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"
        Width="300"
        Height="200">
    <Grid>
        <TextBlock Margin="10,10,0,0"
                   HorizontalAlignment="Left"
                   VerticalAlignment="Top"
                   Text="{Binding Message}"
                   TextWrapping="Wrap" />
        <Button Width="75"
                Margin="10,30,0,0"
                HorizontalAlignment="Left"
                VerticalAlignment="Top"
                Command="{Binding ChangeMessageCommand}"
                Content="Button" />
    </Grid>
</Window>

MainWindow.xaml.cs

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new MainWindowViewModel();
        }
    }

MainWindowViewModel.cs

    class MainWindowViewModel : BindableBase
    {
        private string message;

        public string Message
        {
            get { return message; }
            set { this.SetProperty(ref this.message, value); }
        }

        #region コマンドの実装
        private RelayCommand changeMessageCommand;
        public RelayCommand ChangeMessageCommand
        {
            get { return changeMessageCommand = changeMessageCommand ?? new RelayCommand(ChangeMessage); }
        }

        private void ChangeMessage()
        {
            this.Message = "コマンドを実行しました。";
        }
        #endregion

        public MainWindowViewModel()
        {
            this.Message = "初期値です。";
        }
    }

RelayCommand.cs 通常のRelayCommandの後に、RelayCommandクラスを追加しました。

    /// <summary>
    /// その機能を中継することのみを目的とするコマンド
    /// デリゲートを呼び出すことにより、他のオブジェクトに対して呼び出します。
    ///CanExecute メソッドの既定の戻り値は 'true' です。
    /// <see cref="RaiseCanExecuteChanged"/> は、次の場合は必ず呼び出す必要があります。
    /// <see cref="CanExecute"/> は、別の値を返すことが予期されます。
    /// </summary>
    public class RelayCommand : ICommand
    {
        private readonly Action _execute;
        private readonly Func<bool> _canExecute;

        /// <summary>
        /// RaiseCanExecuteChanged が呼び出されたときに生成されます。
        /// </summary>
        public event EventHandler CanExecuteChanged;

        /// <summary>
        /// 常に実行可能な新しいコマンドを作成します。
        /// </summary>
        /// <param name="execute">実行ロジック。</param>
        public RelayCommand(Action execute)
            : this(execute, null)
        {
        }

        /// <summary>
        /// 新しいコマンドを作成します。
        /// </summary>
        /// <param name="execute">実行ロジック。</param>
        /// <param name="canExecute">実行ステータス ロジック。</param>
        public RelayCommand(Action execute, Func<bool> canExecute)
        {
            if (execute == null)
                throw new ArgumentNullException("execute");
            _execute = execute;
            _canExecute = canExecute;
        }

        /// <summary>
        /// 現在の状態でこの <see cref="RelayCommand"/> が実行できるかどうかを判定します。
        /// </summary>
        /// <param name="parameter">
        /// コマンドによって使用されるデータ。コマンドが、データの引き渡しを必要としない場合、このオブジェクトを null に設定できます。
        /// </param>
        /// <returns>このコマンドが実行可能な場合は true、それ以外の場合は false。</returns>
        public bool CanExecute(object parameter)
        {
            return _canExecute == null ? true : _canExecute();
        }

        /// <summary>
        /// 現在のコマンド ターゲットに対して <see cref="RelayCommand"/> を実行します。
        /// </summary>
        /// <param name="parameter">
        /// コマンドによって使用されるデータ。コマンドが、データの引き渡しを必要としない場合、このオブジェクトを null に設定できます。
        /// </param>
        public void Execute(object parameter)
        {
            _execute();
        }

        /// <summary>
        /// <see cref="CanExecuteChanged"/> イベントを発生させるために使用されるメソッド
        /// <see cref="CanExecute"/> の戻り値を表すために
        /// メソッドが変更されました。
        /// </summary>
        public void RaiseCanExecuteChanged()
        {
            var handler = CanExecuteChanged;
            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }
    }

    /// <summary>
    /// 任意の型の引数を1つ受け付けるRelayCommand
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class RelayCommand<T> : ICommand
    {
        private readonly Action<T> _execute;
        private readonly Func<T, bool> _canExecute;

        /// <summary>
        /// RaiseCanExecuteChanged が呼び出されたときに生成されます。
        /// </summary>
        public event EventHandler CanExecuteChanged;

        /// <summary>
        /// 常に実行可能な新しいコマンドを作成します。
        /// </summary>
        /// <param name="execute">実行ロジック。</param>
        public RelayCommand(Action<T> execute)
            : this(execute, null)
        {
        }

        /// <summary>
        /// 新しいコマンドを作成します。
        /// </summary>
        /// <param name="execute">実行ロジック。</param>
        /// <param name="canExecute">実行ステータス ロジック。</param>
        public RelayCommand(Action<T> execute, Func<T, bool> canExecute)
        {
            if (execute == null)
                throw new ArgumentNullException("execute");
            _execute = execute;
            _canExecute = canExecute;
        }

        /// <summary>
        /// 現在の状態でこの <see cref="RelayCommand"/> が実行できるかどうかを判定します。
        /// </summary>
        /// <param name="parameter">
        /// コマンドによって使用されるデータ。コマンドが、データの引き渡しを必要としない場合、このオブジェクトを null に設定できます。
        /// </param>
        /// <returns>このコマンドが実行可能な場合は true、それ以外の場合は false。</returns>
        public bool CanExecute(object parameter)
        {
            return _canExecute == null ? true : _canExecute((T)parameter);
        }

        /// <summary>
        /// 現在のコマンド ターゲットに対して <see cref="RelayCommand"/> を実行します。
        /// </summary>
        /// <param name="parameter">
        /// コマンドによって使用されるデータ。コマンドが、データの引き渡しを必要としない場合、このオブジェクトを null に設定できます。
        /// </param>
        public void Execute(object parameter)
        {
            _execute((T)parameter);
        }

        /// <summary>
        /// <see cref="CanExecuteChanged"/> イベントを発生させるために使用されるメソッド
        /// <see cref="CanExecute"/> の戻り値を表すために
        /// メソッドが変更されました。
        /// </summary>
        public void RaiseCanExecuteChanged()
        {
            var handler = CanExecuteChanged;
            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }
    }