Skip to content

Prism框架

约 2917 字大约 10 分钟

WPFprism

2023-10-26

框架特点
MvvmLight老版本的 mvvm 框架,已不更新
CommunityTooltik.Mvvm应用大量的特性来简化代码。两大特性 RelayCommand 和 Messenger。所有可用的 API 都不依赖于任何特定的运行时或框架
Prism成熟的 mvvm 框架,集合多种特性,一般用于大型应用程序的

MVVM 介绍

  • M:Model,即模型,它是应用程序的数据模型,用于存储应用程序的数据。Model 通常包含应用程序的业务逻辑,例如数据验证、数据访问、数据操作等。
  • V:View,即视图,是 MVVM 中的视图,它是用户界面,通常是一个页面或者一个控件。View 通常包含用户界面的布局、样式、事件处理等。(MainWindow.xaml.cs
  • VM:ViewModel,即视图模型,是 MVVM 中的视图模型,它是连接 Model 和 View 的桥梁,它负责将 Model 中的数据绑定到 View 中,同时负责将 View 中的事件传递给 Model。ViewModel 通常包含 Model 的引用,以及一些命令、属性、事件等。(MainViewModel.cs

MVVM 误区

  • MVVM 模式不一定代码都要写到 viewmodel 中,也可以写到 xaml.cs 中

CommunityToolTik.Mvvm

轻量级跨平台,专注于提供 MVVM 模式的核心组件,如数据绑定、命令和消息传递

...

Pirsm

数据绑定、命令、消息传递、导航,容器模块化

.NETCore wpf 引入 prism

  • 1.新建 wpf 项目,nuget 下载Prism.CorePrism.Unity
  • 2.在App.xaml中添加 prism 名称空间,并将最外层标签修改为prism:PrismApplication
<prism:PrismApplication  x:Class="WpfApp4.App" xmlns:prism="http://prismlibrary.com/"
	...>
   ...
</prism:PrismApplication>
  • 3.将App.xaml.cs的继承类修改为Prism.Unity.PrismApplication,并实现
public partial class App : Prism.Unity.PrismApplication
{
    protected override Window CreateShell()
    {
        return Container.Resolve<PrismWindow1>();
    }
	//容器
    protected override void RegisterTypes(IContainerRegistry containerRegistry)
    {
        //函数RegisterServices()需要Prism.Container.Extensions包
        containerRegistry.RegisterServices(services =>
        {
            services.AddSqlSugar(Log.Logger, Configuration);
        });
    }

}

概念

  • MVVM 模式:Prism 鼓励使用 MVVM(Model-View-ViewModel)模式来实现应用程序的分层架构。MVVM 模式可以有效地将应用程序的界面逻辑与业务逻辑分离,并提供了一种松耦合的方式来测试和维护应用程序。

  • 依赖注入:Prism 提供了一个强大的依赖注入容器,用于解决应用程序中的依赖关系。通过依赖注入,你可以轻松地管理和解决对象之间的依赖关系,促进代码的可测试性和可扩展性。

  • 导航:Prism 提供了导航框架,用于管理应用程序中的页面或视图之间的导航。它支持 URI 导航和参数传递,使得导航变得简单且可扩展。

  • 事件聚合:Prism 的事件聚合机制允许不同的模块或组件之间进行松耦合的通信。它允许一个模块发布一个事件,而其他模块可以订阅并响应该事件,从而实现模块之间的解耦通信。

  • 模块化开发:Prism 支持应用程序的模块化开发,使得应用程序可以根据需要加载和卸载不同的模块。这样可以提高应用程序的可维护性和可扩展性。

  • 可测试性:Prism 提供了一系列的设计模式和工具,帮助开发人员编写可测试的代码。通过使用 Prism,你可以更容易地编写单元测试和集成测试,以确保应用程序的质量。

使用:

  • 下载包Prism.Unity,Prism.Core
  • 在 App.xaml 将标签修改为<prism:PrismApplication ..> ... </prism:PrismApplication>
  • 新建文件夹ViewsViewModels

MVVM

  • Binding 模式
    • Model:分五种,决定前台与后台绑定的值 如何绑定流向。 [[WPF基础#Binding Model]]
  • 通知函数
private WPF.Infrastructure.SlideStatusEnum _slideStatus;
public WPF.Infrastructure.SlideStatusEnum SlideStatus
{
    get => _slideStatus;
    set => SetProperty(ref _slideStatus, value);//自身的简化变化通知
	OnPropertyChanged(new System.ComponentModel.PropertyChangedEventArgs(nameof(ScanDataStatistics)));//标准的更改通知
}
// `SetProperty`:`SetProperty`是Prism框架提供的一个用于简化属性更改通知的方法。它封装了属性的值设置、比较和通知的逻辑。使用`SetProperty`方法可以减少在属性的setter方法中编写重复的代码,提高代码的可读性和可维护性。`SetProperty`方法会自动比较新旧值,如果值有变化,它会更新属性并触发属性更改通知。

// `OnPropertyChanged`:`OnPropertyChanged`是标准的属性更改通知模式,它是实现`INotifyPropertyChanged`接口的一部分。通过调用`OnPropertyChanged`方法并传递属性名称,可以通知订阅者属性的值已经改变,需要更新。

MVVM 图片格式

// 使用绝对路径
var imagePath = @"C:\path\to\your\image.jpg";
ScanLiveImage = new BitmapImage(new Uri(imagePath, UriKind.Absolute));

// 使用相对路径
var relativePath = @"images\your_image.jpg"; //images文件夹在应用程序的根目录下
ScanLiveImage = new BitmapImage(new Uri(relativePath, UriKind.Relative));

// 使用pack URI
var packUri = "pack://application:,,,/YourAssembly;component/your_image.jpg"; ScanLiveImage = new BitmapImage(new Uri(packUri));

对象绑定

private ScanDataStatistics _scanDataStatistics = new();
public ScanDataStatistics ScanDataStatistics
{
    get { return _scanDataStatistics; }
    set { SetProperty(ref _scanDataStatistics, value); }
}

想让前端相应属性变化,对应的属性需要实现BindableBase
并进入监听状态
 private int _scanTime;
 public int ScanTime { get => _scanTime; set => SetProperty(ref _scanTime, value); }

导航

  • 实现 INavigationAware
/// <summary>
        /// 进入页面
        /// </summary>
        /// <param name="navigationContext"></param>
        public void OnNavigatedTo(NavigationContext navigationContext)
        {
            _logger.Information("FirstViewModel-OnNavigatedTo进入页面");
        }
        /// <summary>
        /// 根据条件判断,true:重新加载页面,false:显示之前的页面对象
        /// </summary>
        /// <param name="navigationContext"></param>
        /// <returns></returns>
        public bool IsNavigationTarget(NavigationContext navigationContext)
        {
            _logger.Information("FirstViewModel-你好久来");
            return false;
        }
        /// <summary>
        /// 离开页面
        /// </summary>
        /// <param name="navigationContext"></param>
        /// <exception cref="System.NotImplementedException"></exception>
        public void OnNavigatedFrom(NavigationContext navigationContext)
        {
            _logger.Information("FirstViewModel-OnNavigatedFrom离开页面");
        }

~~在 Prism 8 中,跨 Region 导航不会触发一般的导航事件,主要原因有以下几点: 1. 跨 Region 导航属于同一页面内部的视图替换,而不是完全新页面的导航。2. 跨 Region 替换不会导致当前页面实例重新创建,不满足导航的定义。3. Prism 默认只为完全新页面的导航绑定相关事件。 ~~4. 跨 Region 视图替换不需要保存/还原导航状态。

需注意,设置 Region 域的问题,Region 不对 无法进行导航

还有他妈一个关键原因我草 RegionManager实例,如果注入的是 RegionManager就有问题,应该使用 IRegionManager:

RegionManager.SetRegionManager 方法的作用是将一个特定的 RegionManager 实例与一个 UI 组件(如 MainWindow)关联起来。这样,当你在 UI 组件中使用 Regions 时,Prism 知道应该使用哪个 RegionManager 来查找和加载视图。~~

实际是需要手动 RequestNavigate() 到对象的 region() 就能触发导航事件

public MainWindow() {
	InitializeComponent(); // 创建 RegionManager 实例
	var regionManager = new RegionManager(); // 设置 RegionManager 与 MainWindow 关联
	RegionManager.SetRegionManager(this, regionManager); // 初始化 Regions
	regionManager.RegisterViewWithRegion("MainRegion", typeof(MyView));
}

RegionManager.SetRegionManager(mainWindow, _regionManager);

容器

  • Dependency Injection 属性注入
[Dependency]
public IRegionManager _regionManager { get; set; }
//属性注入的对象 在构造函数后才完成的,所以无法在构造行数中使用属性注入的对象
/*
1. 容器支持属性注入:首先,您需要使用支持属性注入的依赖注入容器。不是所有的依赖注入容器都直接支持属性注入,因此请确保您选择的容器能够满足该需求。

2. 属性必须是可写的:属性注入需要使用属性的 Setter 方法来进行注入,因此被注入的属性必须是可写的(即具有公共的 Setter 方法)。

3. **属性必须是公共的**:属性注入通常要求被注入的属性是公共的(即具有公共的 Getter 和 Setter 方法),以便依赖注入容器能够访问和设置该属性的值。

4. 属性必须具有正确的类型:属性注入需要确保被注入的属性具有正确的类型,以便依赖注入容器能够解析并提供正确的依赖项。

5. 注入属性必须使用适当的注解/属性:根据所使用的依赖注入框架,您可能需要使用特定的注解或属性来标记需要进行属性注入的属性。例如,在 Prism 中,可以使用 `DependencyAttribute` 来标记需要注入的属性。
*/

容器内部的注入:

  1. Register:
    • Register  方法用于注册服务,但它不指定服务的生命周期。这意味着服务的生命周期将由依赖注入容器在运行时决定,通常是每次请求时创建一个新的实例(Transient)。
    • 使用  Register  方法时,如果没有指定生命周期,服务默认为 Transient。
      containerRegistry.Register<MyService>();
  2. RegisterSingleton:
    • RegisterSingleton  方法用于注册单例服务。这意味着容器将为每个服务类型创建一个单一的实例,并且该实例将在应用程序的整个生命周期内被重用。
    • 单例服务在第一次请求时创建,并在后续的请求中重复使用相同的实例。
      containerRegistry.RegisterSingleton<MyService>();
  3. RegisterInstance:
    • RegisterInstance  方法用于注册一个特定的实例。这允许你将一个已经存在的实例注册到容器中,而不是让容器创建一个新的实例。
    • 使用  RegisterInstance  时,你可以提供一个实例和一个服务类型,容器将使用这个实例来满足所有对该服务类型的请求。
    • 这通常用于当你有一个已经创建的实例,或者需要控制实例的创建过程时。
      var instance = new MyService();
      containerRegistry.RegisterInstance(instance);
容器内需注意
  • prism 中 viewmodel 与 view.cs 中容器取的对象可能不是同一个,取决于获取方式;比如在 viewmodel 中 IEventAggregator,使用构造函数注入法就无法获取到,使用属性注入法就可以
[Dependency]
public IEventAggregator _EventAggregator { get; set; }

事件分发、订阅

Prism 库提供了一种事件机制,支持应用程序中松散耦合组件之间的通信。这种机制基于事件聚合器服务,允许发布者和订阅者通过事件进行通信,并且仍然没有相互直接引用。 PubSubEvent  可以在位于 Prism.Core NuGet 包中的 Prism.Events 命名空间中找到。

  • EventAggregator  类在容器中作为服务提供,可以通过  IEventAggregator  接口进行检索。事件聚合器负责查找或构建事件,并在系统中保留事件的集合。该对象已在容器,直接在构造函数中注入使用:```
public class TestEvent : PubSubEvent<string> { }

eventAggregator.GetEvent<TestEvent>().Publish("abcd");//发布
eventAggregator.GetEvent<TestEvent>().Subscribe(aa); //订阅
//可以进行订阅过滤,根据值
//eventAggregator.GetEvent<TestEvent>().Subscribe(aa,d=>d.StartsWith("a")); //订阅



_EventAggregator.GetEvent<PopupWindowParametersEvent>().Publish(para);

//订阅示例
_subscriptionToken = _EventAggregator.GetEvent<PopupWindowParametersEvent>().Subscribe(para =>
{
    // to do .....

    _subscriptionToken.Dispose(); //用完就扔 立即回收
}, filter => filter["ViewName"].ToString() == nameof(DataDictionaryItem)); //过滤订阅,只在对应页面触发订阅

模块

使用 Prism Library for WPF 进行模块化应用程序开发 模板导航分两种方式,一是在主页导航,二是模块管理自己的导航


//模块管理自己的导航

public class ModuleAModule : IModule
{
	public void OnInitialized(IContainerProvider containerProvider)
	{
	    var regionManager = containerProvider.Resolve<IRegionManager>();
		regionManager.RequestNavigate("ContentRegion", "PersonList");
	}

	public void RegisterTypes(IContainerRegistry containerRegistry)
	{
	    containerRegistry.RegisterForNavigation<PersonList>();
	    containerRegistry.RegisterForNavigation<PersonDetail>();
	}
}

//具体页面进行导航管理
 _regionManager.RequestNavigate("ContentRegion", "PersonDetail", parameters);

命令(Command)

  • 有符合命令的概念,一次执行多个命令
public class ViewAViewModel : BindableBase
{
  private string title;
  public DelegateCommand OpenCommand1 { get; private set; }
  public DelegateCommand OpenCommand2 { get; private set; }
  public CompositeCommand OpenAll { get; set; }

  public ViewAViewModel()
  {
    Title = "Hello!!!!";
    OpenCommand1 = new DelegateCommand(() => Title += " Prism1");
    OpenCommand2 = new DelegateCommand(() => Title += " Prism2");
    OpenAll = new CompositeCommand();
    OpenAll.RegisterCommand(OpenCommand1);
    OpenAll.RegisterCommand(OpenCommand2);

  }

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




private DelegateCommand<string> _delegateCommand;
public DelegateCommand<string> ClickCommand => _delegateCommand ?? (_delegateCommand = new DelegateCommand<string>(str =>
{
    _regionManager.RequestNavigate("ContentRegion", str);
}));

public DelegateCommand<string> SwitchLanguageCommand => new DelegateCommand<string>(
    lang =>
    {
        Debug.WriteLine("lang:" + lang);
    }
);
}

UI 框架