WPF prism

article/2025/8/28 8:37:41

Prism

Prism.Dryloc 包

安装 Nuget 包 - Prism.DryIoc

1. 修改 App.xaml

修改 App.xaml 文件,添加 prism 命名空间, 继承由 Application → PrismApplication,删除默认启动 url, StartupUri=“MainWindow.xaml”

<dryioc:PrismApplicationx:Class="PrismClass.App"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"🔴xmlns:dryioc="http://prismlibrary.com/"xmlns:local="clr-namespace:PrismClass"><dryioc:PrismApplication.Resources /></dryioc:PrismApplication>

2. 修改App.xaml.cs

打开 App.xaml.cs 文件, 继承由 Application → PrismApplication(注意这里一定要编译一下,不然会报分部类继承类不一致的错误) , 如下所示。实现基类两个抽象方法:CreateShell( ) 与 RegisterTypes( ) 。

  • CreateShell:该方法返回了一个 Window 类型的窗口, 其实就是返回应用程序的主窗口。
  • RegisterTypes:该方法用于在 Prism 初始化过程中, 我们定义自身需要的一些注册类型, 以便于在 Prism 中可以使用。说白了,就是依赖注入,可以注入需要的服务等。
public partial class App : PrismApplication
{protected override Window CreateShell() //返回应用程序的主窗口{return Container.Resolve<MainWindow>();}protected override void RegisterTypes(IContainerRegistry containerRegistry) //依赖注入{throw new NotImplementedException();}
}

Prism Template Pack 扩展

Prism Template Pack 提供了哪些?

  • Blank Project 空项目
  • Module Project 模块示例项目
  • 代码片段(用户快速创建属性、命令)
  • propp-property(depends on BindableBase)
  • cmd-DelegateCommand
  • cmdg-DelegateCommand

安装完成后,再次打开 Visual Studio,将会看到 Prism Template Pack 提供了多种项目模板,用于快速构建基于 Prism 的应用程序

  1. 完整带Damo

  2. 模块

  3. 空项目

内置代码片段

这个生产力工具也内置了大量代码片段,例如:

  • propp - Property, 有一个后端字段,该字段依赖于 BindableBase 类。

    private string _fieldName;
    public string PropertyName
    {
    get { return _fieldName; }
    set { SetProperty(ref _fieldName, value); }
    }
    
  • cmd - 创建一个带有执行方法的委托命令属性。

    private DelegateCommand _fieldName;
    public DelegateCommand CommandName =>_fieldName ?? (_fieldName = new DelegateCommand(ExecuteCommandName));void ExecuteCommandName()
    {}
    
  • cmdfull - 创建一个具有“执行”和“能否执行”方法的委托命令属性

    private DelegateCommand _fieldName;
    public DelegateCommand CommandName =>_fieldName ?? (_fieldName = new DelegateCommand(ExecuteCommandName));void ExecuteCommandName()
    {}bool CanExecuteCommandName()
    {return true;
    }
    
  • cmdg - 创建一个带参数的委托命令属性

    private DelegateCommand<string> _fieldName;
    public DelegateCommand<string> CommandName =>_fieldName ?? (_fieldName = new DelegateCommand<string>(ExecuteCommandName));void ExecuteCommandName(string parameter)
    {}
    
  • cmdgfull - 创建一个具有“执行”和“能否执行”方法的泛型委托命令属性

    private DelegateCommand<string> _fieldName;
    public DelegateCommand<string> CommandName =>_fieldName ?? (_fieldName = new DelegateCommand<string>(ExecuteCommandName, CanExecuteCommandName));void ExecuteCommandName(string parameter)
    {}bool CanExecuteCommandName(string parameter)
    {return true;
    }
    

Prism MVVM

本框架和其它二个常用的MVVM框架之间的区别:

功能↓ / →框架名PrismMvvmlightCommunityToolkit.MVVM
通知BindableBaseViewModelBaseObservableObject
命令DelegateCommandRelayCommandAsync/RelayCommand
聚合器IEventAggregatorIMessengerIMessenger
模块化××
容器××
依赖注入××
导航××
对话××

Views 和 ViewModels 的绑定

在 MVVM 中,Prism 提供了 Views 和 ViewModels 的绑定的几种方式:

方法一:基于约定

两个个文件夹 Views 和 ViewModels

  • Views 中是 xxx.xaml

  • ViewModels 是 xxxViewModel.cs

  • 注意要在 Xaml 中开启自动绑定:

    prism:ViewModelLocator.AutoWireViewModel="True
    
方法二:重写映射规则

重写 ConfigureViewModelLocator 方法,用于配置 ViewModel 定位器

protected override void ConfigureViewModelLocator()
{// 调用基类的 ConfigureViewModelLocator 方法,保留默认的配置行为base.ConfigureViewModelLocator();// 设置默认的 View 类型到 ViewModel 类型的解析方式ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver((viewType) =>{// 获取视图类型的完整名称(包括命名空间)var viewName = viewType.FullName;// 获取视图所在程序集的完整名称var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;// 构造对应的 ViewModel 类型名称,假设约定为视图类名加后缀 "VM",并与视图同在一个程序集var viewModelName = $"{viewName}VM, {viewAssemblyName}";// 根据构造的 ViewModel 名称获取对应的类型对象return Type.GetType(viewModelName);});
}
方法三:手动指定

手动指定 View 和 ViewModel 关系

protected override void ConfigureViewModelLocator()
{base.ConfigureViewModelLocator();//1️⃣ type / typeViewModelLocationProvider.Register(typeof(MainWindow).ToString(), typeof(CustomViewModel));//2️⃣ type / factoryViewModelLocationProvider.Register(typeof(MainWindow).ToString(), () => Container.Resolve<CustomViewModel>());//3️⃣ generic factoryViewModelLocationProvider.Register<MainWindow>(() => Container.Resolve<CustomViewModel>());//4️⃣ generic typeViewModelLocationProvider.Register<MainWindow, CustomViewModel>();
}

BindableBase 通知

属性通知

在 Prism 中, 继承 BindableBase 可以实现属性的变化通知,变化通知可以设置 SetProperty() 这个可以通知其他属性,带有这个方法重载或 RaisePropertyChanged() 我们可以看到 SetProperty() 方法有两个参数,会判断 _title 和 value 是否相等,如果不相等就进行赋值操作并触发 OnPropertyChanged 事件。

namespace PrismCollection.ViewModels
{public class MainWindowViewModel : BindableBase //支持通知需要继承 BindableBase{private string _title = "Prism Application";public string Title{get { return _title; }set { SetProperty(ref _title, value); } //SetProperty()通知}private string name;public string Name{get { return name; }set{name = value;RaisePropertyChanged(); //相当于原版 OnPropertyChanged()}}public MainWindowViewModel(){}}
}

📍 RaisePropertyChanged() 和 SetProperty() 的区别?

  1. RaisePropertyChanged():手动触发属性变更通知,相当于原版 OnPropertyChanged()
    • 通知绑定系统某个属性的值发生了变化。
    • 你需要手动传入属性名。
  2. SetProperty() :简化属性设置和通知变更
    • 自动比较新旧值(避免重复通知)。
    • 如果值有变化,则赋值并调用 RaisePropertyChanged()
    • 可以添加额外的回调(如值改变后的操作)。
数据验证

在属性变化的时候还会涉及到数据验证的问题,Prism 提供了 ErrorContainer 以便管理及通知数据验证的错误消息,如果想要使用,ViewModel 类不要直接继承 BindableBase,而是抽离出基类 DomainObject,让其实现 INotifyDataErrorInfo 的接口和 BindableBase 类,代码如下:

使用时就让 ViewModel 继承下面的实现类 DomainObject

1️⃣ 创建基类

实现 INotifyDataErrorInfo 的接口和 BindableBase

//带属性验证的基类
public class DomainObject : BindableBase, INotifyDataErrorInfo //继承BindableBase类和INotifyDataErrorInfo接口
{public ErrorsContainer<string> _errorsContainer;protected ErrorsContainer<string> ErrorsContainer{get{if (_errorsContainer == null)_errorsContainer = new ErrorsContainer<string>(s => OnErrorsChanged(s));return _errorsContainer;}}public void OnErrorsChanged(string propertyName){ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));}//实现 INotifyDataErrorInfo 接口public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;public IEnumerable GetErrors(string propertyName){return ErrorsContainer.GetErrors(propertyName);}public bool HasErrors //实现 bool HasErrors { get; } 属性{get { return ErrorsContainer.HasErrors; }}
}
2️⃣ViewModel 继承上面创建的类

在 ViewModel 中通过 ErrorsContainer.SetErrors 输出错误消息ErrorContainer.ClearErrors 清空错误 管理数据验证的错误消息了,代码如下:

namespace PrismCollection.ViewModels
{public class ErrorContainerMockViewModel:DomainObject //继承上面自己实现的基类 DomainObject{private int age; public int Age{get { return age; }set { SetProperty(ref age, value);if (age < 0)//参数1. 属性名  参数2. 错误信息ErrorsContainer.SetErrors(nameof(Age), new[] { "年龄不能小于0" });elseErrorsContainer.ClearErrors(nameof(Age));}}}
}

nameof :是 C# 6.0 引入的一个关键字,用于获取变量、属性、方法、类等成员的名称(字符串),不会因重命名而失效

3️⃣ 在 XAML 中绑定
<TextBoxWidth="200"Height="30"Text="{Binding Age, UpdateSourceTrigger=PropertyChanged}" ToolTip="{Binding (Validation.Errors)[0].ErrorContent, RelativeSource={RelativeSource Self}}" />

DelegateCommand 命令

在 Prism 当中,可以使用 DelegateCommand 即带参数的 Command。

注意 XAML 中传参的方式为 CommandParameter,如果是属性则直接 CommandParameter="{Binding Property}",如果是绑定到控件属性上则要写成下面这种方式,指定元素名称和路径值。

装完 Prism Template Pack 扩展后可以直接使用 cmd 快速生成 DelegateCommand 属性。

命令快捷键:

  • cmd:只有一个逻辑方法

  • cmdfull: 有一个逻辑方法和一个是否执行方法

  • cmdg:只有一个带参数的逻辑方法

  • cmdgfull :有一个带参数的逻辑方法和一个是否执行方法

1️⃣ ViewModel .cs 中实现命令

DelegateCommand 用于命令,其也有泛型 DelegateCommand,本质是一个委托,当命令触发的时候,委托调用方法执行。

using System.Windows;
using Prism.Commands;
using Prism.Mvvm;namespace PrismCollection.ViewModels
{public class MainWindowViewModel : BindableBase{//构造函数public MainWindowViewModel(){ClickBtnCommand = new DelegateCommand(ClickBtnMethod);ClickBtnCommandByPara = new DelegateCommand<string>(ClickBtnMethodByPara);}//1️⃣ 不带参数命令public DelegateCommand ClickBtnCommand { get; set; }private void ClickBtnMethod(){MessageBox.Show("I can click btn");}//2️⃣ 带参数命令public DelegateCommand<string> ClickBtnCommandByPara { get; set; }private void ClickBtnMethodByPara(string obj){MessageBox.Show(obj);}}
}
2️⃣ View .Xaml 中绑定命令
<Button Content="{Binding Title}"/><TextBox Name="textBox" Text="你好"/><Button Content="ClickCommand"Command="{Binding ClickBtnCommand}"/><Button Content="ClickCommandByPara"Command="{Binding ClickBtnCommandByPara}"CommandParameter="{Binding ElementName=textBox,Path=Text}"/>
⏺ 带属性校验的命令:
public MainWindowViewModel(IEventAggregator eventAggregator,IRegionManager regionManager)
{// 如果标题发生变化ClickBtnCommand = new DelegateCommand(ClickBtnMethod, CanExecuteFromTitleChange).ObservesProperty(()=>Title); //当 Title 属性发生变化时,自动调用 RaiseCanExecuteChanged(),刷新按钮是否可用
}// 不带参数命令
public DelegateCommand ClickBtnCommand { get; set; }private void ClickBtnMethod()
{MessageBox.Show("你好");
}private bool CanExecuteFromTitleChange()
{if(Title == "Albert"){return true;}return false;
}

ObservesProperty( ):是 Prism 框架中 DelegateCommand 的一个扩展方法,用于自动监听某个属性的变化,从而自动触发命令的 CanExecute 逻辑刷新。


CompositeCommand 多路命令

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

ShellView\n\nSave All
ShellViewModel\nSaveAll\nCompositeCommand
ViewModel A\nSave A DelegateCommand
ViewModel B\nSave B DelegateCommand
ViewModel C\nSave C DelegateCommand

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

在这里插入图片描述

注意代码中复合命令 CompositeCommandByBtn.RegisterCommand(xxx)

using System.Windows;
using Prism.Commands;
using Prism.Mvvm;namespace PrismCollection.ViewModels
{public class MainWindowViewModel : BindableBase{//构造函数public MainWindowViewModel(){ClickBtnCommand = new DelegateCommand(ClickBtnMethod);ClickBtnCommandByPara = new DelegateCommand<string>(ClickBtnMethodByPara);// 注册复合命令CompositeCommandByBtn = new CompositeCommand();CompositeCommandByBtn.RegisterCommand(ClickBtnCommand);CompositeCommandByBtn.RegisterCommand(ClickBtnCommandByPara);}// 不带参数命令public DelegateCommand ClickBtnCommand { get; set; }// 带参数命令public DelegateCommand<string> ClickBtnCommandByPara { get; set; }// 多路命令public CompositeCommand CompositeCommandByBtn { get; private set; }private void ClickBtnMethodByPara(string obj){MessageBox.Show(obj);}private void ClickBtnMethod(){MessageBox.Show("你好");}}
}

IEventAggregator 事件聚合器

事件聚合器什么意思?
相信大家一定都使用过聊天软件,这就是事件聚合器。当你在一个视图 A 中输入文字点击发送之后,另外一个视图 B 会接收到这个消息,并将文字输出到屏幕上,而这个时候,视图 A 并不关心谁将收到信息,只管提交,视图 B 也不管是谁发来的消息,只管接收,并显示。这个其实就是订阅发布,通过 DelegateCommand 来实现事件的订阅发布。

1️⃣ 消息订阅体

定义一个事件类,这边选择的是用字典来发送消息:

public class MessageEvent: PubSubEvent<Dictionary<string,string>> //要继承 PubSubEvent<> 里面页可以选择其它类型
{
}
2️⃣ 订阅事件和发布事件(先订阅再发布)

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

  • action: 发布事件时执行的委托。
  • ThreadOption 枚举: 指定在哪个线程上接收委托回调,有三种选择 PublisherThread(与发布者保持在同一线程上)、UIThread(在 UI 线程上执行)、BackgroundThread(在后台线程上执行)
  • keepSubscriberReferenceAlive: 如果为 true,则 Prism.Events.PubSubEvent 保留对订阅者的引用因此它不会收集垃圾,用完必须要取消订阅。
  • filter: 进行筛选以评估订阅者是否应接收事件。
 //创建事件聚合器字段
private readonly IEventAggregator _eventAggregator;
private string _textLook = string.Empty;/*1️⃣ SubscribeCommand 订阅命令***********/
public DelegateCommand SubscribeCommand { get; private set; }void SubscribeMessage(){this.UnsubscribeMessage(); //先取消对事件的订阅,防止重复注册或内存泄露_eventAggregator.GetEvent<MessageEvent>().Subscribe(OnMessageReceived); //注册上面定义的事件}void OnMessageReceived(Dictionary<string, string> dicMsg) //收到事件后执行的方法{// 处理接收到的消息//把当前时间和 dicMsg["TextLook"] 的内容,以字符串的形式追加到 TextLook 属性中TextLook += $"{DateTime.Now} Subscribe : {dicMsg["TextLook"]} \r\n";}/*2️⃣ UnSubscribeCommand 取消订阅命令***********/
public DelegateCommand UnsubscribeCommand { get; private set; }void UnsubscribeMessage(){_eventAggregator.GetEvent<MessageEvent>().Unsubscribe(OnMessageReceived);}/*3️⃣ PublishCommand 发布命令***********/
public DelegateCommand<Dictionary<string, string>> PublishCommand { get; private set; }void PublishMessage(Dictionary<string,string> dicMsg){dicMsg = new Dictionary<string,string>();dicMsg.Add("TextLook", "AlbertZhao");_eventAggregator.GetEvent<MessageEvent>().Publish(dicMsg);}/*4️⃣ FilterCommand 带条件过滤的订阅***********/
public DelegateCommand FilterCommand { get; private set; }
private void Filter(){//先取消对事件的订阅,防止重复注册或内存泄露_eventAggregator.GetEvent<MessageEvent>().Unsubscribe(OnMessageReceived);//过滤事件//参数1. 收到事件后执行的方法(自己写的)  //参数2. 指定在哪个线程调用回调(在发布线程中执行)//参数3. 是否保持强引用(不保持强引用,让GC可以清理)//参数4. 过滤器,只让满足条件的消息触发回调_eventAggregator.GetEvent<MessageEvent>().Subscribe(OnMessageReceived, ThreadOption.PublisherThread, false, dicMsg =>{if (dicMsg["TextLook"].Equals("Hello")) return true;else{TextLook += $"{DateTime.Now} : Filter data :{dicMsg["TextLook"]} \r\n";return false;}});}

📌实例项目功能结构概览:

命令方法功能
SubscribeCommandSubscribeMessage普通订阅消息
UnsubscribeCommandUnsubscribeMessage取消订阅
PublishCommandPublishMessage发布消息
FilterCommandFilter带条件过滤的订阅
  1. _eventAggregator.GetEvent<MessageEvent>().Subscribe(OnMessageReceived):订阅
  2. _eventAggregator.GetEvent<MessageEvent>().Unsubscribe(OnMessageReceived):取消订阅
  3. _eventAggregator.GetEvent<MessageEvent>() .Publish(dicMsg):发布
  4. _eventAggregator.GetEvent<MessageEvent>().Subscribe():过滤

📌在 “4” 过滤订阅 中几个参数说明:

参数名类型含义默认值
actionAction<TPayload>接收到事件时执行的回调方法必填
threadOptionThreadOption指定在哪个线程调用回调PublisherThread
keepSubscriberReferenceAlivebool是否保持强引用(防止被GC)false
filterPredicate<TPayload>过滤器,只让满足条件的消息触发回调null

下面是示例中所使用的值解释:

参数说明
actionOnMessageReceived收到事件后执行的方法
threadOptionPublisherThread在发布线程中执行(UI操作时推荐用 UIThread
keepSubscriberReferenceAlivefalse不保持强引用,让GC可以清理(推荐)
filterdicMsg => ...只有 TextLook == "Hello" 的消息才触发 OnMessageReceived,否则被拦截
3️⃣ 构造函数注入 IEventAggregator
//构造函数
public MainWindowViewModel(IEventAggregator eventAggregator) 
{this._eventAggregator = eventAggregator; //注入聚合器字段PublishCommand = new DelegateCommand<Dictionary<string, string>>(PublishMessage);//发布SubscribeCommand = new DelegateCommand(SubscribeMessage); // 订阅UnsubscribeCommand = new DelegateCommand(UnsubscribeMessage);  //取消订阅FilterCommand = new DelegateCommand(Filter);  //过滤订阅
}

注意其中还有消息过滤的事件订阅 FilterCommand,用于过滤消息。


Module

Modules 是能够独立开发、测试、部署的功能单元,Modules 可以被设计成实现特定业务逻辑的模块(如 Profile Management),也可以被设计成实现通用基础设施或服务的模块(如 Logging、Exception Management)。既然 Modules 能够独立开发、测试、部署,那么如何告诉Shell(我们的宿主程序)去 Load 哪些 Module,以怎样的形式 Load 这些 Module 呢?Prism 为此提供了一个叫 ModuleCatalog 的东西。他用这个来管理 Module。所以在 App 启动之初,需要创建配置一个 ModuleCatalog。

  1. 通过 Prism Template Package 创建 Prism Module 项目或者直接创建 Wpf 项目,引入 Prism 包,并删除 App.xaml,然后将 outputType 改为 Class Library。

  2. 新建 Views 文件夹,新建一个用户控件 ViewA,新增一个类 ModuleAModule.cs 实现 IModule 接口(每一个 Module 类都要实现这个接口,而每一个 Module 都要有这样一个类来对 Module 里的资源统一管理)

using Prism.Ioc;
using Prism.Modularity;
using Prism.Regions;
using PrismCollectionModuleA.Views;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PrismCollectionModuleA
{public class ModuleA : IModule{public void OnInitialized(IContainerProvider containerProvider){var regionManager = containerProvider.Resolve<RegionManager>();regionManager.RegisterViewWithRegion("RegionPage", typeof(ModuleViewA));}public void RegisterTypes(IContainerRegistry containerRegistry){}}
}
  1. 在主程序中加载模块官方 Demo 中有五种常见方式 https://github.com/PrismLibrary/Prism-Samples-Wpf

(1)AppConfig 方式

在项目中添加 App.config 配置文件,注意这边 moduleType 是名称空间.类名形式。重写 App.xaml.cs CreateModuleCatalog() 方法。注意拷贝模块 dll 到主项目生成目录下

<?xml version="1.0" encoding="utf-8"?>
<configuration><configSections><section name="modules" type="Prism.Modularity.ModulesConfigurationSection, Prism.Wpf" /></configSections><startup></startup><modules><module assemblyFile="PrismCollectionModuleA.dll" moduleType="PrismCollectionModuleA.ModuleA, PrismCollectionModuleA, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" moduleName="ModuleA" startupLoaded="True" /></modules>
</configuration>
//App.xaml.cs
protected override IModuleCatalog CreateModuleCatalog()
{return new ConfigurationModuleCatalog();
}

(2)目录方式:主项目重写 CreateModuleCatalog 方法,创建一个 Modules 文件夹,里面拷贝生成好的 dll。

protected override IModuleCatalog CreateModuleCatalog()
{return new DirectoryModuleCatalog() { ModulePath = @".\Modules" };
}

(3)代码方式:主项目引用模块项目,重写 ConfigureModuleCatalog 方法,将模块添加进去。

protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{moduleCatalog.AddModule<PrismCollectionModuleA.ModuleA>();
}

(4)手动加载方式:主项目引用模块项目,重写 ConfigureModuleCatalog 方法

protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{var moduleAType = typeof(ModuleA);moduleCatalog.AddModule(new ModuleInfo(){ModuleName = ModuleA.Name,ModuleType = ModuleA.AssemblyQualifiedName,InitializationMode = InitializationMode.OnDemand});
}

(5)Xaml 方式:不推荐,不作介绍,直接查看官方源码


Region 区域

什么是区域?

在理解这个之前, 首先需要了解一下, 在最常见的开发模式当中, 我们去设计某个页面的时候, 实际上界面元素在设计的时候已经被固定。
举个简单的例子,当我们去设计如下页面, 它包含 Header、Menu、Content 内容。我们可以为这个页面设计一些元素, 例如:

  1. Menu 可以放置 ListBox

  2. Content 可以放置一个 ContentControl

  3. Header 可以放置一些 ToolBar

在这里插入图片描述

这就导致了页面多的时候难以统一管理,就引出了 Prism 中 Region 的概念,演变为下图:

RegionManager 功能主要有维护区域集合、提供对区域的访问、合成视图、区域导航、定义区域。

定义 Region

区域(Region)就是 View 的占位容器,你可以将其他 View 动态地插入这个容器中,而不需要在 XAML 中提前写死。

在 XAML 代码中引入名称空间xmlns:prism="http://prismlibrary.com/",定义一个 ContentControl,官方实现了 ContentControlRegionAdapter.cs 内容控件适配器,所以可以为其指定区域,这里用的设计模式为适配器模式。

区域名称 RegionManager.RegionName=“xxx”

定义区域

可以使用前端或者后端指定区域

  1. 前端定义

    • prism:RegionManager.RegionName=“RegionPage”
  2. 后端代码定义

    • RegionManager.SetRegionName(RegionPage, “RegionPage”);
1️⃣ 前端代码定义区域
<Window x:Class="PrismCollection.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><ContentControl Name="RegionPage"prism:RegionManager.RegionName="RegionPage"/></Grid>
</Window>
2️⃣ 后端代码定义区域:
using Prism.Regions;
using System.Windows;namespace PrismCollection.Views
{/// <summary>/// Interaction logic for MainWindow.xaml/// </summary>public partial class MainWindow : Window{public MainWindow(){InitializeComponent();//参数1. 你要标记为区域的控件  参数2. 给这个区域起的名字RegionManager.SetRegionName(RegionPage, "RegionPage");}}
}
定义视图和区域的关系

我们有两种方式来定义视图和区域的关系,一种是视图发现,一种是视图注入,下面将介绍两种方式。

1️⃣ 视图发现

视图发现 ViewDiscovery

  1. 创建一个用户自定义控件叫做 RegionPageB,
  2. 在 MainWindowViewModel 中进行区域注入,使用容器注入 IRegionManager
  3. RegisterViewWithRegion 将我们的视图和区域进行关联起来

IRegionManager 接口包含一个只读属性 Regions,是 Region 的集合(这个集合是从 xaml 中获取的,也就是我们定义的那些),RegionManager 的实例会使用他们,并将 view 注册给他们。

namespace PrismCollection.ViewModels
{public class MainWindowViewModel : BindableBase{private readonly IRegionManager regionManager; //声明一个字段来接收 Region 集合public MainWindowViewModel(IRegionManager regionManager){// 在区域中注册视图this.regionManager = regionManager;//参数1. 区域名称  参数2. 你要注册到该区域的视图this.regionManager.RegisterViewWithRegion("RegionPage", typeof(RegionPageB));}}
}

📌RegisterViewWithRegionRequestNavigate 区别

方法作用显示多个视图?备注
RegisterViewWithRegion注册一个默认视图,自动显示通常只用于初始加载
RequestNavigate动态导航切换视图✅(配合 INavigationAware更灵活,推荐用于菜单切换
2️⃣ 视图注入

视图注入 View Injection:实际上还是注入 IRegionManager,通过容器 IContainerExtension 来获取 view,通过 RegionManager 来获取 region,最后在 region 中加入 view。

  1. 先在 App.xaml.cs 中注册两个视图 ChangeRegionToA 和 ChangeRegionToB:

    protected override void RegisterTypes(IContainerRegistry containerRegistry)
    {// 注册 RegionPageAcontainerRegistry.Register<RegionPageA>();// 如果有 RegionPageB,也一并注册containerRegistry.Register<RegionPageB>();
    }
    
  2. 然后再 view.cs 中注入并激活:

    public partial class MainWindow : Window{private readonly IContainerExtension _container;private readonly IRegionManager _regionManager;public DelegateCommand ChangeRegionToA { get; private set; }public DelegateCommand ChangeRegionToB { get; private set; }public MainWindow(IContainerExtension container, IRegionManager regionManager){InitializeComponent();_container = container;_regionManager = regionManager;}ChangeRegionToA = new DelegateCommand(() =>{//获取名为 "RegionPage" 的区域容器var region = _regionManager.Regions["RegionPage"];//获取注册的类型实例 RegionPageAvar view = _container.Resolve<RegionPageA>();if (!region.Views.Contains(view))region.Add(view); //把 视图 添加到 区域中//激活视图 a,切换显示当前视图region.Activate(view);});ChangeRegionToB = new DelegateCommand(() =>{var view = containerExtension.Resolve<RegionPageB>();var region = regionManager.Regions["RegionPage"];region.Add(view);//激活视图 b,切换显示当前视图region.Activate(view);});}
    

IContainerExtension:依赖注入容器的扩展封装接口,它用于在运行时解析服务 / 视图 / ViewModel常用方法:

方法用法说明
Resolve<T>()获取注册的类型实例
Register<TFrom, TTo>()注册服务接口和实现
RegisterInstance<T>(T instance)注册已存在的实例
IsRegistered<T>()判断是否已注册
视图激活
private void Button_Click(object sender, RoutedEventArgs e)
{//activate view a_region.Activate(_viewA);
}private void Button_Click_1(object sender, RoutedEventArgs e)
{//deactivate view a_region.Deactivate(_viewA);
}

导航 Navigation

导航基础

导航核心api:

名称说明
Region(区域)页面中用来承载视图的容器(如 ContentControl)
RegionManager管理区域和导航的工具
RequestNavigate请求区域跳转到某个视图
RegisterForNavigation将视图注册为可导航的组件
IContainerRegistry依赖注入容器注册接口

Prism 官方实现的导航方式示例:

  1. 注册区域,按照上面所述注册,比如区域名为 RegionPage.
  2. 编写前台界面并绑定到后台 Command 上如下文代码:
<Windowx:Class="PrismCollection.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/"Title="{Binding Title}"Width="525"Height="350"prism:ViewModelLocator.AutoWireViewModel="True"><Grid><Grid.ColumnDefinitions><ColumnDefinition Width="40*" /><ColumnDefinition Width="67*" /></Grid.ColumnDefinitions><!--定义区域--><ContentControl Name="RegionPage"Grid.Column="1"prism:RegionManager.RegionName="RegionPage" /> <StackPanel><Button Command="{Binding NavigationACommand}" CommandParameter="NavigationA"Content="NavigationA"/><Button Command="{Binding NavigationBCommand}" CommandParameter="NavigationB"Content="NavigationB"/></StackPanel></Grid>
</Window>
  1. 在 App.xmal.cs 中注册导航
namespace PrismCollection
{public partial class App: PrismApplication{/*设置主窗口 *********/protected override Window CreateShell(){return Container.Resolve<MainWindow>();}/*注册可导航视图 *********/protected override void RegisterTypes(IContainerRegistry containerRegistry){// 注册两个导航containerRegistry.RegisterForNavigation<NavigationA>();containerRegistry.RegisterForNavigation<NavigationB>();} /*模块系统 *********/protected override IModuleCatalog CreateModuleCatalog(){return new ConfigurationModuleCatalog();}}
}
  1. CreateShell():设置主窗口
    • 这是应用启动时的入口,告诉 Prism 使用哪个窗口作为主界面。
    • MainWindow 需要在容器中注册(Prism 会自动处理主窗体),这里你只需要确保 MainWindow.xaml 定义好区域。
  2. RegisterTypes():注册导航视图
    • 注册 NavigationA.xamlNavigationB.xaml 为可导航视图。
    • 注册后你可以使用 RequestNavigate("RegionName", "NavigationA") 来切换页面。
    • 这里的 "NavigationA" 默认就是类名
  3. CreateModuleCatalog():模块系统
    • 表示你打算使用模块(Module)功能,可以加载独立功能模块(DLL)。
    • 如果暂时没有使用模块系统,可以用默认的 return base.CreateModuleCatalog();
  4. IContainerRegistry:依赖注入容器注册接口
    • Register()注册类型Transient(每次新实例)
    • RegisterSingleton()注册单例Singleton(全局单例)
    • RegisterInstance()注册实例Singleton(使用提供的实例)
    • RegisterForNavigation()注册导航视图根据框架管理
    • RegisterDialog()注册对话框根据框架管理
  1. 在 MainViewModel 中绑定带参数命令来切换导航。
//导航到 A 页面命令
private DelegateCommand<string> navigationACommand;
public DelegateCommand<string> NavigationACommand =>
navigationACommand ?? (navigationACommand = new DelegateCommand<string>(ExecuteNavigationACommand));void ExecuteNavigationACommand(string parameter){if (!string.IsNullOrEmpty(parameter)){//参数1. 目标区域名  参数2. 要导航的页面名  参数3. 回调方法,用于处理导航成功或失败的情况this.regionManager.RequestNavigate("NavigationPage", parameter, NavigationCompelted);}          }//导航到 B 页面命令
private DelegateCommand<string> navigationBCommand;
public DelegateCommand<string> NavigationBCommand =>navigationBCommand ?? (navigationBCommand = new DelegateCommand<string>(ExecuteNavigationBCommand));void ExecuteNavigationBCommand(string parameter){if (!string.IsNullOrEmpty(parameter)){this.regionManager.RequestNavigate("NavigationPage", parameter, NavigationCompelted);}}//导航回调处理
//NavigationResult 包含导航是否成功的结果。
//若找不到页5面或绑定失败,则 Result 为 false。
private void NavigationCompelted(NavigationResult result){if (result.Result == true){MessageBox.Show($"Success-{result.Context.Uri.ToString()}");}else{MessageBox.Show($"Failure-{result.Context.Uri.ToString()}");}}
名称内容
RequestNavigate(region, viewName)请求导航
NavigationResult回调判断是否导航成功
CommandParameter在按钮中传递 "NavigationA""NavigationB"
导航前后回调 INavigationAware(导航传参)

我们经常在两个界面切换的时候需要做一些逻辑处理,比如保存当前用户填写的一些信息,这时候就要用到 INavigationAware 接口来处理了。

📌 INavigationAware 源码:

public interface INavigationAware : Object
{Void OnNavigatedTo(NavigationContext navigationContext);Boolean IsNavigationTarget(NavigationContext navigationContext);Void OnNavigatedFrom(NavigationContext navigationContext);
}

此接口有三个方法,分别是:

  • OnNavigatedFrom:导航之前触发,一般用于保存该页面的数据
  • OnNavigatedTo:导航后目的页面触发,一般用于初始化或者接受上页面的传递参数
  • IsNavigationTarget:True 则重用该 View 实例,Flase 则每一次导航到该页面都会实例化一次。
导航基础传递参数:
public class MainWindowViewModel
{private readonly IRegionManager regionManager;public MainWindowViewModel(IRegionManager regionManager){this.regionManager = regionManager;}private DelegateCommand<string> navigationACommand;public DelegateCommand<string> NavigationACommand =>navigationACommand ?? (navigationACommand = new DelegateCommand<string>(ExecuteNavigationACommand));void ExecuteNavigationACommand(string parameter){if (!string.IsNullOrEmpty(parameter)){var param = new NavigationParameters(); // 创建导航参数对象param.Add("OpenA","Tuling"); // 添加参数 key = OpenA, value = Tuling//参数1. 导航到的页面    参数2. 目标区域    参数3. 要传递的参数this.regionManager.RequestNavigate("NavigationPage", "PageA", param); // 发起导航并传参}          }
}

this.regionManager.RequestNavigate(“NavigationPage”, “PageA”, param);

其中的参数:

  • 参数1. 目标区域是 "NavigationPage"(你在 XAML 中指定的 RegionName="NavigationPage" 的控件);
  • 参数2. 请求导航到名为 "PageA" 的页面;
  • 参数3. 附带参数 param(类型是 NavigationParameters)一起传过去。
导航页面 A
public class LoginMainContentViewModel : BindableBase, INavigationAware
{private readonly IRegionManager _regionManager;private DelegateCommand _createAccountCommand;public LoginMainContentViewModel(IRegionManager regionManager){_regionManager = regionManager;}public DelegateCommand CreateAccountCommand =>_createAccountCommand ?? (_createAccountCommand = new DelegateCommand(ExecuteCreateAccountCommand));private void ExecuteCreateAccountCommand(){Navigate("CreateAccount");}private void Navigate(string navigatePath){if (navigatePath != null)_regionManager.RequestNavigate(RegionNames.LoginContentRegion, navigatePath);}// True 则重用该 View 实例,False 则每一次导航到该页面都会实例化一次。public bool IsNavigationTarget(NavigationContext navigationContext){return true;}// 导航离开当前页时触发public void OnNavigatedFrom(NavigationContext navigationContext){MessageBox.Show("退出了LoginMainContent");}// 导航完成后,接收用户传递的参数public void OnNavigatedTo(NavigationContext navigationContext){//获取键为 "OpenA" 的字符串参数值//GetValue 是 NavigationParameters 类的一个泛型方法,用于从导航参数中获取指定类型的值var test = navigationContext.Parameters.GetValue<string>("OpenA");}
}

以上代码的基本流程为:

用户点击按钮
调用 RequestNavigate
执行 OnNavigatedFrom 方法
加载目标视图
执行 OnNavigatedTo 方法

NavigationContext 包含了导航相关的信息,比如:

  • Parameters:导航时传递的参数
  • NavigationService:导航服务
  • Uri:导航的目标URI
导航页面 B
// ViewModel
public class CreateAccountViewModel : BindableBase, INavigationAware
{private readonly IRegionManager _regionManager;private DelegateCommand _loginMainContentCommand;public CreateAccountViewModel(IRegionManager regionManager){_regionManager = regionManager;}public DelegateCommand LoginMainContentCommand =>_loginMainContentCommand ?? (_loginMainContentCommand = new DelegateCommand(ExecuteLoginMainContentCommand));private void ExecuteLoginMainContentCommand(){Navigate("LoginMainContent");}private void Navigate(string navigatePath){if (navigatePath != null)_regionManager.RequestNavigate(RegionNames.LoginContentRegion, navigatePath);}public bool IsNavigationTarget(NavigationContext navigationContext){return true;}public void OnNavigatedFrom(NavigationContext navigationContext){MessageBox.Show("退出了CreateAccount");}public void OnNavigatedTo(NavigationContext navigationContext){MessageBox.Show("从LoginMainContent导航到CreateAccount");}
}
导航询问是否允许

IConfirmNavigationRequest 这个接口继承自 INavigationAware,里面有一个导航前是否询问的方法。

IConfirmNavigationRequest 接口:

  • 在导航发生前进行拦截
  • 允许用户确认或取消导航操作
  • 必须调用 continuationCallback(bool) 来告知框架是否继续导航
//导航前询问,由 Prism 框架自动调用
//当用户尝试 离开当前页面 时,框架会检查当前ViewModel是否实现了 IConfirmNavigationRequest
//如果实现了,就会自动调用这个方法
public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
{var result = false;if (MessageBox.Show("是否需要导航到LoginMainContent页面?", "温馨提示",MessageBoxButton.YesNo) ==MessageBoxResult.Yes){result = true;}continuationCallback(result);
}
导航间传递参数

当然我们也可以通过导航前询问传参到下一处都是 OK 的,本质上还是依托 navigationContext

public class CreateAccountViewModel : IConfirmNavigationRequest, INavigationAware
{private readonly IRegionManager _regionManager;public User CurrentUser { get; set; }public string RegisteredLoginId { get; set; }public bool IsUseRequest { get; set; }public CreateAccountViewModel(IRegionManager regionManager){_regionManager = regionManager;}public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback){if (!string.IsNullOrEmpty(RegisteredLoginId) && this.IsUseRequest){var result = MessageBox.Show("是否需要用当前注册的用户登录?", "Navigate?", MessageBoxButton.YesNo);if (result == MessageBoxResult.Yes){// 取消当前导航continuationCallback(false);// 手动导航并带参(MainRegion 是你在 Shell 界面里注册的区域名)var parameters = new NavigationParameters{{ "loginId", RegisteredLoginId }};_regionManager.RequestNavigate("MainRegion", "LoginMainContent", parameters);return;}}// 正常继续原有导航continuationCallback(true);}public void OnNavigatedTo(NavigationContext navigationContext){MessageBox.Show("从CreateAccount导航到LoginMainContent");var loginId = navigationContext.Parameters["loginId"] as string;if (loginId != null){//创建一个新的 User 实例,并设置它的 LoginId 属性为导航参数中接收到的 loginId,//然后赋值给 ViewModel 的 CurrentUser 属性。this.CurrentUser = new User() { LoginId = loginId };}}public bool IsNavigationTarget(NavigationContext navigationContext) => true;public void OnNavigatedFrom(NavigationContext navigationContext) { }
}
导航日志

IRegionNavigationJournal

//ViewModelA 代码
private readonly IRegionManager _regionManager;
private IRegionNavigationJournal _journal; // 用于记录导航历史public ViewModelA(IRegionManager regionManager)
{_regionManager = regionManager;
}regionManager.RequestNavigate("ContentRegion","ViewA",arg=>{journal = arg.Context.NavigationService.Journal;
});regionManager.RequestNavigate("ContentRegion","ViewB",arg=>{journal = arg.Context.NavigationService.Journal;
});//
IRegionNavigationJournal _journal;private DelegateCommand _goBackCommand;
public DelegateCommand GoBackCommand =>_goBackCommand ?? (_goBackCommand = new DelegateCommand(ExecuteGoBackCommand));void ExecuteGoBackCommand()
{_journal.GoBack();
}

如果不打算将页面在导航过程中不加入导航日志,可以通过实现 IJournalAware 并从 PersistInHistory 返回 false

public class LoginMainContentViewModel : IJournalAware
{public bool PersistInHistory() => false;
}   

对话框

在 Prism 中,通过一个 IDialogAware 接口来实现对话框服务。

public interface IDialogAware
{bool CanCloseDialog();void OnDialogClosed();void OnDialogOpened(IDialogParameters parameters);string Title { get; set; }event Action<IDialogResult> RequestClose;
}
  • CanCloseDialog() 函数是决定窗体是否关闭
  • OnDialogClosed() 函数是窗体关闭时触发,触发条件取决于CanCloseDialog() 函数
  • OnDialogOpened() 函数时窗体打开时触发,比窗体Loaded事件早触发
  • Title 为窗体的标题
  • RequestClose 为关闭事件,可由此控制窗体的关闭
  1. 弹出框前后端代码

    <!--xaml-->
    <Button Content="{Binding DialogTitle}" FontSize="30" />
    
    //View.cs
    namespace PrismCollection.ViewModels
    {public class DialogAViewModel : Albert_BindableBase, IDialogAware{public string Title => "弹框提醒";public event Action<IDialogResult> RequestClose;// 允许关闭当前窗口public bool CanCloseDialog(){return true;}public void OnDialogClosed(){}public void OnDialogOpened(IDialogParameters parameters){DialogTitle = parameters.GetValue<string>("message");}private string dialogTitle;public string DialogTitle{get { return dialogTitle; }set { SetProperty(ref dialogTitle, value); }}}
    }
  2. App.cs 注册对话框

    using Prism.DryIoc;
    using Prism.Ioc;
    using Prism.Modularity;
    using Prism.Regions;
    using PrismCollection.ViewModels;
    using PrismCollection.Views;
    using System.Windows;namespace PrismCollection
    {/// <summary>/// Interaction logic for App.xaml/// </summary>public partial class App: PrismApplication{protected override Window CreateShell(){return Container.Resolve<MainWindow>();}/// <summary>/// 用于导航/// </summary>/// <param name="containerRegistry"></param>protected override void RegisterTypes(IContainerRegistry containerRegistry){// 注册两个导航containerRegistry.RegisterForNavigation<NavigationA>();containerRegistry.RegisterForNavigation<NavigationB>();// 注册区域Container.Resolve<RegionManager>().RegisterViewWithRegion("RegionPage", typeof(RegionPageA));// 注册弹窗containerRegistry.RegisterDialog<DialogA, DialogAViewModel>("Albert_Dialog");}protected override IModuleCatalog CreateModuleCatalog(){return new ConfigurationModuleCatalog();}}
    }
    
  3. 使用对话框服务,构造函数注入 IDialogService 服务

    private IDialogService _dialogService;private DelegateCommand dialogCommand;
    public DelegateCommand DialogCommand =>dialogCommand ?? (dialogCommand = new DelegateCommand(ExecuteDialogCommand));void ExecuteDialogCommand()
    {// 第二个参数可以传参_dialogService.ShowDialog("Albert_Dialog",null,arg=>{if(arg.Result == ButtonResult.OK){}});
    }public MainWindowViewModel(IDialogService dialogService,IEventAggregator eventAggregator,IRegionManager regionManager,IContainerExtension containerExtension)
    {this._dialogService = dialogService;  
    }
    

    IDialogService 中有两个方法:

    //源码
    public interface IDialogService : Object
    {Void Show(String name, IDialogParameters parameters, Action<IDialogResult> callback);Void ShowDialog(String name, IDialogParameters parameters, Action<IDialogResult> callback);  }
    

    我们可以发现 Show 和 ShowDialog 函数都是一样形参,无非就是使用场景不一样

    • name:所要调用对话框 view 的名字,当注册别名时,只能使用别名来调用
    • parameters:IDialogParameters 接口类型参数,传入的提示消息,通常是 $“message={xxxx}” 格式,然后在 ViewModel 中 OnDialogOpened 方法通过IDialogParameters 接口的 GetValue 函数来获取
    • callback:用于传入无返回值回调函数

有一些常见我们需要自定义对话框窗体,可以按照下面样式进行修改:

 <prism:Dialog.WindowStyle><Style TargetType="Window"><Setter Property="prism:Dialog.WindowStartupLocation" Value="CenterScreen" /><Setter Property="ShowInTaskbar" Value="False"/><Setter Property="SizeToContent" Value="WidthAndHeight"/><Setter Property="WindowStyle" Value="None"/></Style></prism:Dialog.WindowStyle>

http://www.hkcw.cn/article/dBeiRDxgcY.shtml

相关文章

Shell 脚本

注&#xff1a;文章参考《鸟哥的linux私房菜》、通义千问AI产品 认识 Shell Linux 中的 Shell 就是 linux 内核的一个外层保护工具&#xff0c;并负责完成用户与内核之间的交互。 Shell 可以分为以下几类&#xff1a; Bourne Shell &#xff08;简称 sh&#xff09;C Shell…

Win11安装Dify

1、打开Virtual Machine Platform功能 电脑系统为&#xff1a;Windows 11 家庭中文版24H2版本。 打开控制面板&#xff0c;点击“程序”&#xff0c;点击“启用或关闭Windows功能”。 下图标记的“Virtual Machine Platform”、“适用于 Linux 的 Windows 子系统”、“Windows…

自动化立体仓库堆垛机SRM控制系统FC19手动控制功能块开发

1、控制系统手动控制模块HMI屏幕设计如下图 屏幕分为几个区域:状态显示区、控制输入区、导航指示区、报警信息区。状态显示区需要实时反馈堆垛机的位置、速度、载货状态等关键参数。控制输入区要有方向控制按钮,比如前后左右移动,升降控制,可能还需要速度调节的滑块或选择按…

软件无线电技术之基带QPSK 调制技术+扩频技术

基带QPSK 调制技术 数字正交调制以0、1 比特流为调制信号&#xff0c;其过程就是将原始数据按照一定的规则映射至IQ 坐标系&#xff0c;而后经过DAC 转为模拟信号后才能进行后续的IQ 调制。 数字IQ 调制完成了符号到矢量坐标系的映射&#xff0c;映射点一般称为星…

图像数据与显存

一、 图像数据的介绍 1.1 灰度图像 从这里开始我们进入到了图像数据相关的部分&#xff0c;也是默认你有之前复试班计算机视觉相关的知识&#xff0c;但是一些基础的概念我仍然会提。 昨天我们介绍了minist这个经典的手写数据集&#xff0c;作为图像数据&#xff0c;相较于结…

opencut:如何用AI工具把中文图片/视频翻译成英语、日语、俄语等100多种语言!

在全球化背景下&#xff0c;无论是学习、工作还是生活&#xff0c;多语言翻译需求日益增长。从跨境电商产品图的本地化适配&#xff0c;到学习资料的快速翻译&#xff0c;传统人工翻译不仅成本高、耗时长&#xff0c;还可能因文化差异导致误解。 今天为大家分享一款高效实用的 …

揭开帕金森的神秘面纱

帕金森是一种常见的神经退行性疾病&#xff0c;多在中老年群体中出现&#xff0c;平均发病年龄约 60 岁。它主要是由于脑内特定区域产生多巴胺的神经细胞退化&#xff0c;导致多巴胺分泌减少&#xff0c;从而影响了人体的运动和其他生理功能。 这种疾病最典型的表现是运动症状&…

SAP ByDesign,项目管理为核心的中小企业数字化转型之二

1.商机管理 – 把整个销售过程控制在公司手里 2.高效的销售跟进 – 方便地创建销售报价&#xff0c;销售订单和项目来有效跟进商机 – 灵活地定义服务:服务可以是固定价格的&#xff0c;也可以是按时间和物料来计算的;可以按项目的方式或者以管理服务的 方式 – 结合物料和费用…

利用Python直接生成html注意事项

最近在结合大模型直接生成代码&#xff0c;总是出现一些看起来没问题但就是运行不通的情况如下。 明明代码正常&#xff0c;但是报错 后来查询发现&#xff0c;在Python使用f-strnig直接写入时&#xff0c;在一个字符串内的单个{}为占位符&#xff0c;需要转义&#xff0c;也…

【前端】Hexo一键生成目录插件推荐_放入Hexo博客

效果 使用 安装 npm install hexo-auto-toc插件会自动对<article>包含下的所有内容进行解析&#xff0c;自动生成目录。如果你的文章页面结构中内容没被<article>包裹&#xff0c;需要自行添加它&#xff08;即blog文件夹下的index.html&#xff09;查看效果 hex…

智警杯备赛--数据库管理与优化及数据库对象创建与管理

sql操作 插入数据 如果要操作数据表中的数据&#xff0c;首先应该确保表中存在数据。没有插入数据之前的表只是一张空表&#xff0c;需要使用insert语句向表中插入数据。插入数据有4种不同的方式&#xff1a;为所有字段插入数据、为指定字段插入数据、同时插入多条数据以及插…

【LangChain】LangChain2-基础概念P1-输入控制与输出解析

欢迎来到啾啾的博客&#x1f431;。 记录学习点滴。分享工作思考和实用技巧&#xff0c;偶尔也分享一些杂谈&#x1f4ac;。 有很多很多不足的地方&#xff0c;欢迎评论交流&#xff0c;感谢您的阅读和评论&#x1f604;。 目录 引言基础代码LangChain python官方文档 输出可控…

在windows环境下安装Nmap并使用

Date: 2025-05-29 19:46:20 author: lijianzhan Nmap&#xff08;简称&#xff1a;Network Mapper&#xff09;是一款开源的网络扫描和嗅探工具包&#xff0c;Nmap主要作用是可以检测目标主机是否在线、主机端口开放情况、检测主机运行的服务类型及版本信息、检测操作系统与设备…

为什么我开始用 Data.olllo 做数据处理了?

之前我写过一篇文章&#xff0c;讲的是为什么我要做一个能打开 100GB CSV 的工具&#xff0c;叫 Data.olllo。 结果没想到&#xff0c;这篇文章不少人留言、私信我&#xff0c;问了一个类似的问题&#xff1a; “打开是解决了&#xff0c;那你用这个工具到底能干嘛&#xff1f…

通过远程桌面连接Windows实例提示“出现身份验证错误,无法连接到本地安全机构”错误怎么办?

本文介绍通过远程桌面连接Windows实例提示“出现身份验证错误无法连接到本地安全机构”错误的解决方案。 问题现象 通过本地电脑内的远程桌面连接Windows实例提示“出现身份验证错误&#xff0c;无法连接到本地安全机构”错误。 问题原因 导致该问题的可能原因如下&#x…

Spring未能自动解决循环依赖的问题

有过经验的同学应该都知道Spring能够自动解决循环依赖的问题&#xff0c;依靠的是它为单例池提供的三级缓存。如果你还不清楚三级缓存具体是怎么个解法的话&#xff0c;可以看一下这篇文章【图文详解】Spring是如何解决循环依赖的&#xff1f; 本文中的问题来源于我在开发项目…

RV1126 FFMPEG推流器理解

一.FFMPEG概念 概念&#xff1a;FFMPEG是一种音视频推流工具&#xff0c;把RV1126编码的视频&#xff0c;通过FFMPEG推流到流媒体服务器上&#xff0c;让大家都能访问和观看。为什么RV1126 编码的视频码流要利用 FFMPEG 框架推送到流媒体服务器&#xff0c;之前通过终端ffplay…

TeleAI发布TeleChat2.5及T1正式版,双双开源上线魔乐社区!

5月12日&#xff0c;中国电信开源TeleChat系列四个模型&#xff0c;涵盖复杂推理和通用问答的多个尺寸模型&#xff0c;包括TeleChat-T1-35B、TeleChat-T1-115B、TeleChat2.5-35B和TeleChat2.5-115B&#xff0c;实测模型性能均有显著的性能效果。TeleChat系列模型基于昇思MindS…

TMS320F28388D使用sysconfig配置IPC

第1章 配置IPC底层代码 使用IPC的动机&#xff1a; 我计划我的项目中要使用RS485&#xff0c;CANFD通信和EtherCAT通信&#xff0c;由于通信种类较多&#xff0c;而对于电机控制来说大部分数据都是重复的&#xff0c;并且有些数据可以很久才改变一次&#xff0c;所以我计划使…

Linux的线程同步

一、互斥锁&#xff08;互斥量&#xff09; 互斥锁是一种特殊的变量&#xff0c;有上锁&#xff08;lock&#xff09;和解锁&#xff08;unlock&#xff09;两种状态。 当处于解锁状态时&#xff0c;线程想获取该互斥锁&#xff0c;就可以获取不被阻塞&#xff0c;互斥锁变为…