WPF Prism ViewModel的应用 电脑版发表于:2025/4/25 14:52  ># WPF Prism ViewModel的应用 [TOC] tn2>在 WPF 开发中,Prism 是一个非常流行的框架,它基于 MVVM(Model-View-ViewModel)模式,提供了一套强大的工具和模式来构建复杂、可维护、可扩展的应用程序。 本文将深入探讨如何在 WPF 中使用 Prism 的 ViewModel,实现视图与数据的优雅交互。 项目结构的组织 ------------ tn2>在开始之前,我们需要合理组织项目结构,以确保代码的清晰和可维护性。一个典型的项目结构如下: Views 目录:存放所有的视图文件(如 `MainWindow.xaml`),这些文件负责界面的展示。 ViewModels 目录:存放所有的视图模型文件(如 `MainWindowViewModel.cs`),这些文件负责业务逻辑的实现。  ### 示例代码 tn2>MainWindowViewModel.cs ```csharp namespace LearningPrismUnityIoC.ViewModels { public class MainWindowViewModel:BindableBase { private string _value = "Hello MainWindowViewModel"; public string Value { get { return _value; } set { SetProperty(ref _value, value); } } } } ``` tn2>在这个视图模型中,我们定义了一个 `Value` 属性,用于存储和提供数据给视图。 `SetProperty` 方法是 `Prism` 提供的一个辅助方法,用于简化属性的绑定和更新。 MainWindow.xaml ```xml <Window x:Class="LearningPrismUnityIoC.Views.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:LearningPrismUnityIoC" mc:Ignorable="d" xmlns:prism="http://prismlibrary.com/" prism:ViewModelLocator.AutoWireViewModel="True" Title="MainWindow" Height="450" Width="800"> <Grid> <Button Content="按钮" Margin="0,0,672,364"></Button> <TextBlock Text="{Binding Value}" Margin="0,75,497,276"/> </Grid> </Window> ``` tn2>在视图中,我们通过 `prism:ViewModelLocator.AutoWireViewModel="True"` 实现了视图与 `ViewModel` 的自动绑定。`TextBlock` 的 `Text` 属性与 `MainWindowViewModel` 中的 `Value` 属性进行了绑定。 ### 自动绑定机制的解析 tn2>Prism 提供的 ViewModelLocator 是一个强大的工具,它能够自动将视图与对应的 ViewModel 进行绑定。 具体来说,ViewModelLocator 会根据视图的命名约定在指定的命名空间中查找对应的 ViewModel 类。<br/> 例如,MainWindow 视图会自动查找 MainWindowViewModel 类作为其 ViewModel。这种自动绑定机制大大减少了样板代码,提高了开发效率。 ### 数据更新与绑定的实现 tn2>在 `MainWindowViewModel` 中,`Value` 属性通过 `SetProperty` 方法实现了 `INotifyPropertyChanged` 接口,这意味着当 `Value` 属性的值发生变化时,与之绑定的视图元素会自动更新显示。 下面将进行展示:  自定义绑定机制 ------------ tn2>首先我们创建两个文件夹`BobViews`和`BobModels`,然后在`BobViews`下创建一个`CCWindow`窗体,在`BobModels`下创建一个需要进行绑定的`CCModel`类,目录结构如下所示:  tn2>`CCWindow.xaml`内容如下,就是绑定一个简单的`Value`值。 ```xml <Window x:Class="LearningPrismUnityIoC.BobViews.CCWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:LearningPrismUnityIoC.BobViews" mc:Ignorable="d" xmlns:prism="http://prismlibrary.com/" prism:ViewModelLocator.AutoWireViewModel="True" Title="CCWindow" Height="450" Width="800"> <Grid> <TextBlock Text="{Binding Value}" /> </Grid> </Window> ``` tn2>`CCModel`类中创建一个简单的`Value`属性: ```csharp public class CCModel : BindableBase { private string _value = "Hello CCModel"; public string Value { get { return _value; } set { SetProperty(ref _value, value); } } } ``` tn2>然后我们在`App.xaml.cs`中做一些修改,通过调用`SetDefaultViewTypeToViewModelTypeResolver`方法进行修改解释器,将试图解析到我们对应的模型。 下面完整的代码: ```csharp public partial class App : PrismApplication { protected override Window CreateShell() { return Container.Resolve<CCWindow>(); } protected override void RegisterTypes(IContainerRegistry containerRegistry) { } protected override void ConfigureViewModelLocator() { base.ConfigureViewModelLocator(); ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(ViewToViewModelResolver); } /// <summary> /// /// </summary> /// <param name="type">需要进行匹配的试图模型</param> /// <returns></returns> /// <exception cref="NotImplementedException"></exception> private Type? ViewToViewModelResolver(Type type) { var viewName = type.FullName; var vmName = viewName.Replace(".BobViews.", ".BobModels."); // 如果是Window结尾,则我们需要截取字符串 if (vmName.EndsWith("Window")) { vmName = vmName.Substring(0,vmName.Length - 6); } vmName += "Model"; return Type.GetType(vmName); } } ```  临时绑定 ------------ ```csharp protected override void ConfigureViewModelLocator() { base.ConfigureViewModelLocator(); //ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(ViewToViewModelResolver); ViewModelLocationProvider.Register<CCWindow, CCModel>(); } ``` tn2>`SetDefaultViewTypeToViewModelTypeResolver`方法会对所有的解析都会产生影响,如果我们只是某个别的需要进行绑定,我们可以通过注册的方式来完成。举例:  属性注入 ------------ tn2>如果我们有其他的接口和属性,我们可以这样进行注入。 ```csharp [Dependency] public IDataBaseAccess _data { get; set; } ``` tn>注意需要先注册`IDataBaseAccess`才能注入。 事件聚合器 ------------ ### IEventAggregator tn2>`IEventAggregator` 是 Prism 框架中的一个接口,用于获取和管理事件。 它背后的实现类 `EventAggregator` 提供了发布/订阅多播的功能。 这意味着可以有多个发布者发起同一个事件,也可以有多个订阅者监听同一个事件。 这种机制允许组件之间通过事件进行通信,而无需直接引用彼此。 创建一个事件 ```csharp /// <summary> /// 事件创建 /// </summary> public class TestEvent:PubSubEvent<object> { } ``` tn2>然后我们在Model中进行绑定事件方法,并且EventMessage方法进行处理,然后创建一个`BtnCommand`命令用于发布事件。 ```csharp public class CCModel : BindableBase { private string _value = "Hello CCModel"; public string Value { get { return _value; } set { SetProperty(ref _value, value); } } public CCModel(IEventAggregator eventAggregator) { _eventAggregator = eventAggregator; // 创建一个事件 _eventAggregator.GetEvent<TestEvent>().Subscribe(EventMessage); } private void EventMessage(object obj) { } IEventAggregator _eventAggregator; public ICommand BtnCommand { get => new DelegateCommand(() => { _eventAggregator.GetEvent<TestEvent>().Publish("Hello Test Event"); }); } } ``` tn2>窗体我们创建一个按钮进行绑定事件。 ```xml <Window x:Class="LearningPrismUnityIoC.BobViews.CCWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:LearningPrismUnityIoC.BobViews" mc:Ignorable="d" xmlns:prism="http://prismlibrary.com/" prism:ViewModelLocator.AutoWireViewModel="True" Title="CCWindow" Height="450" Width="800"> <Grid> <TextBlock Text="{Binding Value}" /> <Button Command="{Binding BtnCommand}" Content="测试事件" Margin="0,0,567,316"/> </Grid> </Window> ```  tn2>注册事件可以在多个地方进行注册,举例,我们在`CCWindow.xaml.cs`中进行注册,那么它会在我们点击之后两边都会进行执行: ```csharp public partial class CCWindow : Window { public CCWindow(IEventAggregator eventAggregator) { eventAggregator.GetEvent<TestEvent>().Subscribe(EventMessage); InitializeComponent(); } private void EventMessage(object obj) { } } ```   tn2>除此之外它还可以定义什么样的线程来执行这样的事件。 如下面代码所示: ```csharp // 这里使用了UI线程执行 _eventAggregator.GetEvent<TestEvent>().Subscribe(EventMessage,ThreadOption.UIThread); // 它支持三种线程 public enum ThreadOption { // 发布线程 PublisherThread, // UI渲染线程 UIThread, // 后台线程 BackgroundThread } ``` tn2>这里传第三个参数是`keepSubscriberReferenceAlive`,当设置为 `true` 时,`PubSubEvent` 会使用强引用来保留对订阅者的引用,避免垃圾回收机制回收订阅者对象。 当设置为 `false` 时,`PubSubEvent` 会使用弱引用来引用订阅者,这样订阅者对象可以在不需要时被垃圾回收。 默认情况下,`keepSubscriberReferenceAlive` 参数为 `false`,也就是说,默认情况下是使用弱引用。 ```csharp _eventAggregator.GetEvent<TestEvent>().Subscribe(EventMessage,ThreadOption.UIThread,false); ``` tn>通过这些信息,我能够总结出这个参数的两个主要应用场景: 当订阅者需要长期保持订阅状态时(如全局模块的订阅),可以设置为 `true`,以确保订阅者不会被过早回收。 当订阅者仅在某个作用域内有效时(如视图模型在窗口关闭后不再需要订阅),通常设置为 `false`,以便订阅者可以被垃圾回收。 ```csharp _eventAggregator.GetEvent<TestEvent>().Subscribe(EventMessage,ThreadOption.UIThread,false, filter => filter.ToString().Contains("")); ``` tn2>第四个参数用于判断是否需要触发该事件。