
WPF Prism 框架:打造高效、可维护的 WPF 应用
Prism 框架简介
Prism 是一个用于构建松耦合、可维护且可测试的 XAML 应用程序的框架,支持 WPF、.NET MAUI、Uno Platform 和 Xamarin Forms 等多个平台。
它提供了多种设计模式的实现,如 MVVM(Model-View-ViewModel)、依赖注入、命令、事件聚合器等,这些模式有助于编写结构良好且易于维护的 XAML 应用程序。
Prism 框架关键库
库名 | 描述 |
---|---|
Prism.Core |
实现MVVM的核心功能,属于一个与平台无关的项目。 |
Prism.WPF |
包含了DialogService、Region、Module、Navigation,其他的一些WPF的功能 |
Prism.Unity |
Prism.Unity 是 Prism 框架的一个扩展,它集成了 Unity 依赖注入容器。 |
这里我创建了一个LearningPrism
项目,并安装好了这三个包,如下图所示:
<ItemGroup>
<PackageReference Include="Prism.Core" Version="9.0.537" />
<PackageReference Include="Prism.Unity" Version="9.0.537" />
<PackageReference Include="Prism.Wpf" Version="9.0.537" />
</ItemGroup>
BindableBase
BindableBase 是 Prism 提供的一个基类,实现了 INotifyPropertyChanged 接口。
它简化了属性变更通知的触发逻辑,确保 UI 能够自动响应数据变化。
INotifyDataErrorInfo
INotifyDataErrorInfo 是 WPF 4.0 引入的一个接口,用于实现自定义的数据验证逻辑。它包含以下三个成员:
方法 | 描述 |
---|---|
bool HasErrors |
一个只读属性,用于指示是否存在错误。 |
event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged |
一个事件,用于通知绑定的控件错误信息已更改。 |
IEnumerable GetErrors(string propertyName) |
一个方法,用于获取指定属性的错误信息。 |
下面是一个简单的例子,当Value大于100
报错。
public class MainViewModel : BindableBase, INotifyDataErrorInfo
{
private int _value = 100;
public int Value
{
get { return _value; }
set {
if (value > 100)
{
ErrorsContainer.SetErrors("Value",new string[] { "值不能大于100" });
}
SetProperty(ref _value, value);
}
}
/// <summary>
/// 定义 OnErrorsContainerCreate 方法,用于在 ErrorsContainer 中添加错误时触发
/// </summary>
/// <param name="arg"></param>
private void OnErrorsContainerCreate(string arg)
{
// 触发 ErrorsChanged 事件,通知 UI 错误信息已更改
ErrorsChanged?.Invoke(this,new DataErrorsChangedEventArgs(arg));
}
/// <summary>
/// 定义一个私有字段 _errorsContainer,用于存储错误信息
/// </summary>
private ErrorsContainer<string> _errorsContainer;
public ErrorsContainer<string> ErrorsContainer
{
get {
if (_errorsContainer == null)
{
_errorsContainer = new ErrorsContainer<string>(OnErrorsContainerCreate);
}
return _errorsContainer;
}
set { _errorsContainer = value; }
}
/// <summary>
/// 判断是否存在异常
/// </summary>
public bool HasErrors => ErrorsContainer.HasErrors;
/// <summary>
/// 用于通知 UI 错误信息已更改
/// </summary>
public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;
/// <summary>
/// 用于获取指定属性的错误信息
/// </summary>
/// <param name="propertyName"></param>
/// <returns></returns>
public IEnumerable GetErrors(string? propertyName)=> ErrorsContainer.GetErrors(propertyName);
}
修改MainWindow.xaml
。
<Window x:Class="LearningPrism.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:LearningPrism"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<!-- 设置窗口的数据上下文为 MainViewModel -->
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<!-- 定义一个资源字典,用于存储控件模板 -->
<Window.Resources>
<!-- 定义一个 ControlTemplate,用于自定义 TextBox 的样式 -->
<ControlTemplate TargetType="{x:Type TextBox}" x:Key="ct">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<!-- 定义一个 Border,用于显示 TextBox 的边框 -->
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}" SnapsToDevicePixels="True"
CornerRadius="5">
<!-- 定义一个 ScrollViewer,用于显示 TextBox 的内容 -->
<ScrollViewer x:Name="PART_ContentHost" Focusable="false" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"
VerticalContentAlignment="Center" Margin="3,5" BorderThickness="0"/>
</Border>
<!-- 定义一个 TextBlock,用于显示错误信息 -->
<TextBlock Grid.Row="1" Text="{Binding (Validation.Errors)[0].ErrorContent,RelativeSource={RelativeSource AncestorType=TextBox,Mode=FindAncestor}}"
Foreground="Red" Margin="10,5"
Name="txtError"/>
</Grid>
<!-- 定义触发器,用于在 TextBox 验证失败时显示错误信息 -->
<ControlTemplate.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Visibility" Value="Visible" TargetName="txtError"/>
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Window.Resources>
<Grid>
<StackPanel>
<TextBlock Text="{Binding Value}"/>
<TextBox Text="{Binding Value,UpdateSourceTrigger=PropertyChanged}" Template="{StaticResource ct}"/>
</StackPanel>
</Grid>
</Window>
当我们进行修改100为110时,它将会报错。
行为处理
DelegateCommand
DelegateCommand 是 Prism 框架中提供的一个命令类(如果你没有使用 Prism 框架,可能也会有类似的自定义命令类来实现类似功能),它用来表示一个可以执行的操作(命令)。
DelegateCommand 包含了两个重要的部分:Execute
:表示命令被执行时要执行的方法(后面会看到绑定的 Execute 方法)。CanExecute
:用于判断命令是否可以执行,根据这个方法的返回值来决定绑定的按钮是否启用(是否变灰)。
我们将在MainViewModel
类中定义一个 DelegateCommand
类型的属性 BtnCheckCommand
。
public DelegateCommand BtnCheckCommand { get; }
接着我们定义命令执行逻辑
private bool CheckExecute()
{
return Value == 100;
}
private void Execute()
{
this.Value = 0;
}
CheckExecute
方法是 CanExecute
的具体实现,它会根据 Value
的值来判断命令是否可以执行。
如果 Value
的值等于 100,则返回 true
表示命令可以执行;否则返回 false
,表示命令不能执行。Execute
方法是命令被执行时的操作,也就是当按钮可以点击并且被点击时,会调用这个方法。
这里简单地将 Value
的值设置为 0。
接着我们初始化命令
public MainViewModel()
{
BtnCheckCommand = new DelegateCommand(Execute,CheckExecute);
}
我们给值属性添加一行代码,这行代码的作用是通知所有与这个命令相关的控件(比如按钮),命令的可执行状态可能发生了变化,让它们重新检查命令是否可以执行。
public int Value
{
get { return _value; }
set {
if (value > 100)
{
ErrorsContainer.SetErrors("Value",new string[] { "值不能大于100" });
}
SetProperty(ref _value, value);
// 触发命令相关的可执行的检查逻辑
BtnCheckCommand.RaiseCanExecuteChanged();
}
}
接着我们在窗体绑定命令按钮。
然后运行测试。
<Button Content="检查" Command="{Binding BtnCheckCommand}"></Button>
我们将点击检查,发现值等于100,然后将100改成0后,按钮将被禁用了。
检查方式
除了上面的BtnCheckCommand.RaiseCanExecuteChanged();
检查方式我们还有其他两种方式。ObservesProperty
是 Prism 框架中用于简化命令的 CanExecute 方法和属性变化通知的一种特性。它可以帮助你自动触发命令的 CanExecuteChanged
事件,而无需手动调用 RaiseCanExecuteChanged()
。下面是如何使用 ObservesProperty 来实现命令的自动检测:
public MainViewModel()
{
BtnCheckCommand = new DelegateCommand(Execute,CheckExecute)
.ObservesProperty(()=>Value);
}
// 触发命令相关的克执行的检查逻辑
//BtnCheckCommand.RaiseCanExecuteChanged();
我们还可以使用ObservesCanExecute
来进行检测。
但是ObservesCanExecute
方法只能用于观察一个简单的布尔属性,所以我们这里定义一个状态来解决。
private bool CheckExecute()
{
return Value == 100;
}
private void Execute()
{
this.Value = 0;
Status = !Status;
}
private bool _status;
public bool Status {
get => Value == 100;
set {
SetProperty(ref _status,value);
}
}
public MainViewModel()
{
BtnCheckCommand = new DelegateCommand(Execute,CheckExecute)
//.ObservesProperty(()=>Value)
.ObservesCanExecute(()=> Status)
;
}
异步命令
异步命令非常简单,就只是在普通命令的方法处理上面添加上async
就可以了。
public DelegateCommand BtnAsyncCommand { get=>new DelegateCommand(DoAsyncSoming); }
private async void DoAsyncSoming()
{
await Task.Delay(3000);
this.Value = 30;
}
在窗体我们添加一个异步按钮。
<Button Content="异步" Command="{Binding BtnAsyncCommand}"></Button>
泛型参数
首先实例化一个泛型DelegateCommand
类,这里我们以string作为一个参数进行实现。
public ICommand BtnPpCommand { get => new DelegateCommand<string>(DoPpSoming); }
private async void DoPpSoming(string obj)
{
await Task.Delay(3000);
this.Value = 40;
}
前端我们通过CommandParameter
进行传入参数
<Button Content="传参" Command="{Binding BtnPpCommand}" CommandParameter="传递的参数"></Button>
事件命令
我们这里用一个ComboBox
做一个示范,当我们进行选择时将触发该事件。
首先我们在Windows节点中添加下面两个引用。
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:prism="http://prismlibrary.com/"
添加下面的ComboBox
代码
<!-- 定义一个 ComboBox 控件,默认选中第一个选项 -->
<ComboBox SelectedIndex="0">
<!-- 设置交互触发器,用于在发生特定事件时执行相应动作 -->
<i:Interaction.Triggers>
<!-- 指定在 ComboBox 的 SelectionChanged 事件发生时触发动作 -->
<i:EventTrigger EventName="SelectionChanged">
<!-- 调用视图模型中的命令 BtnEventCommand -->
<prism:InvokeCommandAction Command="{Binding BtnEventCommand}">
</prism:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
<!-- 向 ComboBox 添加可选择的项目 -->
<ComboBoxItem Content="11111" />
<ComboBoxItem Content="22222" />
<ComboBoxItem Content="33333" />
<ComboBoxItem Content="44444" />
<ComboBoxItem Content="55555" />
</ComboBox>
添加一下BtnEventCommand
命令。
public ICommand BtnEventCommand { get => new DelegateCommand<object>(DoEventSoming); }
private void DoEventSoming(object obj)
{
}
选择后我们发现,它可以接收到我们的参数。
如果我们只希望接收到Source.SelectedItem.Content
中的参数的话可以这样写。
<prism:InvokeCommandAction Command="{Binding BtnEventCommand}" TriggerParameterPath="Source.SelectedItem.Content"/>
欢迎加群讨论技术,1群:677373950(满了,可以加,但通过不了),2群:656732739

