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介面,需要我們自己建立型別去實現介面的CanExecute、Execute、CanExecuteChanged,下面直接貼一下實現介面的程式碼,需要新建一個類,名字我們取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;
}
再次執行能夠看到,介面中按鈕已經是不可點選的狀態了!
所以我們繫結的這個命令是否可以執行,是直接影響到按鈕能否被點選的!這個值會直接作用在按鈕的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" />
執行斷點看一下,能夠看到按鈕自身已經當作引數傳入了:
下一節說一下事件的繫結,讓其他事件,如MouseEnter、MouseLeave也能夠像按鈕的Command一樣。