外观
Prism框架
| 框架 | 特点 | |
|---|---|---|
| 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.Core、Prism.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> - 新建文件夹
Views、ViewModels
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` 来标记需要注入的属性。
*/容器内部的注入:
- Register:
Register方法用于注册服务,但它不指定服务的生命周期。这意味着服务的生命周期将由依赖注入容器在运行时决定,通常是每次请求时创建一个新的实例(Transient)。- 使用
Register方法时,如果没有指定生命周期,服务默认为 Transient。containerRegistry.Register<MyService>();
- RegisterSingleton:
RegisterSingleton方法用于注册单例服务。这意味着容器将为每个服务类型创建一个单一的实例,并且该实例将在应用程序的整个生命周期内被重用。- 单例服务在第一次请求时创建,并在后续的请求中重复使用相同的实例。
containerRegistry.RegisterSingleton<MyService>();
- 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 框架
HandyControl 欢迎使用 HandyControl | HandyOrg
ModernWpf Kinnara/ModernWpf:适用于 WPF 应用程序的新式样式和控件 (github.com)
