WPF MVVM Light(三):RelayCommand 命令与 Messenger 消息

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 这个"广播中心"通信,解耦干净。

四、小结

  1. RelayCommand / RelayCommand<T> 把交互逻辑封装成命令,View 只需绑定 Command。
  2. CanExecute 控制按钮启用状态,属性变化时要手动 RaiseCanExecuteChanged。
  3. Messenger 用 Register / Send / Unregister 实现 ViewModel 间的解耦通信,用完记得 Unregister。

下一篇是收官篇:讲 SimpleIoc 依赖注入 + ViewModelLocator 视图定位,并给出一个完整的 TodoList 待办事项实例,把本系列所有知识点串起来。


系列导航:第一篇·入门 / 第二篇·ViewModelBase 与属性通知 / 第三篇·RelayCommand 与 Messenger / 第四篇·SimpleIoc 与 ViewModelLocator 及完整实例

相关推荐
FungLeo14 小时前
成为全栈·React 管理后台篇·总复盘:从“接口能调通”到“后台值得使用”
前端·react.js·前端框架·项目复盘·成为全栈
Csvn15 小时前
并发模式:让渲染学会排队、插队和让路
前端
可乐鸡翅yeah_16 小时前
新手梳理:M3U8 线上问题,哪些是前端锅,哪些是后端锅
前端·ios·音视频·实时音视频·m3u8·音视频在线播放
Flynt16 小时前
Linear 用 1000 个 PR 换掉 styled-components,我写了 200 个按钮,把这笔账复现了一遍
前端·css·preact
波力海苔夹心脆67516 小时前
C# 序列化与反序列化详解:System.Text.Json、Newtonsoft.Json、XmlSerializer 用法、特性选项与安全实践
经验分享·后端·c#·json·.net
JavaGuide17 小时前
NVIDIA 又开源了!这次给 AI Agent 加上权限管控
前端·后端
excel17 小时前
prisma 如何处理数据库竞态
前端·数据库·后端
莪_幻尘18 小时前
Skill 体检:30 个 Skill 全凭感觉?体检器先自曝了 8 个“假 0 分
前端·人工智能·llm
风骏时光牛马18 小时前
AI模型综合能力评测:性能、指令遵循与多场景实测对比
前端
Frag0ut18 小时前
Chrome与Chromium内核浏览器在Windows 11上的新特性全景解析
前端·chrome·windows·web安全·chromium·gemini ai·playready drm