WPF使用MVVM(二)-命令繫結

醜萌氣質狗發表於2022-01-17

WPF使用MVVM(二)-命令繫結

上一節已經介紹了WPF的屬性繫結,這使得我們只需要指定介面的DataContext,然後就可以讓介面繫結我們的屬性資料呢。

但是上一節還遺留了一個問題就是我們的按鈕的Click方法,依然是寫在介面的後臺中的,現在我們希望將按鈕的Click方法也採用繫結的形式。

原先是這樣的:

    <Button
        Grid.Row="3"
        Grid.ColumnSpan="2"
        Margin="20"
        Click="Button_Click"
        Content="更新一下資訊"
        FontSize="30"
        FontWeight="Bold" />

希望變成這樣:

        <Button
            Grid.Row="3"
            Grid.ColumnSpan="2"
            Margin="20"
            Click="{Binding ClickAction}"
            Content="更新一下資訊"
            FontSize="30"
            FontWeight="Bold" />

讓我們的MainWindowVM(ViewModel)也提供一個方法,讓我們繫結一下,這樣介面的資料和按鈕的點選處理邏輯,都放到了橋樑ViewModel中了,介面看起來也很清爽。

Command

WPF呢,為了讓我們用MVVM的形式替換按鈕的點選行為,給我們提供了一個Command的屬性,讓我們也可以像繫結屬性的方式,來繫結我們的點選方法,具體的寫法如下:

        <Button
            Grid.Row="3"
            Grid.ColumnSpan="2"
            Margin="20"
            Command="{Binding ClickAction}"
            Content="更新一下資訊"
            FontSize="30"
            FontWeight="Bold" />

之前寫的Button_Click方法也可以直接刪除了。

注意:Command屬性僅僅作為Click行為的繫結,其他行為,如滑鼠移入、移出。。。等行為,要使用另外的MVVM方式進行繫結。(本文只介紹點選行為,後續介紹其他行為的MVVM實現)

新增ClickAction的實現

上面我們也刪除了Button_Click方法,並且還給Button按鈕的Command屬性繫結了一個方法叫做ClickAction,接下來我們就要在MainWindowVM(ViewModel)中去新增這個方法。

要實現繫結的方法ClickAction,就需要用到ICommand介面,需要我們自己建立型別去實現介面的CanExecuteExecuteCanExecuteChanged,下面直接貼一下實現介面的程式碼,需要新建一個類,名字我們取RelayCommand:

    public class RelayCommand : ICommand
    {
        /// <summary>
        /// 命令能否執行
        /// </summary>
        readonly Func<bool> _canExecute;
        /// <summary>
        /// 命令執行的方法
        /// </summary>
        readonly Action _execute;

        /// <summary>
        /// 命令的建構函式
        /// </summary>
        /// <param name="action">命令需執行的方法</param>
        /// <param name="canExecute">命令是否可以執行的方法</param>
        public RelayCommand(Action action, Func<bool> canExecute)
        {
            _execute = action;
            _canExecute = canExecute;
        }

        /// <summary>
        /// 判斷命令是否可以執行
        /// </summary>
        /// <param name="parameter"></param>
        /// <returns></returns>
        public bool CanExecute(Object parameter)
        {
            if (_canExecute == null)
                return true;
            return _canExecute();
        }

        /// <summary>
        /// 執行命令
        /// </summary>
        /// <param name="parameter"></param>
        public void Execute(Object parameter)
        {
            _execute();
        }

        /// <summary>
        /// 事件追加、移除
        /// </summary>
        public event EventHandler CanExecuteChanged
        {
            add
            {
                if (_canExecute != null)
                    CommandManager.RequerySuggested += value;
            }
            remove
            {
                if (_canExecute != null)
                    CommandManager.RequerySuggested -= value;
            }
        }

    }

建立這個類,就是為了在使用命令的時候, 建立一條命令出來用於繫結,這個型別接收兩個引數,一個是命令執行的方法,另一個是有返回值的方法, 這個返回值bool用來確定,該條命令是否可以執行,如果命令不能被執行,則按鈕的IsEnabled就被會設定成不可點選,下面我們來挨個看下效果

MainWindowVM中建立一個命令

剛才我們已經做好了建立命令的準備工作,下面直接建立一個命令,並給這個命令指定一個方法即可。

MainWindowVM新增如下程式碼:

        /// <summary>
        /// 命令要執行的方法
        /// </summary>
        void UpdateNameExecute()
        {
            EmployeeM.Name = "王明(原屬性修改)";
            EmployeeM = EmployeeM;
        }

        /// <summary>
        /// 命令是否可以執行
        /// </summary>
        /// <returns></returns>
        bool CanUpdateNameExecute()
        {
            return true;
        }

        /// <summary>
        /// 建立新命令
        /// </summary>
        public ICommand ClickAction
        {
            get
            {
                return new RelayCommand(UpdateNameExecute, CanUpdateNameExecute);
            }
        }

注意,建立這個新的命令的名字需要和我們介面按鈕Command中繫結的名字一致,叫ClickAction

這時候我們執行一下程式,點選按鈕,可以看到命令是可以生效的。

此時我們做一個小小的改動,我們將是否可以執行的方法返回為False

        /// <summary>
        /// 命令是否可以執行
        /// </summary>
        /// <returns></returns>
        bool CanUpdateNameExecute()
        {
            return false;
        }

再次執行能夠看到,介面中按鈕已經是不可點選的狀態了!

image-20220117151414246

所以我們繫結的這個命令是否可以執行,是直接影響到按鈕能否被點選的!這個值會直接作用在按鈕的IsEnabled上。

命令帶點私貨-引數

上面的命令就是純命令,啥引數都沒帶上,有時候希望執行命令的時候,希望能夠傳個引數,那就需要改造一下了!

採用泛型的形式,給Action加點料,重新貼一下RelayCommand的程式碼:

    public class RelayCommand<T> : ICommand
    {
        /// <summary>
        /// 命令能否執行
        /// </summary>
        readonly Func<bool> _canExecute;
        /// <summary>
        /// 命令執行的方法
        /// </summary>
        readonly Action<T> _execute;

        /// <summary>
        /// 命令的建構函式
        /// </summary>
        /// <param name="action">命令需執行的方法</param>
        /// <param name="canExecute">命令是否可以執行的方法</param>
        public RelayCommand(Action<T> action, Func<bool> canExecute)
        {
            _execute = action;
            _canExecute = canExecute;
        }

        /// <summary>
        /// 判斷命令是否可以執行
        /// </summary>
        /// <param name="parameter"></param>
        /// <returns></returns>
        public bool CanExecute(Object parameter)
        {
            if (_canExecute == null)
                return true;
            return _canExecute();
        }

        /// <summary>
        /// 執行命令
        /// </summary>
        /// <param name="parameter"></param>
        public void Execute(Object parameter)
        {
            _execute((T)parameter);
        }

        /// <summary>
        /// 事件追加、移除
        /// </summary>
        public event EventHandler CanExecuteChanged
        {
            add
            {
                if (_canExecute != null)
                    CommandManager.RequerySuggested += value;
            }
            remove
            {
                if (_canExecute != null)
                    CommandManager.RequerySuggested -= value;
            }
        }

    }

MainWindowVM(ViewModel)中建立的命令和給到的方法也要有點小變化:

        /// <summary>
        /// 命令要執行的方法
        /// </summary>
        void UpdateNameExecute(object sender)
        {
            EmployeeM.Name = "王明(原屬性修改)";
            EmployeeM = EmployeeM;
        }

        /// <summary>
        /// 命令是否可以執行
        /// </summary>
        /// <returns></returns>
        bool CanUpdateNameExecute()
        {
            return true;
        }
        /// <summary>
        /// 建立新命令
        /// </summary>
        public ICommand ClickAction
        {
            get
            {
                return new RelayCommand<object>(UpdateNameExecute, CanUpdateNameExecute);
            }
        }

引數從哪裡傳呢, 當然是我們的介面傳了,通過按鈕的CommandParameter屬性來傳,這裡我們將按鈕自己傳過去!

        <Button
            Grid.Row="3"
            Grid.ColumnSpan="2"
            Margin="20"
            Command="{Binding ClickAction}"
            CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}}"
            Content="更新一下資訊"
            FontSize="30"
            FontWeight="Bold" />

執行斷點看一下,能夠看到按鈕自身已經當作引數傳入了:

image-20220117153354363

下一節說一下事件的繫結,讓其他事件,如MouseEnterMouseLeave也能夠像按鈕的Command一樣。

相關文章