WPF MVVM Light(三):RelayCommand 命令与 Messenger 消息
系列导航:第一篇·入门 / 第二篇·ViewModelBase 与属性通知 / 第三篇·RelayCommand 与 Messenger / 第四篇·SimpleIoc 与 ViewModelLocator 及完整实例
一、命令:把按钮点击交给 ViewModel
前面我们解决了"数据怎么展示",但按钮点击、菜单操作这类"交互"怎么办?传统做法是在 code-behind 里写 Click 事件,MVVM 的思路是用命令(ICommand)。
ICommand 接口定义了三个成员:
csharp
public interface ICommand
{
event EventHandler CanExecuteChanged;
bool CanExecute(object parameter); // 能否执行
void Execute(object parameter); // 执行什么
}
WPF 的 Button.Command 属性支持绑定命令,这样 View 就不用碰 code-behind 了。但手写 ICommand 实现很繁琐,RelayCommand 把它封装好了。
二、RelayCommand 详解
2.1 无参命令
csharp
using GalaSoft.MvvmLight.Command;
public class MainViewModel : ViewModelBase
{
public RelayCommand SaveCommand { get; }
public MainViewModel()
{
SaveCommand = new RelayCommand(Save, CanSave);
}
private void Save()
{
// 保存逻辑
}
private bool CanSave()
{
// 返回 false 时按钮自动禁用
return !string.IsNullOrEmpty(Name);
}
}
XAML 中绑定:
xml
<Button Content="保存" Command="{Binding SaveCommand}" />
CanSave 返回 false 时按钮自动变灰禁用,这就是 CanExecute 的作用。
2.2 带参数的命令 RelayCommand
需要传参时用泛型版本:
csharp
public RelayCommand<string> DeleteCommand { get; }
public MainViewModel()
{
DeleteCommand = new RelayCommand<string>(Delete);
}
private void Delete(string id)
{
// 用 id 删除
}
XAML 中用 CommandParameter 传参:
xml
<Button Content="删除"
Command="{Binding DeleteCommand}"
CommandParameter="{Binding Id}" />
2.3 手动刷新 CanExecute
当影响 CanExecute 的属性变化时,需要手动通知命令重新判定:
csharp
private string _name;
public string Name
{
get => _name;
set
{
if (Set(ref _name, value))
SaveCommand?.RaiseCanExecuteChanged();
}
}
注意
RelayCommand不像 WPF 原生的RoutedCommand那样会自动响应CommandManager.RequerySuggested,所以影响启用状态的属性变化时,记得手动RaiseCanExecuteChanged,否则按钮的禁用状态不会实时更新。
三、Messenger:ViewModel 之间的通信
同一个窗口里,命令 + 属性够用了。但两个互不知晓的 ViewModel(比如"子窗口保存后通知主窗口刷新")怎么通信?直接互相引用会导致强耦合,Messenger(消息) 就是解耦方案。
3.1 发送消息
csharp
using GalaSoft.MvvmLight.Messaging;
Messenger.Default.Send(new NotificationMessage("保存成功"));
3.2 接收消息
csharp
Messenger.Default.Register<NotificationMessage>(this, msg =>
{
// msg.Notification 就是 "保存成功"
StatusText = msg.Notification;
});
3.3 注销消息
csharp
// ViewModel 销毁时必须注销,否则会内存泄漏 / 重复接收
Messenger.Default.Unregister(this);
3.4 常用消息类型
| 类型 | 用途 |
|---|---|
MessageBase |
基类,自定义消息可继承它 |
NotificationMessage |
携带一段文本通知 |
NotificationMessage<T> |
携带文本 + 一个对象 |
GenericMessage<T> |
只携带一个对象 |
PropertyChangedMessage<T> |
携带新旧值,属性变化通知 |
3.5 一个完整例子
主 ViewModel 保存后,通知另一个 ViewModel 刷新列表:
csharp
// 发送方(子窗口 ViewModel)
private void Save()
{
// 保存到数据库...
Messenger.Default.Send(new NotificationMessage("数据已更新,请刷新"));
}
// 接收方(主窗口 ViewModel)
public MainViewModel()
{
Messenger.Default.Register<NotificationMessage>(this, OnNotification);
}
private void OnNotification(NotificationMessage msg)
{
if (msg.Notification == "数据已更新,请刷新")
LoadData();
}
private void LoadData()
{
// 重新加载列表
}
两个 ViewModel 完全不互相引用,只通过 Messenger.Default 这个"广播中心"通信,解耦干净。
四、小结
RelayCommand/RelayCommand<T>把交互逻辑封装成命令,View 只需绑定Command。CanExecute控制按钮启用状态,属性变化时要手动RaiseCanExecuteChanged。Messenger用Register/Send/Unregister实现 ViewModel 间的解耦通信,用完记得Unregister。
下一篇是收官篇:讲 SimpleIoc 依赖注入 + ViewModelLocator 视图定位,并给出一个完整的 TodoList 待办事项实例,把本系列所有知识点串起来。
系列导航:第一篇·入门 / 第二篇·ViewModelBase 与属性通知 / 第三篇·RelayCommand 与 Messenger / 第四篇·SimpleIoc 与 ViewModelLocator 及完整实例