
WPF Prism ViewModel的应用
在 WPF 开发中,Prism 是一个非常流行的框架,它基于 MVVM(Model-View-ViewModel)模式,提供了一套强大的工具和模式来构建复杂、可维护、可扩展的应用程序。
本文将深入探讨如何在 WPF 中使用 Prism 的 ViewModel,实现视图与数据的优雅交互。
项目结构的组织
在开始之前,我们需要合理组织项目结构,以确保代码的清晰和可维护性。一个典型的项目结构如下:
Views 目录:存放所有的视图文件(如 MainWindow.xaml
),这些文件负责界面的展示。
ViewModels 目录:存放所有的视图模型文件(如 MainWindowViewModel.cs
),这些文件负责业务逻辑的实现。
示例代码
MainWindowViewModel.cs
namespace LearningPrismUnityIoC.ViewModels
{
public class MainWindowViewModel:BindableBase
{
private string _value = "Hello MainWindowViewModel";
public string Value {
get { return _value; }
set { SetProperty(ref _value, value); }
}
}
}
在这个视图模型中,我们定义了一个 Value
属性,用于存储和提供数据给视图。SetProperty
方法是 Prism
提供的一个辅助方法,用于简化属性的绑定和更新。
MainWindow.xaml
<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>
在视图中,我们通过 prism:ViewModelLocator.AutoWireViewModel="True"
实现了视图与 ViewModel
的自动绑定。TextBlock
的 Text
属性与 MainWindowViewModel
中的 Value
属性进行了绑定。
自动绑定机制的解析
Prism 提供的 ViewModelLocator 是一个强大的工具,它能够自动将视图与对应的 ViewModel 进行绑定。
具体来说,ViewModelLocator 会根据视图的命名约定在指定的命名空间中查找对应的 ViewModel 类。
例如,MainWindow 视图会自动查找 MainWindowViewModel 类作为其 ViewModel。这种自动绑定机制大大减少了样板代码,提高了开发效率。
数据更新与绑定的实现
在 MainWindowViewModel
中,Value
属性通过 SetProperty
方法实现了 INotifyPropertyChanged
接口,这意味着当 Value
属性的值发生变化时,与之绑定的视图元素会自动更新显示。
下面将进行展示:
自定义绑定机制
首先我们创建两个文件夹BobViews
和BobModels
,然后在BobViews
下创建一个CCWindow
窗体,在BobModels
下创建一个需要进行绑定的CCModel
类,目录结构如下所示:
CCWindow.xaml
内容如下,就是绑定一个简单的Value
值。
<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>
CCModel
类中创建一个简单的Value
属性:
public class CCModel : BindableBase
{
private string _value = "Hello CCModel";
public string Value
{
get { return _value; }
set { SetProperty(ref _value, value); }
}
}
然后我们在App.xaml.cs
中做一些修改,通过调用SetDefaultViewTypeToViewModelTypeResolver
方法进行修改解释器,将试图解析到我们对应的模型。
下面完整的代码:
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);
}
}
临时绑定
protected override void ConfigureViewModelLocator()
{
base.ConfigureViewModelLocator();
//ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(ViewToViewModelResolver);
ViewModelLocationProvider.Register<CCWindow, CCModel>();
}
SetDefaultViewTypeToViewModelTypeResolver
方法会对所有的解析都会产生影响,如果我们只是某个别的需要进行绑定,我们可以通过注册的方式来完成。举例:
属性注入
如果我们有其他的接口和属性,我们可以这样进行注入。
[Dependency]
public IDataBaseAccess _data { get; set; }
注意需要先注册IDataBaseAccess
才能注入。
事件聚合器
IEventAggregator
IEventAggregator
是 Prism 框架中的一个接口,用于获取和管理事件。
它背后的实现类 EventAggregator
提供了发布/订阅多播的功能。
这意味着可以有多个发布者发起同一个事件,也可以有多个订阅者监听同一个事件。
这种机制允许组件之间通过事件进行通信,而无需直接引用彼此。
创建一个事件
/// <summary>
/// 事件创建
/// </summary>
public class TestEvent:PubSubEvent<object>
{
}
然后我们在Model中进行绑定事件方法,并且EventMessage方法进行处理,然后创建一个BtnCommand
命令用于发布事件。
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");
});
}
}
窗体我们创建一个按钮进行绑定事件。
<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>
注册事件可以在多个地方进行注册,举例,我们在CCWindow.xaml.cs
中进行注册,那么它会在我们点击之后两边都会进行执行:
public partial class CCWindow : Window
{
public CCWindow(IEventAggregator eventAggregator)
{
eventAggregator.GetEvent<TestEvent>().Subscribe(EventMessage);
InitializeComponent();
}
private void EventMessage(object obj)
{
}
}
除此之外它还可以定义什么样的线程来执行这样的事件。
如下面代码所示:
// 这里使用了UI线程执行
_eventAggregator.GetEvent<TestEvent>().Subscribe(EventMessage,ThreadOption.UIThread);
// 它支持三种线程
public enum ThreadOption
{
// 发布线程
PublisherThread,
// UI渲染线程
UIThread,
// 后台线程
BackgroundThread
}
这里传第三个参数是keepSubscriberReferenceAlive
,当设置为 true
时,PubSubEvent
会使用强引用来保留对订阅者的引用,避免垃圾回收机制回收订阅者对象。
当设置为 false
时,PubSubEvent
会使用弱引用来引用订阅者,这样订阅者对象可以在不需要时被垃圾回收。
默认情况下,keepSubscriberReferenceAlive
参数为 false
,也就是说,默认情况下是使用弱引用。
_eventAggregator.GetEvent<TestEvent>().Subscribe(EventMessage,ThreadOption.UIThread,false);
通过这些信息,我能够总结出这个参数的两个主要应用场景:
当订阅者需要长期保持订阅状态时(如全局模块的订阅),可以设置为 true
,以确保订阅者不会被过早回收。
当订阅者仅在某个作用域内有效时(如视图模型在窗口关闭后不再需要订阅),通常设置为 false
,以便订阅者可以被垃圾回收。
_eventAggregator.GetEvent<TestEvent>().Subscribe(EventMessage,ThreadOption.UIThread,false, filter => filter.ToString().Contains(""));
第四个参数用于判断是否需要触发该事件。
欢迎加群讨论技术,1群:677373950(满了,可以加,但通过不了),2群:656732739

