WPF Prism框架

一、关于Prism框架

Prism.Core:【Prism.dll】实现MVVM的核心功能,属于一个与平台无关的项目

Prism.Wpf:【Prism.Wpf】包含了DialogService,Region,Module,Navigation,其他的一些WPF的功能

Prism.Unity:【Prism.Unity.Wpf】,IOC容器

Prism.Unity=>Prism.Wpf=>Prism.Core

二、Prism框架的对象

2.1 数据绑定

2.2 行为操作

2.3 触发检查方式

2.4 行为命令

三、Prism框架的初始化

3.1 Bootstrapper

3.2 App初始化

四、ViewModelLocator

ViewModelLocator 用于把 ViewModel 实例绑定到 View 的 DataContext.

解析ViewModel

当设置 ViewModelLocator.AutoWireViewModel = true 时,ViewModelLocationProvider 类会通过调用 AutoWireViewModelChanged 方法来解析 ViewModel 实例。解析规则如下:

先解析用户通过 ViewModelLocationProvider.Register 方法注册的 ViewModel
如果 1 失败,则通过基本约定规则进行解析ViewModel
View 和 ViewModel 位于同一个程序集中
View 在 .Views 子命名空间中,ViewModel 在 .ViewModels 子命名空间中
ViewModel名称与 View 对应并以 “ViewModel” 结尾

自定义命名解析规则

如果不想遵循 ViewModelLocator 默认的命名约定,可以自定义约定,ViewModelLocator 会根据你自己定义的约定将 View 关联到 ViewModel.

protected override void ConfigureViewModelLocator(){base.ConfigureViewModelLocator();// 设置自定义解决规则(统一设定)ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver((viewType) =>{// 1. 获取到解析的视图全名称string viewName = viewType.FullName;// 2. 根据全名称替换为对应的ViewModel名称string vmName = viewName.Replace(".Views.", ".CustomerViewModels.");// 获取类型对应程序名称var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;// 拼接ViewModel名称var viewModelName = $"{vmName}ViewModel, {viewAssemblyName}";return Type.GetType(viewModelName);});// 特殊不规则的直接注册[其他方式不讲,这个效率最高]ViewModelLocationProvider.Register<Custom, CustomWindowVM>();}
明确指定ViewModel

有时候,不需要 ViewModelLocator 尝试根据自定义命名约定来解析 ViewModel,可以直接手动指定将 ViewModel 指定给对应的 View.

// Type / Type
ViewModelLocationProvider.Register(typeof(MainWindow).ToString(), typeof(CustomViewModel));
// Type / Factory
ViewModelLocationProvider.Register(typeof(MainWindow).ToString(), () => Container.Resolve<CustomViewModel>());
// 通用工厂
ViewModelLocationProvider.Register<MainWindow>(() => Container.Resolve<CustomViewModel>());
// 通用类型
ViewModelLocationProvider.Register<MainWindow, CustomViewModel>();

这种通过 ViewModelLocationProvider.Register 手动指定 ViewModel 的方式更快,效率更高。

自定义ViewModel解析方式

默认情况,ViewModelLocator 使用选择的DI容器来解析 ViewModel。如果需要自定义解析方式或者更改解析器,可以使用 ViewModelLocationProvider.SetDefaultViewModelFactory方法。

protected override void ConfigureViewModelLocator()
{base.ConfigureViewModelLocator();ViewModelLocationProvider.SetDefaultViewModelFactory((view, viewModelType) =>{switch (view){case Window window://your logicbreak;case UserControl userControl://your logicbreak;}return MyAwesomeNewContainer.Resolve(someNewType);});
}

五、EventAggregator

步骤:

  1. 继承PubSubEvent<T>创建自定义事件类型
  2. 创建事件发送者
  3. 创建事件接收者,并订阅指定的事件

1. 事件聚合器

Prism 提供了一种机制,可以实现应用程序中松散耦合组件之间的通信。这种机制基于事件聚合器服务,允许发布者和订阅者通过事件进行通信,并且彼此之间仍然没有直接引用。

事件聚合器提供多播发布/订阅功能。这意味着可以有多个发布者引发相同的事件,并且可以有多个订阅者监听相同的事件。

通过事件聚合器服务可以使用IEventAggregator接口获取到事件聚合器。事件聚合器负责定位或构建事件,并在系统中保存事件的集合。首次访问一个事件时,如果该事件尚未构建,则构建。

2. 事件类型

PubSubEventPrism中对积累EventBase的唯一实现。此类维护订阅者列表并向订阅者发送事件。PubSubEvent是一个泛型类,在使用时需要指定具体类型以进行特化。

3. 发布

发布者通过事件聚合器服务获取到EventAggregator,并调用Publish方法来触发事件。

4. 订阅

订阅者通过事件聚合器服务获取到EventAggregator,并调用Subscribe方法进行注册。之后,注册的事件被触发是,通过参数指定委托进行相应。

4.1 订阅类型
  • ThreadOption.PublisherThread 与发布者使用相同线程,默认方式
  • ThreadOption.BackgroundThread 使用线程池线程
  • ThreadOption.UIThread 使用UI线程
4.2 事件过滤

订阅者在注册事件订阅是可以通过参数指定过滤的事件条件,只有满足条件的事件才能被订阅者真正使用。过滤通过System.Predicate<T>委托进行。

4.3 强引用订阅

默认情况下使用弱引用方式。强引用调用能够加速事件的传递,但必须手动的取消订阅。

5. 样例

5.1 创建一个Event类
/// <summary>
/// 定义一个事件类
/// </summary>
public class MessageEvent : PubSubEvent<object>
{
}
5.2 事件聚合对象的引入
  • 窗口中使用,直接注入依赖
  public MainWindow(IEventAggregator eventAggregator){InitializeComponent();}private void EventMessage(object arg){}
  • ViewModel中使用,二种方式
  //1、 通过构造函数注入// 注入一个IEventAggregatorIEventAggregator _eventAggregator;public MainWindowViewModel(IEventAggregator eventAggregator)//2、直接注入IOC容器对象public MainWindowViewModel(IUnityContainer unityContainer){_eventAggregator = unityContainer.Resolve<IEventAggregator>();}
5.3 发布事件(窗体或者ViewModel写法完全一样),与MvvmToolkit的Messenger基本相同
_eventAggregator.GetEvent<MessageEvent>().Publish("Hello Event");
5.4 订阅事件
_eventAggregator.GetEvent<MessageEvent>().Subscribe(EventMessage);
5.5 订阅的事件过滤
_eventAggregator.GetEvent<MessageEvent>().Subscribe(EventMessage, ThreadOption.PublisherThread, false,filter => filter.ToString().Contains("abc"));
5.6 指定线程运行
  • ThreadOption.PublisherThread 与发布者使用相同线程,默认方式
  • ThreadOption.BackgroundThread 使用线程池线程
  • ThreadOption.UIThread 使用UI线程

订阅的事件指定,订阅者在那个线程中处理

//Action<TPayload> action, ThreadOption threadOption, bool keepSubscriberReferenceAlive, Predicate<TPayload> filter
// 通过ThreadOption.UIThread来控制Actin在哪种线程(主线程、后台线程)执行
// bool keepSubscriberReferenceAlive,默认值是false
_eventAggregator.GetEvent<MessageEvent>().Subscribe(EventMessage, ThreadOption.PublisherThread);
验证

xaml文件

<Window x:Class="PrismEventAggregator.Views.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:PrismEventAggregator.Views"mc:Ignorable="d" FontSize="20"Title="MainWindow" Height="450" Width="800"><Grid><StackPanel><TextBlock Text="{Binding Hello}"/><Button Content="发送" Command="{Binding BtnCommand}"/></StackPanel></Grid>
</Window>

cs文件代码


public ICommand BtnCommand{get => new DelegateCommand(() =>{Task.Run(() =>{System.Diagnostics.Debug.WriteLine("按钮命令ID:");System.Diagnostics.Debug.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);// 命令里做事件发布_eventAggregator.GetEvent<MessageEvent>().Publish("Hello Event");});});}

订阅者代码

_eventAggregator.GetEvent<MessageEvent>().Subscribe(EventMessage, ThreadOption.PublisherThread);
private void EventMessage(object arg){System.Diagnostics.Debug.WriteLine($"事件执行的Id:{System.Threading.Thread.CurrentThread.ManagedThreadId}");// 操作UI、对象的时候,必须是UI线程处理// 判断逻辑在哪个线程,能不能在后台执行,如能,不用管// 如不能在后台执行,必须在UI执行,必须加UIThread// 必须在后台线程执行,}
5.7 强引用设置
// 后面参数设置为true就是强引用,默认为false弱引用
_eventAggregator.GetEvent<MessageEvent>().Subscribe(EventMessage, ThreadOption.PublisherThread, true);
5.8 注销订阅【强引用后必须手动注销】
_eventAggregator.GetEvent<MessageEvent>().Unsubscribe(EventMessage);

六、弹出窗口

一、什么是Dialog

对话框实际上是我们应用程序经常用到得一个功能,类如:Show、Show Dialog。可以弹出一个我们指定得窗口,仅此而已那么在Prism当中,Dialog指的什么?

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

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

二、创建Dialog

Dialog其实也是一组用户控件,我们可以创建一个子模块,然后不需要继承IModule,继承了也没有关系,只要不加载该子模块就行,这样我们可以把他当作对话框或者子模块使用,而不需要改动太多代码。值得一提的是,默认情况下这个控件会嵌入到窗体中弹出,所以我们可以定义弹出窗体的一些属性:

<prism:Dialog.WindowStyle><Style TargetType="Window"><Setter Property="prism:Dialog.WindowStartupLocation" Value="CenterOwner" /><Setter Property="WindowChrome.WindowChrome"><Setter.Value><WindowChrome CaptionHeight="0" ResizeBorderThickness="5" /></Setter.Value></Setter><Setter Property="WindowStyle" Value="None" /><Setter Property="AllowDrop" Value="True" /><Setter Property="BorderThickness" Value="0" /><Setter Property="ShowInTaskbar" Value="False" /><Setter Property="SizeToContent" Value="WidthAndHeight" /></Style>
</prism:Dialog.WindowStyle>

然后我们Dialog的ViewModel需要继承接口**IDialogAware**,这个接口我们在后面再细说。

三、注册Dialog

我们在App类的重写函数中注册对话框服务:

public partial class App : PrismApplication
{protected override Window CreateShell(){// 显示主窗口return Container.Resolve<MainWindow>();}/// <summary>/// 注册Dialog/// </summary>/// <param name="containerRegistry"></param>protected override void RegisterTypes(IContainerRegistry containerRegistry){// 注册一个Dialog的内容containerRegistry.RegisterDialog<DialogContentView>();// 注册一个Dialog父窗口containerRegistry.RegisterDialogWindow<DialogWindowBase>();containerRegistry.RegisterDialogWindow<DialogWindowBase>("win1");containerRegistry.RegisterDialogWindow<DialogWindow2>("win2");}
}

如果没有传递字符串参数,则DialogName默认为类名(View的类名)。也可以在注册的使用将View和ViewModel进行绑定,不过一般就是View的xaml代码中使用

prism:ViewModelLocator.AutoWireViewModel="True"

进行关联。

四、调用Dialog

要调用Dialog,我们需要拿到IDialogService接口的实现,而这个实现容器中已经注册过了,我们只需要在构造函数中注入即可。

// 构造函数方式注入对话服务
private IDialogService dialog;
public MainWindowViewModel(IDialogService dialogService)
{this.dialog = dialogService;
}
// 通过特性注入对话服务
[Dependency]
public IDialogService dialogService { get; set; }

然后调用这两个方法中的一个就可以打开对话框:

this.dialog.ShowDialog("DialogA");//模态对话框:父窗体禁止使用
this.dialog.Show("DialogA");//非模态对话框:父窗体可以继续使用// 打开对话框
//dialogService.ShowDialog("DialogContentView");
// 在指定的窗口中打开对话框
//dialogService.ShowDialog("DialogContentView", null, null, "win2");//---string name,
//IDialogParameters parameters,   打开窗口时要传递的参数
//Action<IDialogResult> callback, 打开窗口后的回调
//----string windowName
DialogParameters dialogParameters = new DialogParameters();
dialogParameters.Add("username", "gerry");
dialogService.ShowDialog("DialogContentView", dialogParameters, DoDialogResult, "win2");

五、IDialogService接口及其拓展

public interface IDialogService
{void Show(string name, IDialogParameters parameters, Action<IDialogResult> callback);void Show(string name, IDialogParameters parameters, Action<IDialogResult> callback, string windowName);void ShowDialog(string name, IDialogParameters parameters, Action<IDialogResult> callback);void ShowDialog(string name, IDialogParameters parameters, Action<IDialogResult> callback, string windowName);
}
//
// 摘要:
//     Extensions for the IDialogService
public static class IDialogServiceExtensions
{public static void Show(this IDialogService dialogService, string name);public static void Show(this IDialogService dialogService, string name, Action<IDialogResult> callback);public static void ShowDialog(this IDialogService dialogService, string name);public static void ShowDialog(this IDialogService dialogService, string name, Action<IDialogResult> callback);
}

接口的实现中,最多有四个参数:

  • name:指的是Dialog的name,由注册的时候指定,没有指定的时候默认为View类的类名
  • parameters:传递给对话框的参数
  • callback:对话框被关闭时的回调函数
  • windowName:注册对话框的父窗体名称
  • 拓展方法中最多有两个参数,其中省略了parameters和windowName。
private void OpenDialog()
{IDialogParameters parameters = new DialogParameters();parameters.Add("param1", "Hello");this.dialog.ShowDialog("DialogA", parameters, DialogCallback);
}private void DialogCallback(IDialogResult result)
{//对话框关闭之后的回调函数,可以在这解析结果。ButtonResult result1 = result.Result;var param = result.Parameters.GetValue<string>("param1");
}

六、IDialogAware

<Grid><StackPanel><TextBlock Text="{Binding Title,RelativeSource={RelativeSource AncestorType=Window}}"/><TextBlock Text="Hello Dialog"/><TextBlock Text="{Binding Value}"/><Button Content="Close" Command="{Binding CloseCommand}"/></StackPanel>
</Grid>

这个接口一般由ViewModel来继承。

//
// 摘要:
//     Interface that provides dialog functions and events to ViewModels.
public interface IDialogAware
{//// 摘要://     The title of the dialog that will show in the window title bar.string Title { get; }//// 摘要://     Instructs the Prism.Services.Dialogs.IDialogWindow to close the dialog.event Action<IDialogResult> RequestClose;//// 摘要://     Determines if the dialog can be closed.//// 返回结果://     If true the dialog can be closed. If false the dialog will not close.bool CanCloseDialog();//// 摘要://     Called when the dialog is closed.void OnDialogClosed();//// 摘要://     Called when the dialog is opened.//// 参数://   parameters://     The parameters passed to the dialog.void OnDialogOpened(IDialogParameters parameters);
}
  • Title:表示对话框窗体的标题。(打开对话框的时候,其实还是将该用户空间装载到了一个窗体中)
  • Action RequestClose:触发这个事件去关闭对话框,这个事件的订阅就是前面说到的当对话框关闭后的回调函数。
  • CanCloseDialog():如果返回true,则表示可以关闭对话框,否则不可以关闭对话框。(这时按钮不会灰调,但是点击无效。即使触发RequestClose也会无效)
  • OnDialogClosed:当对话框被关闭的时候触发这个方法(无论是点击关闭按钮还是触发事件)
  • OnDialogOpened(IDialogParameters parameters):当对话框被打开的时候触发这个方法,并且传递对话框参数进来。
public class DialogContentViewModel : BindableBase, IDialogAware
{#region IDialogAware的接口实现// 窗口标题public string Title => "弹窗 --> 子窗口";// // 关闭弹窗操作public event Action<IDialogResult> RequestClose;// 是否允许关闭弹窗public bool CanCloseDialog(){return true;}// 当窗口关闭的调用public void OnDialogClosed(){}// 当窗口打开的时候调用public void OnDialogOpened(IDialogParameters parameters){this.Value = parameters.GetValue<string>("username");}#endregionprivate string _value;public string Value{get { return _value; }set { SetProperty(ref _value, value); }}public ICommand CloseCommand{get;}public DialogContentViewModel(){CloseCommand = new DelegateCommand(() =>{// 关闭当前窗口DialogResult result = new DialogResult(ButtonResult.OK);//result.Result = ButtonResult.OK;RequestClose?.Invoke(result);});}
}

七、区域管理

一、什么是Region

在Prism当中,一个页面我们可以不再为其固定显示的内容,而这种概念变成了区域(Region)划分的概念。将页面显示的区域划分成N个Region,每一个Region将动态分配区域。它将负责承担我们的UI组件或者控件。

二、RegionManager类

RegionManager功能

  • 维护区域集合
  • 提供对区域的访问
  • 合成视图
  • 区域导航
  • 定义区域

三、使用RegionManager

首先,我们需要将MainWindow的构造函数传入IRegionManager参数:

public partial class MainWindow : Window
{public MainWindow(IRegionManager regionManager){InitializeComponent();}
}

注意:因为MainWindow对象是我们在App类中使用容器解析得到的,那么它需要的依赖IRegion也会自动被创建,不需要我们自己创建。

在定义视图与Region之间的映射关系之前我们需要先定义Region,定义Region有两种方法:

3.1、在xaml中
<Windowxmlns:prism="http://prismlibrary.com/"Title="Shell"><Grid><Grid.RowDefinitions><RowDefinition Height="0.2*" /><RowDefinition /></Grid.RowDefinitions><ContentControl Grid.Row="0" prism:RegionManager.RegionName="HeaderRegion" /></Grid>
</Window>
3.2、在cs文件中
<ContentControl x:Name="Header" Grid.Row="0" />
public MainWindow(IRegionManager regionManager)
{InitializeComponent();RegionManager.SetRegionName(Header, "HeaderRegion");
}

四、View Discovery和View Injection

在我们在MainWindow中定义了三个Region,同时定义了三个UserControl。

<Windowx:Class="PrismBlankAppCore.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="Shell"Width="525"Height="350"><Grid><Grid.RowDefinitions><RowDefinition Height="0.2*" /><RowDefinition /></Grid.RowDefinitions><ContentControl Grid.Row="0" prism:RegionManager.RegionName="HeaderRegion" /><Grid Grid.Row="1"><Grid.ColumnDefinitions><ColumnDefinition Width="0.4*" /><ColumnDefinition /></Grid.ColumnDefinitions><ContentControl Grid.Column="0" prism:RegionManager.RegionName="MenuRegion" /><ContentControl Grid.Column="1" prism:RegionManager.RegionName="ContentRegion" /></Grid></Grid>
</Window>

在Prism中有两种方式来定义视图与Region之间的映射关系——**View Discovery**和**View Injection**。

4.1View Discovery
public MainWindow(IRegionManager regionManager)
{InitializeComponent();//View DicoveryregionManager.RegisterViewWithRegion("HeaderRegion", typeof(HeaderView));regionManager.RegisterViewWithRegion("MenuRegion", typeof(MenuView));regionManager.RegisterViewWithRegion("ContentRegion", typeof(ContentView));
}
4.2View Injection
public partial class MainWindow : Window
{private IRegionManager regionManager;private IContainerExtension container;public MainWindow(IRegionManager regionManager, IContainerExtension container){InitializeComponent();//View Dicovery//regionManager.RegisterViewWithRegion("HeaderRegion", typeof(HeaderView));//regionManager.RegisterViewWithRegion("MenuRegion", typeof(MenuView));//regionManager.RegisterViewWithRegion("ContentRegion", typeof(ContentView));this.regionManager = regionManager;this.container = container;}private void Window_Loaded(object sender, RoutedEventArgs e){//View InjectionHeaderView headerView = container.Resolve<HeaderView>();regionManager.Regions["HeaderRegion"].Add(headerView);MenuView menuView = container.Resolve<MenuView>();regionManager.Regions["MenuRegion"].Add(menuView);ContentView contentView = container.Resolve<ContentView>();regionManager.Regions["ContentRegion"].Add(contentView);}
}

注入View的时候需要用到Add方法,还得提前将View对象创建好,所以在构造函数中我们需要注入IRegionManager和IContainerExtension的实现。

值得一提的是,在构造函数中IRegionManager对象还没有创建完成(应该是RegionName还没有创建完成),所以在Load函数中完成View Injection。如果需要View对象是单例的,可以提前在App类的重写函数中注册单例对象。

4.3Active和Deactivate

将App类修改为:View对象注册为单例对象。

protected override void RegisterTypes(IContainerRegistry containerRegistry)
{containerRegistry.RegisterSingleton<HeaderView>();containerRegistry.RegisterSingleton<MenuView>();containerRegistry.RegisterSingleton<ContentView>();
}

MainWindow增加了三个按钮:

<Window x:Class="PrismRegionManager.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="Shell"Width="525"Height="350" Loaded="Window_Loaded"><Grid><Grid.RowDefinitions><RowDefinition Height="0.2*" /><RowDefinition /><RowDefinition /></Grid.RowDefinitions><ContentControl Grid.Row="0" prism:RegionManager.RegionName="HeaderRegion" /><Grid Grid.Row="1"><Grid.ColumnDefinitions><ColumnDefinition Width="0.4*" /><ColumnDefinition /></Grid.ColumnDefinitions><ContentControl Grid.Column="0" prism:RegionManager.RegionName="MenuRegion" /><ContentControl Grid.Column="1" prism:RegionManager.RegionName="ContentRegion" /></Grid><UniformGrid Grid.Row="2" Columns="3"><ButtonWidth="80"Height="30"Content="Activate" Click="Activate"/><ButtonWidth="80"Height="30"Content="Refresh" Click="Refresh" /><ButtonWidth="80"Height="30"Content="Deactivate" Click="Deactivate" /></UniformGrid></Grid>
</Window>

生成对应事件

private void Activate(object sender, RoutedEventArgs e)
{//激活HeaderView headerView = container.Resolve<HeaderView>();regionManager.Regions["HeaderRegion"].Activate(headerView);MenuView menuView = container.Resolve<MenuView>();regionManager.Regions["MenuRegion"].Activate(menuView);ContentView contentView = container.Resolve<ContentView>();regionManager.Regions["ContentRegion"].Activate(contentView);
}private void Refresh(object sender, RoutedEventArgs e)
{//刷新  因为已经add过,所以需要先removeHeaderView headerView = container.Resolve<HeaderView>();regionManager.Regions["HeaderRegion"].Remove(headerView);MenuView menuView = container.Resolve<MenuView>();regionManager.Regions["MenuRegion"].Remove(menuView);ContentView contentView = container.Resolve<ContentView>();regionManager.Regions["ContentRegion"].Remove(contentView);regionManager.Regions["HeaderRegion"].Add(headerView);regionManager.Regions["MenuRegion"].Add(menuView);regionManager.Regions["ContentRegion"].Add(contentView);
}private void Deactivate(object sender, RoutedEventArgs e)
{//使无效HeaderView headerView = container.Resolve<HeaderView>();regionManager.Regions["HeaderRegion"].Deactivate(headerView);MenuView menuView = container.Resolve<MenuView>();regionManager.Regions["MenuRegion"].Deactivate(menuView);ContentView contentView = container.Resolve<ContentView>();regionManager.Regions["ContentRegion"].Deactivate(contentView);
}

五、RegionAdapters

Prism提供了许多内置得RegionAdapter:

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

下面我们创建一个一个基于StackPanel的自定义适配器。

5.1首先从创建一个继承于RegionAdapterBase的类。
public class StackPanelRegionAdapter : RegionAdapterBase<StackPanel>
{public StackPanelRegionAdapter(IRegionBehaviorFactory regionBehaviorFactory) : base(regionBehaviorFactory){}protected override void Adapt(IRegion region, StackPanel regionTarget){throw new System.NotImplementedException();}protected override IRegion CreateRegion(){throw new System.NotImplementedException();}
}
5.2实现重写的方法
public class StackPanelRegionAdapter : RegionAdapterBase<StackPanel>
{public StackPanelRegionAdapter(IRegionBehaviorFactory regionBehaviorFactory) : base(regionBehaviorFactory){}protected override void Adapt(IRegion region, StackPanel regionTarget){region.Views.CollectionChanged += (s, e) =>{if (e.Action == NotifyCollectionChangedAction.Add){foreach (FrameworkElement element in e.NewItems){regionTarget.Children.Add(element);}}else if (e.Action == NotifyCollectionChangedAction.Remove){foreach (FrameworkElement element in e.OldItems){regionTarget.Children.Remove(element);}}//handle other case};}protected override IRegion CreateRegion(){return new Region();}
}
5.3最后需要在App类中注册适配器
protected override void ConfigureRegionAdapterMappings(RegionAdapterMappings regionAdapterMappings)
{base.ConfigureRegionAdapterMappings(regionAdapterMappings);regionAdapterMappings.RegisterMapping(typeof(StackPanel), Container.Resolve<StackPanelRegionAdapter>());
}

八、复合命令

对于单个Command而言,只能触发单个对应的功能,而复合命令是Prism当中非常强大的功能,CompositeCommand简单来说就是父命令,它可以注册N个子命令。它有两个主要特征:

当父命令被激活的时候,它将触发所有的子命令
只要有一个子命令CanExecute=false,那么父命令将无法被激活。
使用的时候需要使用RegisterCommand()方法进行注册子命令。

九、模块化开发

本质上来说,对于一个应用程序而言,特定功能的所有View、Logic、Service等都可以独立存在。那么我们称每一个独立的功能我们都可以称之为模块。

通常情况下,我们在一个项目当中的结构是这样的:

所有的模块都在一个项目中,这使得应用程序当中,我们难以区分单独的模块,它们似乎变成了一个整体。所以,当我们开始考虑划分模块之间的关系的时候,并且采用新的模块化解决方案,它的结构将变成如下:

该项目包含了一个启动页,并且在启动页当中,我们划分好了对应的区域。这个时候,我们便可以灵活的配置我们的应用程序功能。使用Prism提供的强大功能,我们可以动态的加载应用程序模块,为指定的Region动态分配内容。

实例看代码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/475516.html

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

相关文章

STM32F103系统时钟配置

时钟是单片机运行的基础&#xff0c;时钟信号推动单片机内各个部分执行相应的指令。时钟系统就是CPU的脉搏&#xff0c;决定CPU速率&#xff0c;像人的心跳一样 只有有了心跳&#xff0c;人才能做其他的事情&#xff0c;而单片机有了时钟&#xff0c;才能够运行执行指令&#x…

2024年 Web3开发学习路线全指南

Web3是一个包含了很多领域的概念&#xff0c;不讨论币圈和链圈的划分&#xff0c;Web3包括有Defi、NFT、Game等基于区块链的Dapp应用的开发&#xff1b;也有VR、AR等追求视觉沉浸感的XR相关领域的开发&#xff1b;还有基于区块链底层架构或者协议的开发。 这篇文章给出的学习路…

CTF--php伪协议结合Base64绕过

Base64绕过 在ctf中&#xff0c;base64是比较常见的编码方式&#xff0c;在做题的时候发现自己对于base64的编码和解码规则不是很了解&#xff0c;并且恰好碰到了类似的题目&#xff0c;在翻阅了大佬的文章后记录一下&#xff0c;对于base64编码的学习和一个工具 base64编码是…

Linux 命令之 tar

文章目录 1 tar 命令介绍2 压缩与解压缩2.1 压缩2.2 解压 4 高级用法4.1 排除目录4.2 显示进度4.2.1 脚本解压缩4.2.2 命令解压缩4.2.3 压缩进度 1 tar 命令介绍 常见的压缩包有 .tar.gz、.tar.xz、.tar.bz2&#xff0c;以及 .rar、.zip、.7z 等压缩包。 常见的 tar 选项&#…

Jenkins修改LOGO

重启看的LOGO和登录页面左上角的LOGO 进入LOGO存在的目录 [roottest-server01 svgs]# pwd /opt/jenkins_data/war/images/svgs [roottest-server01 svgs]# ll logo.svg -rw-r--r-- 1 jenkins jenkins 29819 Oct 21 10:58 logo.svg #jenkins_data目录是我挂载到了/opt目录&…

【大模型】LLaMA: Open and Efficient Foundation Language Models

链接&#xff1a;https://arxiv.org/pdf/2302.13971 论文&#xff1a;LLaMA: Open and Efficient Foundation Language Models Introduction 规模和效果 7B to 65B&#xff0c;LLaMA-13B 超过 GPT-3 (175B)Motivation 如何最好地缩放特定训练计算预算的数据集和模型大小&…

vue添加LCD字体(液晶字体)数字美化,前端如何引用LCD字体液晶字体,如何转换?@font-face 如何使用?

文章目录 一、效果二、下载字体格式【[https://www.dafont.com/theme.php?cat302&text0123456789](https://www.dafont.com/theme.php?cat302&text0123456789)】三、下载后&#xff0c;解压后都是.ttf文件&#xff0c;在【[https://www.fontsquirrel.com/tools/webfo…

【大数据学习 | Spark】关于distinct算子

只有shuffle类的算子能够修改分区数量&#xff0c;这些算子不仅仅存在自己的功能&#xff0c;比如分组算子groupBy&#xff0c;它的功能是分组但是却可以修改分区。 而这里我们要讲的distinct算子也是一个shuffle类的算子。即可以修改分区。 scala> val arr Array(1,1,2,…

Qt桌面应用开发 第五天(常用控件 自定义控件)

目录 1.QPushButton和ToolButton 1.1QPushButton 1.2ToolButton 2.RadioButton和CheckBox 2.1RadioButton单选按钮 2.2CheckBox多选按钮 3.ListWidget 4.TreeWidget控件 5.TableWidget控件 6.Containers控件 6.1QScrollArea 6.2QToolBox 6.3QTabWidget 6.4QStacke…

Excel - VLOOKUP函数将指定列替换为字典值

背景&#xff1a;在根据各种复杂的口径导出报表数据时&#xff0c;因为关联的表较多、数据量较大&#xff0c;一行数据往往会存在三个以上的字典数据。 为了保证导出数据的效率&#xff0c;博主选择了导出字典code值后&#xff0c;在Excel中处理匹配字典值。在查询百度之后&am…

ctfshow-web入门-SSRF(web351-web360)

目录 1、web351 2、web352 3、web353 4、web354 5、web355 6、web356 7、web357 8、web358 9、web359 10、web360 1、web351 看到 curl_exec 函数&#xff0c;很典型的 SSRF 尝试使用 file 协议读文件&#xff1a; urlfile:///etc/passwd 成功读取到 /etc/passwd 同…

【vmware+ubuntu16.04】ROS学习_博物馆仿真克隆ROS-Academy-for-Beginners软件包处理依赖报错问题

首先安装git 进入终端&#xff0c;输入sudo apt-get install git 安装后&#xff0c;创建一个工作空间名为tutorial_ws&#xff0c; 输入 mkdir tutorial_ws#创建工作空间 cd tutorial_ws#进入 mkdir src cd src git clone https://github.com/DroidAITech/ROS-Academy-for-Be…

AI数字人视频小程序:引领未来互动新潮流

当下&#xff0c;随着人工智能技术的不断创新发展&#xff0c;各类AI系统已经成为了创新市场发展的重要力量&#xff0c;AI文案、AI数字人、AI视频等&#xff0c;为大众带来更加便捷的创作方式&#xff0c;AI成为了一个全新的风口&#xff0c;各种AI红利持续释放&#xff0c;市…

leetcode400第N位数字

代码 class Solution {public int findNthDigit(int n) {int base 1;//位数int weight 9;//权重while(n>(long)base*weight){//300n-base*weight;base;weight*10;}//n111 base3 weight900;n--;int res (int)Math.pow(10,base-1)n/base;int index n%base;return String…

MySQL扩展varchar字段长度能否Online DDL

目录 问题场景 Online DDL 简介 场景复现 DBdoctor快速识别 Online DDL 总结 问题场景 在MySQL数据库中&#xff0c;DDL变更可以通过两种算法实现&#xff1a;Copy算法和In-Place算法。Copy算法会复制整个表&#xff0c;这可能导致长时间的写入阻塞&#xff0c;从而严重影…

【WPF】Prism学习(九)

Prism Dependency Injection 1.Container Locator 1.1. Container Locator的引入&#xff1a; Container Locator是在Prism 8.0版本中新引入的一个特性。它的目的是为了帮助Prism框架摆脱对CommonServiceLocator的依赖&#xff0c;并解决一些必须回退到ServiceLocator模式的内…

.NET 9与C# 13革新:新数据类型与语法糖深度解析

记录&#xff08;Record&#xff09;类型 使用方式&#xff1a; public record Person(string FirstName, string LastName); 适用场景&#xff1a;当需要创建不可变的数据结构&#xff0c;且希望自动生成 GetHashCode 和 Equals 方法时。不适用场景&#xff1a;当数据结构需…

3C产品说明书电子化转变:用户体验、环保与商业机遇的共赢

在科技日新月异的当代社会&#xff0c;3C产品&#xff08;涵盖计算机类、通信类和消费类电子产品&#xff09;已成为我们日常生活中不可或缺的重要元素。与此同时&#xff0c;这些产品的配套说明书也经历了一场从纸质到电子化的深刻变革。这一转变不仅体现了技术的飞速进步&…

GIT 入门详解指南

前言&#xff1a; 注&#xff1a;本博客仅用于记录本人学习过程中对git的理解&#xff0c;仅供学习参考&#xff0c;如有异议请自行查资料求证 安装 使用git之前必须完成git的安装&#xff0c;Git 目前支持 Linux/Unix、Solaris、Mac和 Windows 平台上运行 git 安装教程 基本…

【数据结构】用四个例子来理解动态规划算法

1. 动态规划 动态规划&#xff08;Dynamic Programming, DP&#xff09;是一种通过将复杂问题分解为更小的子问题来求解的算法设计思想&#xff0c;一般用于求解具有最优子结构和重叠子问题性质的问题。动态规划的核心在于&#xff1a;&#xff08;1&#xff09;最优子结构--问…