WPF prism

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.pswp.cn/pingmian/82971.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

循序渐进PersistentVolumes与PersistentVolumeClaim

文章目录 静态配置&#xff08;Static Provisioning&#xff09;&#xff1a;Persistent volume(PV)Local 示例&#xff1a;NFS 示例&#xff1a;检查pvPV 的常见状态说明Persistent volume claim(PVC)1. local PVC示例:2.NFS PVC示例:3. 检查PVC: 挂载静态供应卷验证静态供应卷…

【连接器专题】SD卡座规格书审查需要审哪些方面?

在审查SD卡座规格书时,我们需要考虑哪些方面? 首先在拿到一份SD卡座的详细规格书时,一般供应商给到的规格书中包括了一些基础信息、产品图纸信息、技术参数信息,同时有些供应商会给出产品可靠性测试报告。因此我们会从这几个要素去看规格书。 基础信息 基础信息一般会给变更…

投稿 IEEE Transactions on Knowledge and Data Engineering 注意事项

投稿 IEEE Transactions on Knowledge and Data Engineering 注意事项 要IEEE overleaf 模板私信,我直接给我自己论文,便于编辑 已经投稿完成了,有一些小坑 准备工作 注册IEEE账户:若没有IEEE账户,需前往IEEE官网注册。注册成功后,可用于登录投稿系统。现在新的系统,…

JS入门——三种输入方式

JS入门——三种输入方式 一、方式一&#xff1a;直接在警告框弹出(window可以省略) <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title></head><body><script><!-- 方式一直接在警告框弹…

WordPress免费网站模板下载

大背景图免费wordpress建站模板 这个wordpress模板设计以简约和专业为主题&#xff0c;旨在为用户提供清晰、直观的浏览体验。以下是对其风格、布局和设计理念的详细介绍&#xff1a; 风格 简约现代&#xff1a;整体设计采用简约风格&#xff0c;使用了大量的白色和灰色调&am…

AUTOSAR CP全新系统化培训上线!从底层到应用,三步阶梯,五大学习维度构建完整知识体系

AUTOSAR组织 AUTOSAR官方全新推出「AUTOSAR CP全栈赋能计划」&#xff0c;从架构全景到模块细节&#xff0c;自底向上、由浅入深&#xff0c;覆盖MCAL至SWC全层级&#xff0c;融合通信、诊断、安全等六大核心Feature&#xff0c;带您穿透复杂理论&#xff0c;直击AUTOSAR开发本…

Java网络编程与Socket安全权限详解

Socket安全权限控制 Java通过java.net.SocketPermission类实现对网络套接字访问的细粒度控制。该权限管理机制通常在Java策略文件中配置,其标准授权语法格式如下: grant {permission java.net.SocketPermission"target", "actions"; };目标主机与端口规…

基于本地化大模型的智能编程助手全栈实践:从模型部署到IDE深度集成学习心得

近年来&#xff0c;随着ChatGPT、Copilot等AI编程工具的爆发式增长&#xff0c;开发者生产力获得了前所未有的提升。然而&#xff0c;云服务的延迟、隐私顾虑及API调用成本促使我探索一种更自主可控的方案&#xff1a;基于开源大模型构建本地化智能编程助手。本文将分享我构建本…

视频监控汇聚平台EasyCVR安防小知识:如何通过视频融合平台解决信息孤岛问题?

一、项目背景与需求分析​ 随着数字化技术发展与网络带宽升级&#xff0c;视频技术应用场景不断拓展&#xff0c;视频监控、记录仪等多样化产品构建起庞大体系。但这些独立系统彼此割裂&#xff0c;形成信息孤岛。 在系统集成项目中&#xff0c;视频系统深度融合已成必然趋势…

如何在 Windows 和 Mac 上擦拭和清洁希捷外置硬盘

希捷外置硬盘广泛用于存储目的&#xff0c;但有时您可能出于多种目的需要擦除或清洁希捷外置硬盘&#xff0c;例如转售、重复使用、捐赠等。为了释放硬盘上的存储空间或确保没有人可以从硬盘中恢复您的信息&#xff0c;擦除硬盘是必要的步骤。无论您使用的是 Windows 还是 Mac&…

SAP saml2 元数据 HTTP 错误

使⽤事务 SAML2 或 SAML2_IDP 在 ABAP 系统中配置 SAML 2.0 时&#xff0c; Web 页⾯返回 403 已禁⽌、 404 未找到 或 500 服务器内部错误。 在事务 SAML2 中下载元数据时&#xff0c; ⽹页返回 403 已禁⽌、 404 未找到或 500 服务器内部错误。 在事务 SAML2_IDP 中下载…

powershell 中 invoke-expression 报错解决

打开powershell就出现这个报错&#xff1a; 网上搜了也没有很好的解决办法&#xff0c;抱着一点点期待&#xff0c;问了豆包 根据豆包的指示&#xff0c;在终端执行以下 几个命令&#xff0c;报错解决了&#xff08;开心万岁&#xff09; # 移除多余的引号和空路径 $pathArra…

简单说一说Modern ABAP这个概念

Modern ABAP 是 SAP近些年来提出的一个概念&#xff0c;指的是在传统 ABAP (Advanced Business Application Programming) 的基础上&#xff0c;结合新技术和现代化编程理念进行改进和优化&#xff0c;旨在使 ABAP 更适应云计算、数据驱动业务以及开发效率提升的需求。 这一概…

kafka 常用知识点

文章目录 前言kafka 常用知识点1. kafka 概念2. 消息共享和广播3. 分区和副本数量奇偶数 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说点赞的人每天的运气都不会太差&#xff0…

5G RedCap是什么-与标准5G的区别及支持路由器推荐

技术背景与重要性 从智能穿戴到工业传感器&#xff0c;物联网设备种类繁多&#xff0c;但并非所有设备都需要标准5G的全部功能。为满足这些中端应用的需求&#xff0c;3GPP在Release 17中引入了5G RedCap&#xff08;Reduced Capability&#xff09;&#xff0c;也称为5G NR-L…

C++构造函数与初始化全面指南:从基础到高级实践

C构造函数与初始化全面指南&#xff1a;从基础到高级实践 1. 构造函数基础概念 构造函数是C中一种特殊的成员函数&#xff0c;它在创建类对象时自动调用&#xff0c;用于初始化对象的数据成员。构造函数的核心特点包括&#xff1a; 与类同名无返回类型&#xff08;连void都没…

大模型长对话中上下文无法承载全部历史,如何压缩或提取重点

在人工智能技术迅猛发展的今天,大模型已经渗透到我们生活的方方面面,尤其是自然语言处理领域,简直是掀起了一场革命。从智能客服到个人助手,从在线教育到心理咨询,大模型驱动的对话系统正在以一种前所未有的方式改变我们与机器的互动模式。特别是那些能够进行多轮对话、甚…

ubuntu20.04安装教程(图文详解)

Ubuntu 24.04 LTS&#xff0c;代号 Noble Numbat&#xff0c;于 2024 年 4 月 25 日发布&#xff0c;现在可以从 Ubuntu 官方网站及其镜像下载。此版本将在 2029 年 4 月之前接收为期五年的官方安全和维护更新。 关于 Ubuntu 24.04 LTS 的一些关键点&#xff1a; 发布日期&am…

数据结构之队列:原理与应用

一、基本原理 队列是一种特殊的线性表队列是一个有序表(可以用数组或链表实现)遵循“先来先服务”的原则&#xff0c;它只允许在表的前端&#xff08;队头&#xff09;进行删除操作&#xff0c;在表的后端&#xff08;队尾&#xff09;进行插入操作 (一) 核心操作 入队&…

Ubuntu 安装 Miniconda 及配置国内镜像源完整指南

目录 Miniconda 安装Conda 镜像源配置Pip 镜像源配置验证配置基本使用常见问题 1. Miniconda 安装 1.1 下载安装脚本 wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh1.2 执行安装 bash Miniconda3-latest-Linux-x86_64.sh按回车查看许可协议…