c#软件开发学习笔记--WPF(MVVM框架)

WPF MVVM 框架学习总结(MvvmLight / CommunityToolkit.Mvvm / Prism)


一、三大框架总览与对比

WPF 中 MVVM 框架的发展脉络:

复制代码
INotifyPropertyChanged / ICommand
        └──> MvvmLight(GalaSoft,个人维护,已基本停更)
                └──> Microsoft Toolkit.Mvvm(曾用名)
                        └──> CommunityToolkit.Mvvm(现名,微软官方维护)
                                └──> Prism(企业级,Patterns & Practices / .NET 基金会)
维度 MvvmLight CommunityToolkit.Mvvm Prism
定位 轻量级、入门简单 微软官方轻量级 MVVM 工具集 企业级框架,含模块化/导航
维护方 Laurent Bugnion(个人) Microsoft(官方) .NET 基金会
适用场景 小型/遗留项目 全场景(轻量→中型),新项目首选 大型/复杂/模块化项目
核心组件 ViewModelBaseRelayCommandMessenger ObservableObject[ObservableProperty][RelayCommand]WeakReferenceMessenger BindableBaseDelegateCommandIEventAggregator、区域/导航/模块
依赖 无强依赖 无依赖(.NET Standard) 依赖 Prism.Core / Prism.Wpf 等
运行时性能 一般(运行时反射) 最优(编译时源生成器) 中等
DI 容器 内置 SimpleIoc 适配第三方(MS DI / Autofac) 内置 DryIoc / Unity

核心联系:三者都遵循 MVVM 模式,核心目标都是解耦 View 与 ViewModel,都提供「可绑定的基类 + 命令封装 + 组件间通信」三件套。


二、MVVM 公共核心概念

无论用哪个框架,MVVM 的本质都是:

  • View:XAML 界面,负责显示,不写业务逻辑。
  • ViewModel :为 View 提供可绑定的属性(带通知)命令(执行动作)
  • Model:业务数据模型(含数据库访问、服务)。

三个基本功:

  1. 属性变更通知 :派发 INotifyPropertyChanged,UI 才能感知数据变化。
  2. 命令 :封装 ICommand,让可执行逻辑与 UI 解耦。
  3. 组件通信:跨页面/ViewModel 传递数据(消息/事件)。

三、MvvmLight 知识点

3.1 核心组件

组件 作用
ViewModelBase VM 基类,实现 INotifyPropertyChanged,提供 Set(ref field, value)
RelayCommand / RelayCommand<T> 命令类,替代手动实现 ICommand
Messenger 消息传递器,跨 VM/视图解耦通信
ViewModelLocator 统一管理 VM 实例(配合 SimpleIoc

3.2 属性通知:ViewModelBase 的 Set

csharp 复制代码
public class MainViewModel : ViewModelBase
{
    private string _welcomeMessage;
    public string WelcomeMessage
    {
        get => _welcomeMessage;
        set => Set(ref _welcomeMessage, value); // Set 自动触发 PropertyChanged
    }
}

3.3 命令:RelayCommand

csharp 复制代码
public ICommand ClickCommand { get; }

public MainViewModel()
{
    // 参数1:执行逻辑;参数2(可选):是否可执行
    ClickCommand = new RelayCommand(OnClick, CanClick);
}

private void OnClick() => _clickCount++;
private bool CanClick() => _clickCount < 5;

3.4 消息传递:Messenger.Default

csharp 复制代码
// 定义消息类型
public class UpdateMessage { public string Content { get; set; } }

// 发送
Messenger.Default.Send(new UpdateMessage { Content = "子窗口更新了数据" });

// 接收(构造函数中注册,销毁时注销,避免内存泄漏)
Messenger.Default.Register<UpdateMessage>(this, msg => WelcomeMessage = msg.Content);
public override void Cleanup()
{
    Messenger.Default.Unregister<UpdateMessage>(this);
    base.Cleanup();
}

3.5 ViewModelLocator + SimpleIoc(DI)

MvvmLight 用 ViewModelLocator 把 VM 注册进 SimpleIoc,视图通过 DataContext="{Binding Source={StaticResource Locator}, Path=Main}" 获取。

csharp 复制代码
public class ViewModelLocator
{
    public ViewModelLocator()
    {
        ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
        SimpleIoc.Default.Register<MainViewModel>();          // VM(单例)
        SimpleIoc.Default.Register<LoginViewModel>();
        SimpleIoc.Default.Register<IService<User, ViewUser>, UserService>(); // 服务
    }
    public MainViewModel Main => ServiceLocator.Current.GetInstance<MainViewModel>();
    public LoginViewModel Login => ServiceLocator.Current.GetInstance<LoginViewModel>();
}
xml 复制代码
<!-- App.xaml 注册定位器 -->
<Application.Resources>
    <local:ViewModelLocator x:Key="Locator" />
</Application.Resources>

<!-- 视图绑定 -->
<Window DataContext="{Binding Main, Source={StaticResource Locator}}">

3.6 设计时数据

csharp 复制代码
if (IsInDesignMode)
    WelcomeMessage = "设计时预览文本";   // XAML 设计器预览
else
    WelcomeMessage = "运行时文本";

3.7 常见坑

  • 属性一定要用 Set,直接赋值不会触发通知。
  • RelayCommand 可执行状态变化时手动 RaiseCanExecuteChanged()
  • Messenger 必须 Unregister,或用弱引用注册。
  • SimpleIoc 需 VM 有无参构造函数。

四、CommunityToolkit.Mvvm 知识点

4.1 最大亮点:源生成器(Source Generator)

通过 C# 特性 ,编译期自动生成属性/命令底层代码,无反射开销、零样板代码。核心要求:

  • 类必须是 partial,且继承 ObservableObject
  • 命令方法需为 private(或非 public),方法名不需要写Command后缀。
csharp 复制代码
public partial class MainViewModel : ObservableObject
{
    // 自动生成 Name 属性 + PropertyChanged 通知
    [ObservableProperty]
    private string? name = "张三";

    // 自动生成 FirstCommand(无参)
    [RelayCommand]
    private void First() => MessageBox.Show(Name + "sss");

    // 自动生成 SecondCommand,支持参数
    [RelayCommand]
    private void Second(object o) => MessageBox.Show(o.ToString() + "sss");
}

4.2 属性写法对比

csharp 复制代码
// 写法1:手写 + OnPropertyChanged
private string name;
public string Name
{
    get => name;
    set { name = value; OnPropertyChanged(); }
}

// 写法2:SetProperty(推荐,等价 Hand)
public string Name { get => name; set => SetProperty(ref name, value); }

// 写法3(错误):name = value; SetProperty(ref name, value); 旧值已覆盖,不会通知

// 写法4(最佳):源生成器
[ObservableProperty] private string? name;

4.3 命令:RelayCommand / AsyncRelayCommand

csharp 复制代码
public RelayCommand FirstCommand => new RelayCommand(() =>
    MessageBox.Show("无参数命令"));

public RelayCommand<object> SecondCommand => new RelayCommand<object>(o =>
    MessageBox.Show("带参数命令 " + o.ToString()));

public AsyncRelayCommand<object> ThridCommand => new AsyncRelayCommand<object>(async o =>
{
    await Task.Delay(1000);
    MessageBox.Show("异步命令 " + o.ToString());
});

源生成器方式(自动生成命令、自动监听属性更新可执行状态):

csharp 复制代码
[RelayCommand(CanExecute = nameof(CanClick))]
private void Click() { ClickCount++; }
private bool CanClick() => ClickCount < 5;

// 异步 + 取消
[RelayCommand]
private async Task LoadAsync(CancellationToken token)
{
    token.ThrowIfCancellationRequested();
    await Task.Delay(100, token);
}

4.4 弱引用消息传递:WeakReferenceMessenger

建议提前定义消息类型 (绝不直接 Send(实体),否则会造成消息串扰、语义不清):

csharp 复制代码
// 消息类:继承内置 ValueChangedMessage<T>,自动拥有 Value 属性
public class LoginInfoMessage : ValueChangedMessage<User>
{
    public LoginInfoMessage(User value) : base(value) { }
}

// 发送:弱引用,接收方不再需要手动 Unregister,GC 自动回收
WeakReferenceMessenger.Default.Send(new LoginInfoMessage(u1));
csharp 复制代码
// 接收:推荐继承 ObservableRecipient(内置 Messenger,可自动取消订阅)
public partial class MainViewModel : ObservableRecipient
{
    public MainViewModel()
    {
        IsActive = true;  // 开启消息接收
        Messenger.Register<LoginInfoMessage>(this, (r, m) => {
            var user = m.Value; // 取到传递的 User
        });
    }
}

弱引用优势:接收方关闭窗口且无强引用时 GC 可回收,避免内存泄漏(强引用忘 Unregister 会导致 VM 永远无法回收)。

4.5 属性变更回调

源生成器会生成 partial void On[Name]Changed(...),可重写自定义逻辑:

csharp 复制代码
// private string? _welcomeMessage; 对应生成 WelcomeMessage
partial void OnWelcomeMessageChanged(string oldValue, string newValue)
{
    Console.WriteLine($"从 {oldValue} 变为 {newValue}");
}

4.6 依赖注入(适配 MS DI)

CommunityToolkit.Mvvm 不内置 DI ,但可无缝配 Microsoft.Extensions.DependencyInjection

csharp 复制代码
public partial class App : Application
{
    public App() => Services = ConfigureServices();

    public new static App Current => (App)Application.Current;

    public IServiceProvider Services { get; }

    private static IServiceProvider ConfigureServices()
    {
        var services = new ServiceCollection();
        services.AddTransient<Main>();                                   // 窗口(瞬时)
        services.AddSingleton<MainViewModel>();                          // VM(单例)
        services.AddSingleton<IPeople<User, ViewUser>, UserService>();   // 服务
        return services.BuildServiceProvider();
    }
}

VM 中通过构造函数注入服务,运行时用 App.Current.Services.GetService<MainViewModel>() 或注入 IServiceProvider 获取窗口/对象:

csharp 复制代码
public MainViewModel(IPeople<User, ViewUser> services, IServiceProvider sp)
{
    _services = services;
    i2 = sp;
}
private void Open()
{
    i2.GetService<AnimalWindow>().ShowDialog(); // 由容器创建窗体,避免 VM 耦合窗体
}
4.6.1 GetService 与 GetRequiredService 对比

容器解析对象主要有两种方式:GetService<T>()GetRequiredService<T>()(位于 Microsoft.Extensions.DependencyInjection 命名空间)。

对比项 GetService<T>() GetRequiredService<T>()
服务未注册 返回 null,不会抛异常 InvalidOperationException,第一时间暴露配置错误
适用场景 服务「可有可无」,可空判断后再使用 服务「必须存在」,缺失时尽快失败
返回值声明 可返回空引用(易造成后续 NRE) 保证非空(框架负责检查)

示例(都取自 IServiceProvider 实例,如 App.Current.Services):

csharp 复制代码
// 写法1:GetService ------ 可能返回 null,需自行判空
AnimalWindow? win = App.Current.Services.GetService<AnimalWindow>();
if (win != null)
{
    win.ShowDialog();
}

// 写法2:GetRequiredService ------ 未注册直接抛异常,强约束
AnimalWindow win2 = App.Current.Services.GetRequiredService<AnimalWindow>();
win2.ShowDialog();

使用建议

  • 日常获取 VM、窗体、服务等必需对象 时,优先用 GetRequiredService<T>(),运行期出错能立刻提示「哪个服务没注册」,避免看到难以排查的空引用异常。
  • 仅当某个服务可选(如日志、可选的功能模块)时才用 GetService<T>(),并配合 ?. 判空。
  • 两者本质都是「从容器取出已注册实例」,区别只在未注册时的失败策略 ;若未先 services.AddSingleton/AddTransient(...) 注册,两者都无法取到对象。

4.7 常见坑

  • 源生成器不生效:确保 VS2022、C# ≥ 9.0(<LangVersion>9.0</LangVersion>)、清理重建。
  • 命令绑定失败:命令方法须为 private;方法名 Click 生成命令 ClickCommand
  • 消息不触发:ObservableRecipientIsActive = true;订阅/发送须为同一消息类型。

五、Prism 知识点

5.1 核心功能清单

功能 说明
Region(区域管理) <ContentControl> 等作为容器动态放视图,替代 <Frame>
ViewModelLocationProvider 按约定自动关联 View 与 ViewModel(自动绑定 DataContext)
Command(命令) DelegateCommand / DelegateCommand<T>(等同 RelayCommand)
Navigation(导航) 配合区域实现页面跳转,支持传参/导航日志/导航守卫
Dialog(对话框) 可将 UserControl 以对话框形式展示
Module(模块化) 大型项目拆分独立模块(IModule)
IEventAggregator 跨 VM/模块通信
DI(依赖注入) 内置 DryIoc / Unity 容器

5.2 把普通 WPF 项目改造为 Prism

App.xaml<Application> 改为 <prism:PrismApplication>,去掉 StartupUri,加命名空间。

xml 复制代码
<prism:PrismApplication x:Class="PrismDemo.App"
                        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                        xmlns:prism="http://prismlibrary.com/">
    <Application.Resources></Application.Resources>
</prism:PrismApplication>

App.xaml.cs :App 继承 PrismApplication,重写 CreateShell()RegisterTypes()

csharp 复制代码
public partial class App : PrismApplication
{
    // 创建主窗口(从容器解析,自动注入 DataContext)
    protected override Window CreateShell() => Container.Resolve<MainWindow>();

    // 登录校验:启动时弹出登录窗,取消则关闭程序
    protected override void InitializeShell(Window shell)
    {
        Login l1 = Container.Resolve<Login>();
        if (l1 == null || l1.ShowDialog() != true)
            Application.Current.Shutdown();
    }

    // 注册服务与可导航视图
    protected override void RegisterTypes(IContainerRegistry containerRegistry)
    {
        containerRegistry.RegisterSingleton<Login>();
        containerRegistry.RegisterForNavigation<Page1>();
        containerRegistry.RegisterForNavigation<Page2>();
    }

    // 模块化
    protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
    {
        base.ConfigureModuleCatalog(moduleCatalog);
        moduleCatalog.AddModule<Module1Module>();
    }
}

5.3 ViewModel:BindableBase

csharp 复制代码
public class MainViewModel : BindableBase
{
    private string _title = "Prism Application";
    public string Title
    {
        get => _title;
        set => SetProperty(ref _title, value); // 自动触发通知
    }
}

5.4 自动绑定 ViewModel(约定优于配置)

满足三条约定 即可免写 this.DataContext

  1. 视图(Window/UserControl/Page)放在 Views 文件夹;
  2. 视图模型放在 ViewModels 文件夹;
  3. 名称格式:视图名 + ViewModel(如 MainWindowMainWindowViewModel)。

若用 Show() / ShowDialog() 手动打开窗体,需手动加:

xml 复制代码
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"

5.5 区域(Region)管理:定义与打开方式

概念 :区域(Region)是 Prism 中用来「动态容纳视图」的占位容器,替代传统 <Frame> / <ContentControl>。你负责把视图放进去,展示的位置由区域决定。

定义方式 :在 XAML 中用附加属性 prism:RegionManager.RegionName="区域名" 标记某控件为区域(常用于 <ContentControl><ItemsControl><TabControl>):

xml 复制代码
<ContentControl prism:RegionManager.RegionName="ContentRegion"/>

打开方式(两种)

  1. 视图注入(固定投放)------启动时直接把视图放进来,之后不切换:
csharp 复制代码
public MainWindowViewModel(IRegionManager m)
{
    _m = m;
    _m.RegisterViewWithRegion<Header>("HeaderRegion"); // Header 显示在 HeaderRegion
    _m.RegisterViewWithRegion<Left>("LeftRegion");
}
  1. 拿到区域对象再操作 ------通过 _m.Regions["区域名"] 取出区域(IRegion),再调用其方法:
csharp 复制代码
IRegion region = _m.Regions["ContentRegion"];
region.RequestNavigate("Home");

区域适配器:让控件能被当作区域来放置视图,基类 RegionAdapterBase<T>。内置 ContentControlRegionAdapter(ContentControl)、ItemsControlRegionAdapter(ItemsControl)、SelectorRegionAdapter(ComboBox/ListBox/TabControl/Ribbon)。

5.6 导航(Navigation):定义与打开方式

概念 :导航是一组「在区域内动态切换视图」的机制。

定义方式 :可导航视图必须先通过 RegisterForNavigation<T>() 注册进容器(App.xaml.cs):

csharp 复制代码
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
    containerRegistry.RegisterForNavigation<User>();
    containerRegistry.RegisterForNavigation<Order>();
    containerRegistry.RegisterForNavigation<Home>();
}

打开方式 :在 VM 中借助 IRegionManager(Prism 自动注入)调用 RequestNavigate("区域名", "视图名")

csharp 复制代码
public DelegateCommand SelectChangedCommand =>
    new DelegateCommand(() =>
    {
        _m.RequestNavigate("ContentRegion", SelectedItem.PageName); // 区域名 + 视图名
    });

导航打开方式的进阶形式2区域和导航 项目):带参数 + 回调日志。

csharp 复制代码
NavigationParameters navPars = new NavigationParameters();
navPars.Add("name", "张三");
navPars.Add("age", 10);

_m.RequestNavigate("ContentRegion", page, par =>
{
    if (par.Result == true)                              // 跳转成功
        Journal = par.Context.NavigationService.Journal;   // 导航日志
    // par.Context.NavigationService / par.Context.Uri / par.Context.Parameters
}, navPars);

5.7 区域 vs 导航:定义方式与打开方式对比

一句话区分:区域是「容器」负责视图放哪;导航是「动作」负责如何把视图放入/换出区域

对比项 区域(Region) 导航(Navigation)
本质 一个占位「容器对象」 一组「切换视图的行为/机制」
定义方式 XAML 标记 prism:RegionManager.RegionName="区域名" 视图须 RegisterForNavigation<T>() 注册进容器
打开方式 A RegisterViewWithRegion<View>("区域名")(固定投放,不可切换) RequestNavigate("区域名", "视图名")(动态切换)
打开方式 B _m.Regions["区域名"] 取出区域对象再操作 进阶:RequestNavigate + NavigationParameters 传参 / 回调获取日志
能否切换视图 不能(一次投放固定) 能(随时切换、可前进/后退)
是否需要注册 是(必须先 RegisterForNavigation
典型场景 固定布局:头部、侧边栏、抽屉 页面流转:菜单切换、翻页、Tab

协作关系 :导航一定要落在某个区域内(RequestNavigate 第一个参数就是区域名);区域提供一个「被导航的场地」。实际项目通常先定义区域,再注册导航视图,最后在菜单选中事件里 RequestNavigate

5.8 导航深入:传参、导航确认、导航日志

  • 传参NavigationParameters 对象,通过 RequestNavigate 的第 三/四 个参数传递。
  • 接收 :目标 VM 实现 INavigationAware,重写:
    • OnNavigatedTo(NavigationContext context):到达某视图;
    • OnNavigatedFrom(NavigationContext context):离开某视图;
    • IsNavigationTarget(NavigationContext context):返回布尔值,true 重用视图(性能高)、false 重建。
  • 导航确认/守卫 :实现 IConfirmNavigationRequest.ConfirmNavigationRequest(context, callback),时机应写在离开的页面
  • 导航日志IRegionNavigationJournal,提供 GoForward() / GoBack() / Clear(),判断用 CanGoForward / CanGoBack

5.9 命令:DelegateCommand

csharp 复制代码
public DelegateCommand ClickCommand { get; }
public DelegateCommand<string> ParamCommand { get; }

public PageAViewModel()
{
    ClickCommand = new DelegateCommand(OnClick);
    ParamCommand = new DelegateCommand<string>(OnParamClick);
    // 监听属性变化自动刷新 CanExecute:
    // new DelegateCommand(OnClick, CanClick).ObservesProperty(() => _count);
}

5.10 事件聚合器:IEventAggregator

csharp 复制代码
// 定义事件
public class MessageEvent : PubSubEvent<string> { }

// 发送方:构造注入 IEventAggregator
_eventAggregator.GetEvent<MessageEvent>().Publish("来自 Sender 的消息");

// 接收方:订阅(可指定 UI 线程)
eventAggregator.GetEvent<MessageEvent>()
    .Subscribe(OnMessageReceived, ThreadOption.UIThread);

5.11 对话框服务:IDialogService / IDialogAware

Prism 的对话框服务可把用户控件(UserControl当对话框弹出,替代原生 Window。

定义方式 :把用户控件注册为对话框,并让其对应的 VM 实现 IDialogAware

csharp 复制代码
// App.xaml.cs:注册对话框
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
    containerRegistry.RegisterDialog<Login>(); // 把 Login 用户控件注册成一个对话框
}

打开方式 :注入 IDialogService,调用 ShowDialog("对话框名", 回调)

csharp 复制代码
service.ShowDialog("Login", d =>
{
    if (d.Result != ButtonResult.OK)
    {
        System.Environment.Exit(0); // 登录取消则退出
    }
});

对话框的 VM 必须实现 IDialogAware 四个成员

csharp 复制代码
public class LoginViewModel : BindableBase, IDialogAware
{
    public string Title => "登录窗体";                       // 标题
    public event Action<IDialogResult> RequestClose;         // 请求关闭事件

    public bool CanCloseDialog() => true;                    // 是否可关闭
    public void OnDialogClosed() { }                         // 关闭时触发
    public void OnDialogOpened(IDialogParameters parameters) { } // 打开时触发

    public DelegateCommand LoginCommand => new DelegateCommand(() =>
    {
        if (User.Name == "admin" && User.Psw == "123456")
            RequestClose?.Invoke(new DialogResult(ButtonResult.OK)); // 关闭并返回结果
    });
}

相比原生 Window,对话框服务的好处是:无窗体(解耦合)、可在关闭时统一回传 IDialogResult、支持传参 IDialogParameters

5.12 模块化开发(IModule)

概念 :模块化将复杂系统拆分成多个相对独立、功能单一的类库项目 ,每个模块有自己的 Views / ViewModels / 配置。

优势与应用场景 :简化开发、提高复用、降低耦合、增强扩展性;适合中大型项目(小项目用属过度设计)。

模块开发步骤

  1. 新建 WPF 类库(模块项目),结构与主项目一致(Views、ViewModels 等);
  2. 实现 IModule 接口(两个核心方法 RegisterTypesOnInitialized)。
csharp 复制代码
public class Module1Module : IModule
{
    // 模块初始化(加载后逻辑,如默认导航)
    public void OnInitialized(IContainerProvider containerProvider)
    {
        var regionManager = containerProvider.Resolve<IRegionManager>();
        regionManager.RequestNavigate("ContentRegion", "Home");
    }

    // 注册模块内的视图/服务
    public void RegisterTypes(IContainerRegistry containerRegistry)
    {
        containerRegistry.RegisterForNavigation<ViewA, ViewAViewModel>();
        containerRegistry.RegisterForNavigation<ViewB>();
    }
}

主应用注册模块App.xaml.csConfigureModuleCatalog):

csharp 复制代码
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
    base.ConfigureModuleCatalog(moduleCatalog);
    moduleCatalog.AddModule<Module1Module>();
}

模块加载方式 :代码方式(推荐,AddModule<T>)、配置文件方式(ConfigurationModuleCatalog)、目录路径方式(DirectoryModuleCatalog,按路径扫描)、手动加载(了解)。核心接口:IModuleIModuleCatalog

与类库的区别:Prism 模块除了拆分代码,还包含「视图/视图模型/模块配置」,并可通过模块目录动态加载,比普通类库更适合做插件化、可插拔的扩展。

5.13 Prism Template Pack(模板 + 代码片段)

VS 扩展「Prism Template Pack」提供模板:Prism Full AppPrism Blank AppPrism Module,并提供代码片段:

  • prismprop:生成带通知的属性;
  • prismcmd:生成 DelegateCommand
  • prismnav:生成导航服务(INavigationService)注入模板。

5.14 常见坑

  • 视图/VM 必须满足「Views/ViewModels 文件夹 + 命名」约定,否则需 AutoWireViewModel
  • DelegateCommand 可执行状态不更新:用 ObservesProperty 或手动 RaiseCanExecuteChanged()
  • Region 导航失败:RegionName 拼写一致 + 视图已 RegisterForNavigation 注册。
  • 事件聚合器订阅不触发:同一事件类型;UI 操作指定 ThreadOption.UIThread;避免接收方被 GC 回收。
  • 对话框不弹出:确保已 RegisterDialog<T>() 注册,且对应 VM 实现了 IDialogAware

六、选型建议

场景 推荐框架 原因
小型项目 / 快速原型 CommunityToolkit.Mvvm 轻量、官方维护、源生成器减少代码
遗留项目维护 MvvmLight 兼容存量代码(不建议新项目使用)
大型 / 模块化项目 Prism 模块化、导航、DI 等企业级特性
跨平台(MAUI 等) CommunityToolkit.Mvvm 支持最好,官方推荐
强类型事件通信 Prism / CT.Mvvm EventAggregator / WeakReferenceMessenger

总结

  • MvvmLight:轻量、简单、易上手,但已基本停更,适合学习与遗留项目。
  • CommunityToolkit.Mvvm:微软官方、轻量、高性能(编译期源生成器、弱引用消息),是新项目首选。
  • Prism:企业级重型框架,解决「模块化、区域导航、DI」等复杂需求,学习成本高。

三者核心思想一致、API 高度对应(ViewModelBase↔ObservableObject↔BindableBaseRelayCommand↔DelegateCommandMessenger↔WeakReferenceMessenger↔EventAggregator),迁移成本较低,核心逻辑可复用

相关推荐
hgfsjk1 小时前
跨境电商ERP系统软件哪个好?2026年选型指南
大数据·经验分享·笔记·其他
bruk_spp1 小时前
drm edp屏加载学习
学习
Linux-lucky2 小时前
36-Linux学习之旅之MySQL主从复制
linux·运维·学习·mysql·ubuntu
SatanII2 小时前
华为云ECS实践:从创建、镜像制作到弹性伸缩完整实操指南
linux·运维·服务器·学习·centos·华为云
留白_2 小时前
【tableau入门学习】1、数据预处理
学习
AIGC大时代2 小时前
Obsidian × Claude Cowork × Word:把研究笔记变成可验收写作链
笔记·word·claude·学术写作·obsidian·研工作流
小弥儿2 小时前
GitHub今日热榜 | 2026-09-08:微软 markitdown 冲进前三
学习·microsoft·开源·github
DevNo2 小时前
从笔记混乱到有序:我尝试的三款知识库工具
笔记