Prism

此处记录prism框架的方方面面

MIT开源地址

Prism 是一个用于创建模块化和易于维护的应用程序的框架,它主要起到以下作用:

  • 支持 MVVM 模式:提供了一些工具和类来帮助开发人员实现 MVVM 模式,使视图和模型之间的耦合度降低,提高了代码的可维护性和可测试性。
  • 提供依赖注入容器:可以方便地管理应用程序中的依赖关系,使代码更加简洁和易于理解。
  • 支持模块化开发:可以将应用程序拆分成多个模块,每个模块可以独立开发、测试和部署,提高了开发效率和代码的复用性。
  • 提供导航和视图切换功能:可以方便地实现应用程序中的导航和视图切换,使应用程序的用户体验更加流畅。
  • 支持多种平台:除了 WPF 平台,Prism 还支持其他平台,如 UWP、Xamarin.Forms 等,使开发人员可以在不同的平台上使用相同的框架和技术。

Prism在vs2019已经下架了,在vs2022上架

Getting Started | Prism (prismlibrary.com)

教程|720x360

包含如下内容

  • Region(区域管理)
  • Module(模块)
  • View Injection(视图注入)
  • ViewModelLocationProvider(视图模型定位)
  • Command(绑定相关)
  • Event Aggregator (事件聚合器)
  • Navigation(导航)
  • Dialog(对话框)

使用prism只需要在nuget包中引入Prism.WPF和Prism.Dryloc

  • Unity
    Unity 是由 Microsoft 开发的一个轻量级的依赖注入容器,适用于 .NET 应用程序。它支持多种注入方式,包括构造函数注入、属性注入和方法注入。

  • DryIoc

    DryIoc 是一个高性能的依赖注入容器,支持多种生命周期管理和高级功能。它的设计目标是提供更好的性能和更少的内存占用。

Prism的初始化过程

img

image-20240618103848364

PrismApplicationBase其实也是继承Application,App到Application的继承链上隔了一层PrismApplication

vs插件支持

Prism Template Pack

提供了:

  • Blank Project (空白示例项目)

  • Module Project (模块示例项目)

  • 代码片段(用户快速创建属性,命令)

    propp – property (depends on BindableBase)
    cmd - DelegateCommand
    cmdg – DelegateCommand

Prism也提供了基于Xamarin的项目模板, 因为Prism是一个基于多个平台的框架

ride支持

要在ride中新建prism项目,需要使用安装nuget包:Prism.Templates

然后在ride中新建项目的时候就可以看到有prism框架相关选择了

窗体切换

prism中的窗体显示,使用下面的语法:

1
2
3
4
5
6
7
8
//拿到当前主程序
var main = Application.Current.MainWindow;
var window=Container.Resolve<MainWindow>();
//这行代码的作用是确保新创建的MainWindow实例成为当前应用程序的主窗口,以便用户可以看到并与其交互
//不明具体原因,但还是加上好
Application.Current.MainWindow = window;
window.Show();
main.Close();

需要在App.xaml.cs通过依赖注入创建视图模型,并注册窗体视图模型,才可以执行视图切换

1
2
3
var mainWindow = new MainWindow();
// 注册视图模型
containerRegistry.Register<ViewModels.MainWindowViewModel>();

对比使用下面WPF常规方式显示窗口,将丧失导航功能等

1
2
3
var mainWindow = new MainWindow();
mainWindow.Show();
//这种方式不是prism支持的通过依赖注入来创建窗口的方式,视图模型的构造函数,prism的导航功能都可能无法正常工作

Region

参考链接

Region作为Prism当中模块化的核心功能,其主要目的是弱化了模块与模块之间的耦合关系。
在普遍的应用程序开发中,界面上的元素及内容往往被固定。

image-20240618104745163

基本使用方法

定义Region的方式有两种,一个是在XAML界面指定,另一种这是代码当中指定。

XAML中指定:

1
<ContentControl prism:RegionManager.RegionName="自定义区域名"/>

代码创建:(这种是初始化的时候没有绑定视图,只是注册视图容器)

1
RegionManager.SetRegionName(指定ContentControl的name,自定义区域名);

代码中给区域注册用户控件 (这种是初始化就绑定了视图)

1
2
3
4
5
6
 public MainWindowViewModel(IRegionManager regionManager)
{
//在ContentRegion区域中显示Views目录下的ContentView内容
regionManager.RegisterViewWithRegion("ContentRegion", typeof(Views.view1));
}
//注意Views目录下的view1不能是页面或窗口,只能是用户控件,不然报错

案例

完全符合MVVM的区域切换实现方式

区域名称的定义和绑定

1
2
3
4
<ContentControl 
x:Name="MainContentRegion"
Grid.Column="1"
prism:RegionManager.RegionName="{Binding RegionName}">
  • 区域名称通过 ViewModel 的 RegionName 属性来控制
  • 实现了区域名称的动态绑定,可以在 ViewModel 中随时修改

区域注册的触发时机

1
2
3
4
5
6
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<prism:InvokeCommandAction Command="{Binding SetRegionCommand}"
CommandParameter="{Binding ElementName=MainContentRegion}" />
</i:EventTrigger>
</i:Interaction.Triggers>

当ContentControl加载完成时触发,通过Loaded事件确保控件已经完全初始化

1
2
3
4
5
6
7
8
private void SetRegion(ContentControl contentControl)
{
if (contentControl != null)
{
RegionManager.SetRegionName(contentControl, RegionName);
RegionManager.SetRegionManager(contentControl, _regionManager);
}
}

区域切换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//通过下面代码来切换RegionName对应的视图容器中的内容:navigatePath
_regionManager.RequestNavigate(RegionName, navigatePath, NavigationCallback);

private void NavigationCallback(NavigationResult result)
{
if (result.Result == true)
{
Log.Debug($"导航成功: {result.Context.Uri}");
}
else
{
Log.Error($"导航失败: {result.Error?.Message}");
}
}

区域适配器

RegionAdapter区域适配器

上面能在Grid中能使用prism:RegionManager.RegionName属性是因为官方给Grid提供了区域适配器,像StackPanel就没有区域适配器

img

Prism提供了许多内置得RegionAdapter

  • ContentControlRegionAdapter
  • ItemsControlRegionAdapter
  • SelectorRegionAdapter
    - ComboBox
    - ListBox
    - Ribbon
    - TabControl

自定义区域适配器

自定义区域适配器是用于定义如何将视图(View)添加到区域(Region)中的机制。区域适配器负责将特定类型的 UI 元素(如 UserControlWindowContentControl 等)与区域关联起来,以便在运行时可以将视图加载到这些区域中

以添加StackPanel区域适配器为例

新建名为StackPanelRegion类

下面针对TabControl

TabControl参考此处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class TabControlAdapter : RegionAdapterBase<TabControl>
{
public TabControlAdapter(IRegionBehaviorFactory regionBehaviorFactory) : base(regionBehaviorFactory)
{
}

protected override void Adapt(IRegion region, TabControl regionTarget)
{
region.Views.CollectionChanged += (s, e) =>
{
if (e.Action == NotifyCollectionChangedAction.Add)
foreach (UserControl item in e.NewItems)
{
regionTarget.Items.Add(new TabItem { Header = item.Name, Content = item });
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
foreach (UserControl item in e.OldItems)
{
var tabTodelete = regionTarget.Items.OfType<TabItem>().FirstOrDefault(n => n.Content == item);
regionTarget.Items.Remove(tabTodelete);
}

};
}


protected override IRegion CreateRegion()
{
return new SingleActiveRegion();
}
}

继承RegionAdapterBase<T> T为你要针对的控件

RegionManager

除了定义区域,还有以下功能:

  • 维护区域集合

  • 提供对区域的访问

    获得模块区域: var region = regionManager.Regions("区域名")

    这样就可以拿到导航容器

  • 合成视图

  • 区域导航

  • 定义区域

下面贴个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var tabRegion = _regionManager.Regions[RegionNames.AnalysisTabRegion];

if (tabRegion.Views.Any())
return;

var historyJournalView = _containerProvider.Resolve<HistoryJournalView>();
var historyDataView = _containerProvider.Resolve<HistoryDataView>();
var jobJournalView = _containerProvider.Resolve<JobJournalView>();

tabRegion.Add(historyJournalView);
tabRegion.Add(historyDataView);
tabRegion.Add(jobJournalView);

tabRegion.Activate(historyJournalView);

Module

本质上来说,对于一个应用程序而言,特定功能的所有View、Logic.Service等都可以独立存在。那么意味着,每个独立的功能我们都可以称之为模块。而往往实际上,我们在一个项目当中,他的结构通常是如下所示:

img

当我们开始考虑划分模块之间的关系的时候,并且采用新的模块化解决方案,结构将变成如下所示:

img

创建模块

实现IModule接口的类可以将该包标识为模块

创建Module实际上是将模块独立与类库存在,主程序通过加载类库添加模块。以下步骤:
创建Module

首先, 我们创建一个基于WPF的应用程序, 暂且定义为ModuleA, 接下来为ModuleA定义一个类,并且实现IModule接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class ModuleAModule : IModule
{
/// <summary>
/// 通知模块已被初始化。
/// 执行诸如视图注册或任何其他模块初始化代码之类的操作
/// </summary>
/// <param name="containerProvider"></param>
public void OnInitialized(IContainerProvider containerProvider)
{
//在这里可以进行区域和模块的连接(若有需要的话)
}

/// <summary>
/// 当一个模块被加载到应用程序中时,首先调用RegisterTypes
/// 应用于注册模块实现的任何服务或功能
/// </summary>
/// <param name="containerRegistry"></param>
public void RegisterTypes(IContainerRegistry containerRegistry)
{
//注册视图通过如下方式:
//使用containerProvider找回Prism RegionManage的实例
var regionManager = containerProvider.Resolve<IRegionManager>();
//通过RegionManage注册视图
regionManager.RegisterViewWithRegion("MyModuleView", typeof(Views.ThisModuleView));
}
}

加载模块

ModuleCatalog保存了应用程序可以使用的模块信息。该目录本质上是ModuleInfo类的集合。每个模块都在一个ModuleInfo类中描述,记录了模块的名称、类型和位置等属性。有几种典型的方法可以用来填充ModuleCatalog,使其包含ModuleInfo实例

主程序配置模块目录
配置方式如下:

  • (代码方式)Code
  • (配置文件)App.config
  • (磁盘目录)Disk/Directory
  • (XAML定义)XAML
  • (自定义)Custom
    Register Catalog with PrismApplication
    Register Modules with Catalog

您应该使用的注册和发现机制取决于您的应用程序需要什么。使用配置文件或XAML文件可以使您的应用程序不需要引用模块。使用目录可以使应用程序发现模块,而无需在文件中指定它们

代码方式

在启动项目当中,添加ModuleA的应用, 打开App.xaml.cs, 重写ConfigureModuleCatalog方法,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public partial class App
{
protected override Window CreateShell()
{
return Container.Resolve<MainWindow>();
}

protected override void RegisterTypes(IContainerRegistry containerRegistry)
{

}
//手动代码导入模块需要重写的方法
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
//添加模块A
moduleCatalog.AddModule<ModuleAModule>();
}
}

Directory配置模块目录

通过读取根目录Modules文件夹查找模块

1
2
3
4
5
6
7
8
public partial class App
{
//目录扫描的方式导入模块需要重写该方法
protected override IModuleCatalog CreateModuleCatalog()
{
return new DirectoryModuleCatalog() { ModulePath=@".\Modules" };//表示当前目录下的Modules文件夹下,当前目录是可执行文件的位置
}
}

需要将模块需要的三个文件放到指定的文件夹(上面是Modules文件夹中)中,如:Login.deps.json,Login.dll,Login.pdb

这样的方式可以配合类库上右键-属性0-生成-事件-生成后事件中添加如下内容:

1
2
xcopy "$(ProjectDir)\bin\Debug\net6.0-windows\$(ProjectName).dll" "$(SolutionDir)\IonImplantationSystem\bin\Debug\net6.0-windows\Modules\" /Y /S
xcopy "$(ProjectDir)\bin\Release\net6.0-windows\$(ProjectName).dll" "$(SolutionDir)\IonImplantationSystem\bin\Release\net6.0-windows\Modules\" /Y /S

这样就成功设置可以很简单的导入模块了

App.Config配置模块目录

1
2
3
4
5
6
7
public partial class App
{
protected override IModuleCatalog CreateModuleCatalog()
{
return new ConfigurationModuleCatalog();
}
}

然后,为应用程序添加配置文件app.config, 添加以下内容:

1
2
3
4
5
6
7
8
9
10
<configuration>
<configSections>
<section name="modules" type="Prism.Modularity.ModulesConfigurationSection, Prism.Wpf" />
</configSections>
<startup>
</startup>
<modules>
<module assemblyFile="ModuleA.dll" moduleType="ModuleA.ModuleAModule, ModuleA, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" moduleName="ModuleAModule" startupLoaded="True" />
</modules>
</configuration>

XAML配置模块目录

修改CreateModuleCatalog方法, 从指定XAML文件读取模块配置

1
2
3
4
5
6
7
public partial class App
{
protected override IModuleCatalog CreateModuleCatalog()
{
return new XamlModuleCatalog(new Uri("/Modules;component/ModuleCatalog.xaml", UriKind.Relative));
}
}

创建模块名为ModuleCatalog.xaml文件, 添加模块信息

1
2
3
4
5
6
7
8
9
<m:ModuleCatalog xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:m="clr-namespace:Prism.Modularity;assembly=Prism.Wpf">

<m:ModuleInfo ModuleName="ModuleAModule"
ModuleType="ModuleA.ModuleAModule, ModuleA,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />

</m:ModuleCatalog>

Prism控制何时加载模块

  • 应用程序可以尽快初始化模块,称为“when available”
  • 在应用程序需要它们时初始化,称为“on-demand”

视图注入

应用程序模块后,每个子模块中的视图可以独立的进行依赖注入。再使用IRegionManager来实现页面导航。

步骤:
1.利用Region进行导航功能。
2.使用Module将应用程序模块化。
3.将独立模块的视图、服务使用注入到容器当中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ModuleAModule : IModule
{
private readonly IRegionManager _regionManager;

public ModuleAModule(IRegionManager regionManager)
{
_regionManager = regionManager;
}

public void OnInitialized(IContainerProvider containerProvider)
{
_regionManager.RegisterViewWithRegion("ContentRegion", typeof(ViewA));
}

public void RegisterTypes(IContainerRegistry containerRegistry)
{

}
}

依赖注入

盘点注册方法

  • Register:这是一种通用的注册方法,用于将具体的实现类与接口或抽象类进行关联。通过这种方式,容器可以在需要时提供相应的实例。
  • RegisterSingleton:用于将某个实现类注册为单例模式。这意味着在整个应用程序的生命周期中,只会创建一个该实现类的实例,并在需要时进行共享。
  • RegisterInstance:允许你直接提供一个特定的实例进行注册。这意味着容器将使用你提供的具体实例,而不是创建新的实例。
  • RegisterScoped:表示注册为作用域范围内的单例。在特定的作用域(例如请求范围)内,会有一个唯一的实例。
1
2
3
4
5
6
7
8
9
// 通用注册
containerRegistry.Register<IService, MyServiceImpl>();
// 单例注册
containerRegistry.RegisterSingleton<IService, MyServiceImpl>();
// 实例注册
var instance = new MyServiceImpl();
containerRegistry.RegisterInstance<IService>(instance);
// 作用域注册
containerRegistry.RegisterScoped<IService, MyServiceImpl>();

以最常用的单例注册为例:

1
2
containerRegistry.RegisterSingleton<IService, MyServiceImpl>();
//这个示例中,通过 containerRegistry 注册了单例实例。其他代码可以通过 IService 接口来使用该单例服务

如何使用该注册的单例

手动注入方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyViewModel : BindableBase
{
private readonly IContainerProvider _containerProvider;

public MyViewModel(IContainerProvider containerProvider)
{
_containerProvider = containerProvider;
}

public void SomeMethod()
{
var myService = _containerProvider.Resolve<IMyService>();
myService.DoSomething();
}
}

最常用的构造函数注入:

1
2
3
4
5
6
7
8
9
public class MyViewModel : BindableBase
{
private readonly IMyService _myService;

public MyViewModel(IMyService myService)
{
_myService = myService;
}
}

单例的好处包括

  • 节省资源:只创建一个对象,避免了不必要的资源消耗。
  • 一致性:确保在整个系统中使用的是同一个对象。
  • 易于管理和维护:减少了对象的创建和销毁逻辑。

依赖注入的好处:

  1. 依赖注入:通过接口来获取实例,实现了依赖注入的原则,提高了代码的灵活性和可维护性。
  2. 解耦:使得代码不再直接依赖具体的实现类,而是依赖接口。

ViewModelLocator

在WPF当中,需要为View与ViewModel建立连接, 我们需要找到View的DataContext

img

wpf中主要是两种建立连接的方式

  • XAML设置

    1
    2
    3
    <UserControl.DataContext>
    <.../>
    </UserControl.DataContext>
  • Code设置 (构造函数注入 或 ViewModelLocator)

    1
    2
    3
    4
    5
    6
    7
    8
    public partial class ViewA : UserControl
    {
    public ViewA()
    {
    InitializeComponent();
    this.DataContext = null; //设定
    }
    }

第三方的MVVM框架, 标准的ViewModelLocator可能如下所示

img

这些方式都可以建立View-ViewModel关系。
但是,这一切并不是Prism想表达的内容, 甚至不建议你按上面的方式去做, 因为这样几乎打破了开发的所有原则。
(我们把View与ViewModel的关系编码的方式通过命名的方式固定了下来, 通过静态类去维护ViewModel的关系…)

在Prism当中, 你可以基于命名约定以及文件夹结构, 便能够轻松的将View/ViewModel建议关联

假设你已经为项目添加Views/ViewModels文件夹。此时, 你的页面为ViewA, 则对应的ViewModel名称为 ViewAViewModel

img img

大致意思为View这个字符串会重叠收纳

注意不仅仅名字遵循约定,并且View要在Views文件夹下,ViewModel要在ViewModels文件夹下

当遵循了命名规范后, 此时还需要在View当中声明,允许当前View自动装配ViewModel

1
2
3
4
5
6
7
<UserControl x:Class="SettingBinding Context.Views.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com"
prism:ViewModelLocator.AutowireViewModel="True" >

</UserControl>

这种命名约定和文件夹结构也可以在代码中修改:此处略,参考这里

Prism中的MVVM

前面介绍了Prism中ViewModel如何与View进行连接

Prism与常见的MVVM框架区别

如果你了解WPF当中的ICommand, INotifyPropertyChanged的作用, 就会发现
众多框架都是基于这些进行扩展, 实现其通知、绑定、命令等功能

对于不同的MVVM框架而言, 大体使用上会在声明方式上的差异, 以及特定功能上的差别,如下图

功能↓ / →框架名 Prism Mvvmlight Micorosoft.Toolkit.Mvvm
通知 BindableBase ViewModelBase ObservableObject
命令 DelegateCommand RelayCommand Async/RelayCommand
聚合器 IEventAggregator IMessenger IMessenger
模块化 × ×
容器 × ×
依赖注入 × ×
导航 × ×
对话 × ×

各个框架之间都有各自的通知、绑定、事件聚合器等基础的功能, 而Prsim自带的依赖注入、容器、以及导航会话等功能, 可以为你提供更加强大的功能

如何在ViewModel实现基础绑定、Command、事件聚合器等操作

实现基础绑定

下面例子既有命令绑定,也有属性绑定

BindableBase

在Prism当中,需要用到绑定的控件的ViewModel需要继承ViewModel,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class TestViewModel : BindableBase
{
public TestViewModel()
{
//赋值
Message = "hello";
//DelegateCommand如果有第二个参数,可以传入一个方法,通过返回true/false来控制命令是否可执行
SendCommand = new DelegateCommand(() =>
{
Message = "hello world!";
});
}
private string _message;

public string Message
{
get { return _message; }
set { _message = value; RaisePropertyChanged(); }
}

//在Prism当中, 你可以使用DelegateCommand及带参数的Command
public DelegateCommand SendCommand { get; private set; }
//public DelegateCommand<string> SendMessageCommand { get;private set; }
}

绑定端代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
<!--截取的内容-->
xmlns:prism ="http://prismlibrary.com/"
mc:Ignorable="d"
prism:ViewModelLocator.AutoWireViewModel="True"
Title="ViewA" Height="450" Width="800">
<Grid>
<StackPanel>
<Button Width="100" Height="30" Content="更新TextBlock" Command="{Binding SendCommand}"/>
<TextBlock Text="{Binding Message}" FontSize="38" HorizontalAlignment="Center"/>
</StackPanel>

</Grid>

绑定多命令

复合命令CompositeCommand

对于单个Command而言, 只是触发单个对应的功能, 而复合命令是Prism当中非常强大的功能, CompositeCommand简单来说是一个父命令, 它可以注册N个子命令

相关语法

1
2
3
4
5
public CompositeCommand 复合命令名 {  get; private set; }
//初始化复合命令,并给复合命令注册普通命令
复合命令名 = new CompositeCommand();
复合命令名.RegisterCommand(命令名);
复合命令名.RegisterCommand(命令名2);

当复合命令被激活,它将触发对所有的子命令, 如果任意一个命令CanExecute=false,它将无法被激活,如下所示:

image-20240619094609395

此图中按下Save All按钮后什么也不执行,因为复合命令被禁用了,假设复合命令被启用,那么将会执行的是Save ASave C

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
 public class ViewAViewModel: BindableBase
{
private string message;

public ViewAViewModel()
{
Message = "hello";
SendCommand = new DelegateCommand(() =>
{
Message = "hello world!";
});
SendCommand2=new DelegateCommand(() => {
Message += "zeroko14";
});
//初始化复合命令
SendAll = new CompositeCommand();
//设置复合命令注册命令,可以注册多个命令
SendAll.RegisterCommand(SendCommand);
SendAll.RegisterCommand(SendCommand2);
}

public string Message
{
get { return message; }
set { message = value; RaisePropertyChanged(); }
}

public DelegateCommand SendCommand { get; private set; }

public DelegateCommand SendCommand2 { get; private set; }

//复合命令
public CompositeCommand SendAll { get; private set; }

}

最终标题会被设置为hello world!zeroko14 ,Command={Binding SendAll}会同时调用SendCommandSendCommand2

事件聚合器

事件聚合器IEventAggregator

事件聚合器负责接收订阅以及发布消息。订阅者可以接收到发布者发送的内容

功能盘点:

  • 松耦合基于事件通讯
  • 多个发布者和订阅者
  • 微弱的事件
  • 过滤事件
  • 传递参数
  • 取消订阅

AViewModel订阅了一个消息接收的事件, 然后BViewModel当中给指定该事件推送消息,此时AViewModel接收BViewModel推送的内容

image-20240619103421409
1
2
3
4
5
6
7
8
9
10
11
//创建事件
public class SavedEvent : PubSubEvent<string> { }

//发布
IEventAggregator.GetEvent<SavedEvent>().Publish("some value");

//订阅
IEventAggregator.GetEvent<SavedEvent>().Subscribe(message=>
{
//do something
});

事件过滤

在实际的开发过程当中,我们往往会在多个位置订阅一个事件, 但是对于订阅者而言, 他并不需要接收任何消息,事件过滤Filtering Events应运而生

image-20240619103615319

在Prism当中, 我们可以指定为事件指定过滤条件

1
2
3
4
5
6
7
8
eventAggregator.GetEvent<MessageSentEvent>().Subscribe(
arg =>{
//do something
},
ThreadOption.PublisherThread,
false,
//设置条件为token等于“MessageListViewModel” 则接收消息
message => message.token.Equals(nameof(MessageListViewModel)));

关于Subscribe当中的4个参数, 详解:

  1. action: 发布事件时执行的回调委托。
  2. ThreadOption枚举: 指定在哪个线程上接收委托回调。
    • PublisherThread: 在发布事件的线程上调用订阅者的委托回调。
    • BackgroundThread: 在后台线程上调用订阅者的委托回调。
    • UIThread: 在UI线程上调用订阅者的委托回调(通常用于更新UI)
  3. keepSubscriberReferenceAlive: 如果为true,则Prism.Events.PubSubEvent保留对订阅者的引用因此它不会回收垃圾。
    • 长期订阅,需要保持状态的情况下设置为true.(基本上可以这么认为,只有贯穿程序一生的订阅才需要设置为true)
    • 临时订阅,不再需要时要被释放资源的情况下设置为false
  4. filter: 可选参数,用于筛选事件通知,订阅者可根据筛选条件来决定是否接收特定的事件通知。返回true表示接受通知,返回false表示不接受

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
namespace prismTestNew.ViewModels
{
public class ViewAViewModel: BindableBase
{
private readonly IEventAggregator eventAggregator;
private string message;

public ViewAViewModel(IEventAggregator eventAggregator)
{
Message = "hello";
SendCommand = new DelegateCommand(() =>
{
//订阅一个消息回调函数(不带订阅过滤器)
//eventAggregator.GetEvent<MessageEvent>().Subscribe(OnMessageReceived);
//订阅一个消息回调函数(只接受hello作为回调函数参数的发布信息)
eventAggregator.GetEvent<MessageEvent>().Subscribe(OnMessageReceived, ThreadOption.PublisherThread, false, (x) => { return x == "hello"; });
});
TriggerCommand = new DelegateCommand(() =>
{
//发布消息触发消息回调(hello作为回调函数的参数)
eventAggregator.GetEvent<MessageEvent>().Publish("hello");
});
//使用他可以去订阅一些消息
this.eventAggregator = eventAggregator;
}

//针对订阅设置的消息回调函数
public void OnMessageReceived(string messageFromMessageEvent)
{
Message += messageFromMessageEvent + "\r\n";
}

public string Message
{
get { return message; }
set { message = value; RaisePropertyChanged(); }
}

public DelegateCommand SendCommand { get; private set; }

public DelegateCommand TriggerCommand { get; private set; }

}
//自定义一个消息类型,他的消息是一个字符串
public class MessageEvent : PubSubEvent<String>{ }

}

视图如下:

1
2
<Button Width="100" Height="30" Content="订阅" Command="{Binding SendCommand}" Margin="10"/>
<Button Width="100" Height="30" Content="发布" Command="{Binding TriggerCommand}" Margin="10"/>

点击订阅之后,点击发布,就会执行到自定义的OnMessageReceived函数

取消订阅

为注册的消息取消订阅, Prism提供二种方式取消订阅,如下:

  1. 通过委托的方式取消订阅

    1
    2
    var event = IEventAggregator.GetEvent<MessageSentEvent>();
    event.Unsubscribe(OnMessageReceived);
  2. 通过获取订阅者token取消订阅

    1
    2
    3
    var event = eventAggregator.GetEvent<MessageSentEvent>();
    var token = _event.Subscribe(OnMessageReceived);//订阅的时候返回token
    event.Unsubscribe(token);

区域导航

在普遍的业务场景当中, 必不可少的是页面切换, 而Prism就可以使用Navigation功能来进行页面导航

prism实现区域导航,需要满足两点基本条件

  • 注册显示区域
  • 注册导航页面

注册显示区域

注册视图类型或添加别名, 如果未指定别名,名称默认为当中类型的名称

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ModuleAModule : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{

}

public void RegisterTypes(IContainerRegistry containerRegistry)
{
//添加别名 "CustomName"
containerRegistry.RegisterForNavigation<ViewA>("CustomName");
//默认名称 "ViewB"
containerRegistry.RegisterForNavigation<ViewB>();
}
}

注册时指定ViewModel或添加别名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ModuleAModule : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{

}

public void RegisterTypes(IContainerRegistry containerRegistry)
{
//指定ViewModel
containerRegistry.RegisterForNavigation<ViewA, ViewAViewModel>();
//指定ViewModel并且添加别名
containerRegistry.RegisterForNavigation<ViewB, ViewBViewModel>("CustomName");
}
}

使用导航

在某窗口对应的viewModel文件中需要使用导航,要在构造函数参数中接受IRegionManager regionManager,并将该值存入成员属性中,就可以使用regionManager来进行下述操作了

Region的注册以及管理、导航等, 我们可以使用IRegionManager接口,所以,我们现在便可以使用该接口实现导航功能

1
2
IRegionManager regionManager = …;
regionManager.RequestNavigate("RegionName", "ViewName");

调用了IRegionManager接口的RequestNavigate方法, 并且传递了两个参数:

  • RegionName: 该参数为注册的区域名称
  • ViewName: 该参数实际为我们上面注册过的导航页, 字符串类型, 对应的是我们注册页面的nameof

带参数导航

导航页前传递一些参数, 则可以使用NavigationParameters

1
2
3
4
5
6
7
8
9
//第一种方式
var param = new NavigationParameters();
//键值对形式
param.Add("Parameter", param);
_regionManger.RequestNavigate("RegionName", "ViewName", param);

//第二种方式
//类似URL地址传递参数
_regionManger.RequestNavigate("RegionName", "ViewName?id=1&Name=xiaoming");

控制导航过程

INavigationAware
该接口包含3个方法, 每个方法中都包含当前导航的上下文, 如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 public void OnNavigatedTo(NavigationContext navigationContext)
{

}

public bool IsNavigationTarget(NavigationContext navigationContext)
{
return true;
}

public void OnNavigatedFrom(NavigationContext navigationContext)
{

}
  • OnNavigatedTo: 导航完成前, 此处可以传递过来的参数以及是否允许导航等动作的控制。
  • IsNavigationTarget: 调用以确定此实例是否可以处理导航请求。否则新建实例
  • OnNavigatedFrom: 当导航离开当前页时, 类似打开A, 再打开B时, A中的该方法被触发。

执行流程:

image-20240619143706078

获取导航请求参数

导航中允许我们传递参数, 用于在我们完成导航之前, 进行做对应的逻辑业务处理。这时候, 我们便可以在OnNavigatedTo方法中通过导航上下文中获取到传递的所有参数

1
2
3
4
5
public void OnNavigatedTo(NavigationContext navigationContext)
{
var id = navigationContext.Parameters.GetValue<int>("id");
var name = navigationContext.Parameters["Name"].ToString();
}

是否拦截导航请求

IConfirmNavigationRequest接口:该接口继承于INavigationAware, 所以, 它多了一个功能: 允许用户针对导航请求判断是否进行拦截

image-20240619144337205
1
2
3
4
5
6
7
8
9
10
11
public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
{
bool result = true;

if (MessageBox.Show("确认导航?", "温馨提示", MessageBoxButton.YesNo) == MessageBoxResult.No)
result = false;

//通过回调当前返回的确认结果,决定是否启动该导航
continuationCallback(result);
}
//返回true表示允许导航,返回false表示不允许导航

导航日志

Navigation Journal导航日志, 其实就是对导航系统的一个管理功能, 理论上来说, 我们应该知道我们上一步导航的位置、以及下一步导航的位置, 包括我们导航的历史记录。以便于我们使用导航对应用程序可以灵活的控制。

IRegionNavigationJournal
该接口包含以下功能:

  • GoBack() : 返回上一页
  • CanGoBack : 是否可以返回上一页
  • GoForward(): 返回后一页
  • CanGoForward : 是否可以返回后一页

完整案例

github上完整代码参考

1
2
3
4
5
6
7
8
9
10
11
12
13

├── App.xaml
├── App.xaml.cs

├── Views\
│ ├── MainWindow.xaml
│ ├── ViewA.xaml
│ └── ViewB.xaml

└── ViewModels\
├── MainWindowViewModel.cs
├── ViewAViewModel.cs
└── ViewBViewModel.cs

新建两个视图ViewA.xamlViewB.xaml,并在App.xaml.cs中注册两个界面

App.xaml.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using Prism.Ioc;
using System.Windows;
using testNavigation.Views;

namespace testNavigation
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App
{
protected override Window CreateShell()
{
return Container.Resolve<MainWindow>();
}

protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
//此处要注册各种资源
//默认名为ViewA
//containerRegistry.RegisterForNavigation<ViewA>();
//起名方式
containerRegistry.RegisterForNavigation<ViewA>("PageA");
containerRegistry.RegisterForNavigation<ViewB>();
}
}
}

MainWindowViewModel.cs以及MainWindow.xaml中对视图进行完善并绑定相关command

MainWindowViewModel.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
using Prism.Commands;
using Prism.Mvvm;
using Prism.Regions;
using System.Runtime.CompilerServices;

namespace testNavigation.ViewModels
{
public class MainWindowViewModel : BindableBase
{
private string _title = "Prism Application";
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}

private readonly IRegionManager regionManager;
//用于记录日志情况
IRegionNavigationJournal journal;

public DelegateCommand OpenACommand { get; set; }
public DelegateCommand OpenBCommand { get; set; }
public DelegateCommand GoBackCommand { get; set; }
public DelegateCommand GoForwordCommand { get; set; }

public MainWindowViewModel(IRegionManager regionManager)
{
OpenACommand = new DelegateCommand(OpenA);
OpenBCommand = new DelegateCommand(OpenB);
GoBackCommand = new DelegateCommand(GoBack);
GoForwordCommand = new DelegateCommand(GoForword);
this.regionManager = regionManager;
}

private void OpenA()
{

//传递参数给ViewA
NavigationParameters param = new NavigationParameters();
//参数传递的方式是通过键值对
param.Add("Value", "Hello");
//导航到ViewA(带参数)
//regionManager.RequestNavigate("ContentRegion", nameof(Views.ViewA),param);
//使用字符串导航(带参数)
//regionManager.RequestNavigate("ContentRegion", "PageA",param);
//传递参数的另一种写法(类似html的写法) 顺便加上了获取上下文日志
regionManager.RequestNavigate("ContentRegion", $"PageA?Value=Hello1", arg =>
{
//导航后拿到上下文日志
journal = arg.Context.NavigationService.Journal;
});
}

private void OpenB()
{
//导航到ViewB(第三个参数设置导航完成的时候触发回调函数)
regionManager.RequestNavigate("ContentRegion", nameof(Views.ViewB), arg =>
{
//导航后拿到上下文日志
journal = arg.Context.NavigationService.Journal;
});
}

private void GoBack()
{
//向后导航
journal.GoBack();
}

private void GoForword()
{
//向前导航
journal.GoForward();
}

}
}

MainWindow.xaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<Window x:Class="testNavigation.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
Title="{Binding Title}" Height="350" Width="525" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<Button Content="上一步" Width="auto" Height="auto" Margin="3" Command="{Binding GoBackCommand}"/>
<Button Content="下一步" Width="auto" Height="auto" Margin="3" Command="{Binding GoForwordCommand}"/>
<Button Content="打开A" Width="auto" Height="auto" Margin="3" Command="{Binding OpenACommand}"/>
<Button Content="打开B" Width="auto" Height="auto" Margin="3" Command="{Binding OpenBCommand}"/>
</StackPanel>
<ContentControl Grid.Row="1" prism:RegionManager.RegionName="ContentRegion" />
</Grid>
</Window>

这行代码在 XAML 中定义了一个 TabControl 控件,并使用 Prism 框架的 RegionManager 来将该控件与名为 ContentRegion 的区域关联起来。这种关联允许在该区域内动态加载和管理与 TabControl 相关的视图

ViewAViewModel.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
using Prism.Mvvm;
using Prism.Regions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;

namespace testNavigation.ViewModels
{
//继承BindableBase用于实现通知,继承INavigationAware用于实现view导航,可以使用IConfirmNavigationRequest代替INavigationAware,就可以多一个ConfirmNavigationRequest方法用于判断是否允许导航(IConfirmNavigationRequest是继承INavigationAware)
public class ViewAViewModel : BindableBase,IConfirmNavigationRequest
{
private string title;

public string Title
{
get { return title; }
set { title = value; }
}

//判断是否允许导航
public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
{
bool result = true;
if(MessageBox.Show("确认导航","温馨提示",MessageBoxButton.YesNo)==MessageBoxResult.No)
result = false;
//固定格式
continuationCallback(result);
}

/// <summary>
/// 重新创建实例的一个判断,如果已经打开过当前实例的话,如果打开过还返回一个true的话,他会创建一个新的实例覆盖原来的
/// </summary>
/// <param name="navigationContext"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public bool IsNavigationTarget(NavigationContext navigationContext)
{
return true;
}

/// <summary>
/// 导航离开当前页时触发
/// </summary>
/// <param name="navigationContext"></param>
/// <exception cref="NotImplementedException"></exception>
public void OnNavigatedFrom(NavigationContext navigationContext)
{

}

/// <summary>
/// 导航完成前,接收用户传递的参数以及是否允许导航等控制
/// </summary>
/// <param name="navigationContext"></param>
/// <exception cref="NotImplementedException"></exception>
public void OnNavigatedTo(NavigationContext navigationContext)
{
//接收Value键对应的值(string类型)
title = navigationContext.Parameters.GetValue<string>("Value");
}
}
}

ViewA.xaml

1
2
3
4
5
6
7
8
9
10
11
12
<UserControl x:Class="testNavigation.Views.ViewA"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:testNavigation.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="Yellow">
<TextBlock Text="{Binding Title}"/>
</Grid>
</UserControl>

对话服务

Prism提供了一组对话服务, 封装了常用的对话框组件的功能

  • RegisterDialog/IDialogService (注册对话及使用对话)
  • 打开对话框传递参数/关闭对话框返回参数
  • 回调通知对话结果

实现对话框

img

创建对话框,(注意)通常是一组用户控件 ,并且实现 IDialogAware

1
2
3
4
5
6
7
8
public interface IDialogAware
{
string Title { get; }
event Action<IDialogResult> RequestClose;
bool CanCloseDialog();
void OnDialogClosed();
void OnDialogOpened(IDialogParameters parameters);
}

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
using Prism.Commands;
using Prism.Mvvm;
using Prism.Services.Dialogs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace testDialog.ViewModels
{
public class MsgViewModel : BindableBase,IDialogAware
{
public string Title{ set; get; }

public event Action<IDialogResult> RequestClose;

public DelegateCommand OkCommand { get; set; }
public DelegateCommand CancleCommand { get; set; }

public MsgViewModel()
{
OkCommand = new DelegateCommand(() =>
{
//成功时构造返回结果返回出去(也是键值对)
DialogParameters param = new DialogParameters();
param.Add("Value", Title);
RequestClose?.Invoke(new DialogResult(ButtonResult.OK, param));
});
CancleCommand = new DelegateCommand(() =>
{
RequestClose?.Invoke(new DialogResult(ButtonResult.No));
});
}

//是否允许关闭当前窗口
public bool CanCloseDialog()
{
return true;
}

//关闭时触发
public void OnDialogClosed()
{

}

//打开前触发(用于接受参数)
public void OnDialogOpened(IDialogParameters parameters)
{
Title = parameters.GetValue<string>("Value");
}
}
}

注册对话框

注册对话框 RegisterDialog

1
2
3
4
5
6
7
8
9
10
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
//三选一:
//仅注册视图
containerRegistry.RegisterDialog<MessageDialog>();
//注册视图时绑定VM
containerRegistry.RegisterDialog<MessageDialog, MessageDialogViewModel>();
//添加别名
containerRegistry.RegisterDialog<MessageDialog>("DialogName");
}

使用Dialog

使用IDialogService接口 Show/ShowDialog 方法调用对话框

img
1
2
3
4
5
6
7
8
9
10
11
12
private readonly IDialogService dialogService;

private void ShowDialog()
{
DialogParameters keys = new DialogParameters();
keys.Add("message", "Hello,Prism!");

dialogService.ShowDialog("MessageDialog", keys, arg =>
{

});
}

调用Show/ShowDialog,我们通过注册时候的名称进行打开, 并且可以传递参数, 以及回调方法(主要用于返回对话框的返回结果)

使用对话框实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
using Prism.Commands;
using Prism.Mvvm;
using Prism.Services.Dialogs;
using System.Windows;
using testDialog.Views;

namespace testDialog.ViewModels
{
public class MainWindowViewModel : BindableBase
{
public DelegateCommand ShowDialogCommand { get; private set; }

//对话框服务
private readonly IDialogService dialog;

private string _title = "Prism Application";
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}

//增加IDialogService参数
public MainWindowViewModel(IDialogService dialog)
{
ShowDialogCommand = new DelegateCommand(ShowDialog);
this.dialog = dialog;
}

private void ShowDialog()
{
//添加参数
DialogParameters param = new DialogParameters();
param.Add("Value", "Hello");
//显示对话框(不带参数)
//dialog.ShowDialog("dialogView");
//显示对话框阳
dialog.ShowDialog("dialogView",param, arg =>
{
if(arg.Result == ButtonResult.OK)
{
//获取返回的结果
MessageBox.Show(arg.Parameters.GetValue<string>("Value"));
}
});
}
}
}

封装Dialog API

对于常用的公共对话框, 我们可以封装成扩展方法, 以便于我们在应用程序的任何位置可以使用到它

1
2
3
4
5
6
7
public static void ShowNotification(this IDialogService dialogService,
string message, Action<IDialogResult> callback)
{
var p = new DialogParameters();
p.Add("message", message);
dialogService.ShowDialog(“NotificationDialog", p, callback);
}

实例参考

测试实例参考

弹窗数据传递的核心机制

传入数据给对话框 => [[ 对话框实现 ]](#弹窗 ViewModel 实现 IDialogAware) => 传出数据给父级窗口处理程序

Prism的对话框服务遵循以下流程:

  • 参数传递: 调用弹窗时通过 DialogParameters 向弹窗传递初始参数
  • 结果回调: 弹窗关闭时通过 IDialogResult 返回用户输入的数据
  • 接口实现: 弹窗的 ViewModel 需实现 IDialogAware 接口,处理打开、关闭和数据回传逻辑

传入及传出参数

在父窗口的 ViewModel 中,使用 IDialogService.ShowDialog 方法打开弹窗,并通过 DialogParameters 传递参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void OpenDialog()
{
DialogParameters parameters = new DialogParameters();
parameters.Add("initialValue", "默认文本"); // 传递初始值
dialogService.ShowDialog("UserInputDialog", parameters, result =>
{
if (result.Result == ButtonResult.OK)
{
string userInput = result.Parameters.GetValue<string>("userInput");
// 处理用户输入的数据
}
});
}
//也可以简写成:
dialogService.ShowDialog("SelectLinkPointDialogView", new DialogParameters
{
{ "input", "测试输入内容111" },
}, r =>
{
if (r.Result == ButtonResult.OK)
{
var values = r.Parameters.GetValue<string>("output");
}
});
  • UserInputDialog:弹窗的注册名称。
  • result:回调参数,包含用户操作结果(确定/取消)和返回的数据

弹窗 ViewModel 实现 IDialogAware

弹窗的 ViewModel 需实现 IDialogAware 接口,处理数据接收与返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class UserInputDialogViewModel : BindableBase,IDialogAware
{

public string Title => "对话框标题";

//是否允许关闭当前窗口
public bool CanCloseDialog()
{
return true;
}

//关闭时触发
public void OnDialogClosed()
{
//在关闭前提供了一个时机点来做一些处理
}

//打开前触发(用于接受参数)
public void OnDialogOpened(IDialogParameters parameters)
{
if (parameters.ContainsKey("Value"))
Title = parameters.GetValue<string>("Value");
}

//请求退出,给外界提供的获取接口
public event Action<IDialogResult> RequestClose;

//可以在对话框中添加按钮,按钮绑定到命令
private DelegateCommand<string> _closeDialogCommand;
public DelegateCommand<string> CloseDialogCommand =>
_closeDialogCommand ?? (_closeDialogCommand = new DelegateCommand<string>(ExecuteCloseDialogCommand));

void ExecuteCloseDialogCommand(string parameter)
{
var dialogResult = new DialogResult(ButtonResult.OK);
dialogResult.Parameters.Add("values", new Dictionary<string, object>(_inputValues));
RequestClose?.Invoke(dialogResult); //确保对话框是通过调用RequestClose事件来关闭的,而不是通过直接关闭窗口的方式。这样可以确保DialogResult被正确传递
}
}

实现的对话框需要注册才能使用

在 Prism 的模块或容器中注册弹窗:

1
2
3
4
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterDialog<UserInputDialog, UserInputDialogViewModel>("UserInputDialog");
}

注册时需要指定弹窗的 View 和 ViewModel,并为弹窗命名

参数与返回值的封装

为提高代码可维护性,可封装自定义参数类:

1
2
3
4
5
6
7
8
public class UserInputResult
{
public string InputText { get; set; }
public bool IsConfirmed { get; set; }
}

// 在回调中使用
var result = result.Parameters.GetValue<UserInputResult>("result");

通过强类型对象代替松散键值对,减少硬编码风险

对话框窗口外观设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<prism:Dialog.WindowStyle>
<Style TargetType="Window">
<!--定义了对话框的启动位置。`CenterScreen` 表示窗口将在屏幕中央启动-->
<Setter Property="prism:Dialog.WindowStartupLocation" Value="CenterScreen" />
<!--决定窗口是否在任务栏中显示。`True` 表示该窗口会在任务栏中显示-->
<Setter Property="ShowInTaskbar" Value="True" />
<!--定义窗口的大小行为。`WidthAndHeight` 表示窗口的大小将根据其内容自动调整,以适应窗口中显示的内容的宽度和高度-->
<Setter Property="SizeToContent" Value="WidthAndHeight" />
<!--指定了窗口的样式。`ToolWindow` 通常用于工具窗口,具有较小的标题栏和不同的外观,适合用于辅助工具或面板-->
<Setter Property="WindowStyle" Value="ToolWindow" />
<!--使窗口不可被手动改变大小-->
<Setter Property="ResizeMode" Value="NoResize" />
</Style>
</prism:Dialog.WindowStyle>