Skip to content
分类目录:

Prism介绍和使用

Post date:
Author:

使用Prism框架,可以为我们带来,IOC,Region,Navigation,Dialog,Binding,MvvM等特征

Application:

Add package:

<PackageReference Include=”Prism.Unity” />

//App.cs
public partial class App : PrismApplication
{
    protected override void OnStartup(StartupEventArgs e)
    {    
        base.OnStartup(e);//调用prism的Startup
    }
    protected override Window CreateShell()
    {
        return Container.Resolve<MainWindow>();
    }
    protected override void RegisterTypes(IContainerRegistry containerRegistry)
    {
        
    }
}

程序启动的生命周期:

App.xaml.cs中

App构造函数 -> OnStartup -> RegisterTypes -> ConfigureModuleCatalog -> CreateShell -> 初始化每个子模块-> OnInitialized->ShowWindow

IOC

//老版本使用ServiceLocator
//新版本使用这个API
//需要添加依赖:
// <PackageReference Include="Prism.Container.Extensions" Version="8.0.62" />
ContainerLocator.Container.Resolve<IEventAggregator>();

Region

Region定义

<Window x:Class="Regions.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" Height="350" Width="525">
    <Grid>
        //如果每次导航到此Region,则会复用或者替换原来的View
        <ContentControl prism:RegionManager.RegionName="ContentRegion" />
      	//如果导航到此Region,则新增或者复用View(因为该Region支持Items)
      	<TabControl prism:RegionManager.RegionName="ContentRegion" Margin="5"  />
      
    </Grid>
</Window>
public MainWindow(IRegionManager regionManager)
 {    
     //view discovery
     //程序执行到此句时,会先初始化ViewA和ViewA的ViewModel实例
     regionManager.RegisterViewWithRegion("ContentRegion", typeof(ViewA));  
 }

ViewRegister

我将View分为两种,一种是用于导航到Region区域的View,另外一种是一个Component,Component可以用于组成第一种的View,将一个大的View分解成很多Component,有助于减少View对应的Viewmodel的逻辑。

第一种的View需要显示的注册到Region或Navigation,并管理生命周期

第二种Component无需注册,直接在View中使用,初始化的时候Component不会被IOC管理,但是使用自动绑定时,对应的Component的ViewModel会在Prism的IOC中进行管理。

public partial class MainWindow : Window
{
    IContainerExtension _container;
    IRegionManager _regionManager;
    public MainWindow(IContainerExtension container, IRegionManager regionManager)
    {
        InitializeComponent();
        _container = container;
        _regionManager = regionManager;
    }
    
   private void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        // View injection
       // 采用这种方式AddView后,该Region会默认显示第一个添加的View
        _viewA = _container.Resolve<ViewA>();
        _viewB = _container.Resolve<ViewB>();
       _region = _regionManager.Regions["ContentRegion"];
       _region.Add(_viewA);
    _region.Add(_viewB);
    }
   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

public class ModuleAModule : IModule
{
    public void OnInitialized(IContainerProvider containerProvider)
    {
        
    }
    public void RegisterTypes(IContainerRegistry containerRegistry)
    {
        //采用此方式注册View,此时不会初始化ViewA及其ViewModel
        containerRegistry.RegisterForNavigation<ViewA>();
        containerRegistry.RegisterForNavigation<ViewB>();
    }
}
public class MainWindowViewModel : BindableBase
{
    private readonly IRegionManager _regionManager;    
    public DelegateCommand<string> NavigateCommand { get; private set; }
    public MainWindowViewModel(IRegionManager regionManager)
    {
        _regionManager = regionManager;
        NavigateCommand = new DelegateCommand<string>(Navigate);
    }
    private void Navigate(string navigatePath)
    {
        if (navigatePath != null)
            _regionManager.RequestNavigate("ContentRegion", navigatePath);
    }
}

Navigation时,如果目标的ViewModel没有实现INavigationAware接口,则每次Navigation到相同的View,会复用之前的View实例,不会再重新创建新的View和ViewModel。

Navigation with parameters:

var parameters = new NavigationParameters();
parameters.Add("person", personif (person != null)
_regionManager.RequestNavigate("PersonDetailsRegion", "PersonDetail", parameters);
public void OnNavigatedTo(NavigationContext navigationContext)
{
    var person = navigationContext.Parameters["person"] as Person;
    if (person != null)
        SelectedPerson = person;
}

public bool IsNavigationTarget(NavigationContext navigationContext)
{
    var person = navigationContext.Parameters["person"] as Person;
    if (person != null)
        return SelectedPerson != null && SelectedPerson.LastName == person.LastName;
    else
        return true;
}

Navigation Aware

public class ViewAViewModel : BindableBase, INavigationAware
{    
    public void OnNavigatedTo(NavigationContext navigationContext)
    {
        
    }

    //Navigation执行逻辑:
     如果是第一次Navigation到此View,会执行构造函数和INavigationAware的逻辑
     如果不是第一次Navigation到此View,则会先判断当前存在的Navigation的目标是不是目标View
     如果是,则复用此View,不会执行构造函数,
     如果不是,则重新创建一个View,会执行构造函数
    public bool IsNavigationTarget(NavigationContext navigationContext)
    {        
        return true;
    }
    public void OnNavigatedFrom(NavigationContext navigationContext)
    {
        
    }
}

Navigation callback:

从ViewA导航到ViewB成功后,ViewA的ViewModel中会收到Callback。

public class AViewModel:BindableBase{

    private void Navigate(string navigatePath)
    {
        if (navigatePath != null)
            _regionManager.RequestNavigate("ContentRegion", navigatePath, NavigationComplete);
    }

    private void NavigationComplete(NavigationResult result)
    {
        MessageBox.Show(String.Format("Navigation to {0} complete. ", result.Context.Uri));
    }
}

IConfirmNavigationRequest:INavigationAware

如果想在导航到View时,给用户确认,则ViewModel可以实现此接口

public class ViewAViewModel : BindableBase, IConfirmNavigationRequest
{    
    public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
    {
        bool result = true;
        if (MessageBox.Show("Do you to navigate?", "Navigate?", MessageBoxButton.YesNo) == MessageBoxResult.No)
            result = false;
        continuationCallback(result);
    }
    public bool IsNavigationTarget(NavigationContext navigationContext)
    {
        return true;
    }
    public void OnNavigatedFrom(NavigationContext navigationContext)
    {        
    }
    public void OnNavigatedTo(NavigationContext navigationContext)
    {        
    }
}

IRegionMemberLifetime

ViewModel中实现此接口,意味着Region中的View被其他View给替换时,是否还保留此View的实例。

Module

 public class ModuleAModule : IModule
 {
     public void OnInitialized(IContainerProvider containerProvider)
     {
         var regionManager = containerProvider.Resolve<IRegionManager>();
         regionManager.RegisterViewWithRegion("ContentRegion", typeof(ViewA));
     }
     public void RegisterTypes(IContainerRegistry containerRegistry)
     {         
     }
 }
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
    moduleCatalog.AddModule<ModuleA.ModuleAModule>();
	//Add Module with more custom
    var moduleAType = typeof(ModuleAModule);
    moduleCatalog.AddModule(new ModuleInfo()
    {
        ModuleName = moduleAType.Name,
        ModuleType = moduleAType.AssemblyQualifiedName,
        InitializationMode = InitializationMode.OnDemand
    });
    
}

ViewModel

ViewModelLocator:

根据默认的View和ViewModel的命名格式来自动设定DataContext

xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"

Binding & Command

在Prism中,通知Command是否可执行的方式:

注意:关联的属性需要是可通知属性。

方式1:

初始化Command时指定CanExecute函数,当相关的可通知属性发生变化时,调用DelegateCommand.RaiseCanExecuteChanged(), 则会通知Command检查CanExecute是否能用。

public class MainWindowViewModel : BindableBase
{
    private bool _isEnabled;
    public bool IsEnabled
    {
        get { return _isEnabled; }
        set
        {
            SetProperty(ref _isEnabled, value);
            ExecuteDelegateCommand.RaiseCanExecuteChanged();
        }
    }
    public DelegateCommand ExecuteDelegateCommand { get; private set; }
   
    public MainWindowViewModel()
    {
        ExecuteDelegateCommand = new DelegateCommand(Execute, CanExecute);       
    }
    private void Execute()
    {
        UpdateText = $"Updated: {DateTime.Now}";
    }
    
    private bool CanExecute()
    {
        return IsEnabled;
    }
}

方式2:

初始化Command的时候,使用ObservesProperty来监听属性变化,自动实现,属性变化->CanExecute的流程。

public bool IsEnabled
 {
     get { return _isEnabled; }
     set
     {
         SetProperty(ref _isEnabled, value);
     }
 }

DelegateCommandObservesProperty = 
		new DelegateCommand(Execute, CanExecute).ObservesProperty(() => IsEnabled);
public class MainWindowViewModel : BindableBase
{
    private bool _isEnabled;
    public bool IsEnabled
    {
        get { return _isEnabled; }
        set
        {
            SetProperty(ref _isEnabled, value);
            ExecuteDelegateCommand.RaiseCanExecuteChanged();
        }
    }
    public DelegateCommand ExecuteDelegateCommand { get; private set; }
    public DelegateCommand<string> ExecuteGenericDelegateCommand { get; private set; }        
    public DelegateCommand DelegateCommandObservesProperty { get; private set; }
    public DelegateCommand DelegateCommandObservesCanExecute { get; private set; }
    public MainWindowViewModel()
    {
        ExecuteDelegateCommand = new DelegateCommand(Execute, CanExecute);
        DelegateCommandObservesProperty = new DelegateCommand(Execute, CanExecute).ObservesProperty(() => IsEnabled);
        ExecuteGenericDelegateCommand = new DelegateCommand<string>(ExecuteGeneric).ObservesCanExecute(() => IsEnabled);
    }
    private void Execute()
    {
        UpdateText = $"Updated: {DateTime.Now}";
    }
    private void ExecuteGeneric(string parameter)
    {
        //UpdateText = parameter;
    }
    private bool CanExecute()
    {
        return IsEnabled;
    }
}

Event To Command

<Window x:Class="UsingInvokeCommandAction.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
        xmlns:prism="http://prismlibrary.com/"
        prism:ViewModelLocator.AutoWireViewModel="True"
        Title="{Binding Title}" Height="350" Width="525">
    <Grid>
        <ListBox ItemsSource="{Binding Items}" SelectionMode="Single">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="SelectionChanged">
                    <prism:InvokeCommandAction Command="{Binding SelectedCommand}"
                                               CommandParameter="{Binding MyParameter}" />
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </ListBox>
    </Grid>
</Window>

给ListView的单元格增加双击事件

<ListView Grid.Row="1"
      ItemsSource="{Binding FacFolderItems}"
      Background="Transparent"
      HorizontalAlignment="Left">    
    <ListView.View>
        <GridView>                            
            <GridViewColumn  >
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <StackPanel.InputBindings>
                                <MouseBinding MouseAction="LeftDoubleClick" 
                                  Command="{
                                  Binding DataContext.OpenFavDir
                                  ,RelativeSource=
                                  {RelativeSource FindAncestor,
                                  AncestorType=UserControl}}" 
                                  CommandParameter="{Binding}"/>
                            </StackPanel.InputBindings>
                            <Image 
                              Source="/Resources/folder.png" 
                              Width="15" Height="15" 
                              VerticalAlignment="Center" Margin="5,0" />
                            <TextBlock Text="{Binding DirectoryPath}" 
                              VerticalAlignment="Center" />
                        </StackPanel>
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>            
        </GridView>
    </ListView.View>
</ListView>

EventAggregator

使用观察者模式,管理事件的发布和订阅,用于不同模块之间的消息通信,减少耦合和依赖。

<PackageReference Include=”Prism.Core” />

public class MessageSentEvent : PubSubEvent<string>
{
}
public class MessageViewModel : BindableBase
{
    IEventAggregator _ea;
    private string _message = "Message to Send";
    public string Message
    {
        get { return _message; }
        set { SetProperty(ref _message, value); }
    
    public DelegateCommand SendMessageCommand { get; private set; }
    public MessageViewModel(IEventAggregator ea)
    {
        _ea = ea;
        SendMessageCommand = new DelegateCommand(SendMessage);
    
    private void SendMessage()
    {
        _ea.GetEvent<MessageSentEvent>().Publish(Message);
    }
}
public class MessageListViewModel : BindableBase
{
    IEventAggregator _ea;
    private ObservableCollection<string> _messages;
    public ObservableCollection<string> Messages
    {
        get { return _messages; }
        set { SetProperty(ref _messages, value); }
    }
    public MessageListViewModel(IEventAggregator ea)
    {
        _ea = ea;
        Messages = new ObservableCollection<string>();
        //Simle subscribe event
        _ea.GetEvent<MessageSentEvent>().Subscribe(MessageReceived);

        //Subscribe with filter:
        _ea.GetEvent<MessageSentEvent>()
        	.Subscribe(MessageReceived, 
         			ThreadOption.PublisherThread,
            		false,
              		(filter) => filter.Contains("Brian"));
    }
    private void MessageReceived(string message)
    {
        Messages.Add(message);
    }
}

Dialog

定义Dialog View

<UserControl x:Class="StylingDialog.Views.NotificationDialog"
             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"
             Width="300" Height="150">
    <prism:Dialog.WindowStyle>
        <Style TargetType="Window">
            <Setter Property="prism:Dialog.WindowStartupLocation" Value="CenterScreen" />
            <Setter Property="ResizeMode" Value="NoResize"/>
            <Setter Property="ShowInTaskbar" Value="False"/>
            <Setter Property="SizeToContent" Value="WidthAndHeight"/>
        </Style>
    </prism:Dialog.WindowStyle>
    <Grid x:Name="LayoutRoot" Margin="5">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <TextBlock Text="{Binding Message}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="0" TextWrapping="Wrap" />
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0" Grid.Row="1" >
            <Button Command="{Binding CloseDialogCommand}" CommandParameter="true" Content="OK" Width="75" Height="25" IsDefault="True" />
            <Button Command="{Binding CloseDialogCommand}" CommandParameter="false" Content="Cancel" Width="75" Height="25" Margin="10,0,0,0" IsCancel="True" />
        </StackPanel>
    </Grid>
</UserControl>

DialogViewModel需要实现IDialogAware接口

这个接口提供了:

设置窗体的Title的属性

提供了一个DialogCloseListener RequestClose,

可以通过调用此来通知prism关闭此弹窗,

关闭弹窗时需要指定DialogResult,new DialogResult(ButtonResult.Cancel),用来表示,弹窗关闭后,是否需要继续处理或者Cancel。

CanCloseDialog(): 实现此方法,指示是否可以关闭此弹窗

OnDialogClosed(): 实现此方法,可以在关闭弹窗的时候,添加一些参数

OnDialogOpened():实现此方法,可以在弹窗打开的时候,获取传入的一些参数。

生命周期为:

ShowDialog -> OnDialogOpened ->RequestClose ->CanCloseDialog -> OnDialogClosed

定义Dialog ViewModel

public partial class NotificationDialogViewModel : ObservableObject, IDialogAware
{
    [ObservableProperty]
    private string _Title;

    [ObservableProperty]
    private string _Message;

    public DialogCloseListener RequestClose { get; }

    public bool CanCloseDialog()
    {
        return true;
    }

    public void OnDialogClosed()
    {
    }

    public void OnDialogOpened(IDialogParameters parameters)
    {
        var title = parameters.GetValue<string>("Title");
        Title = title;
        var message = parameters.GetValue<string>("Message");
        Message = message;
    }

    [RelayCommand]
    private void CloseDialog(string value)
    {
        if (value.Equals("true"))
        {
            RequestClose.Invoke(new DialogResult(ButtonResult.OK));
            //带参数
            RequestClose.Invoke(new DialogResult(ButtonResult.OK) { Parameters = parameters });
        }
    }
}

设置Dialog的window样式:

在定义Dialog的地方添加下方的Style

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

Prism中IDialogWindow只能设置一种,所以使用框架里的ShowDialog方式,所有的Window都是同一个,如果想在Window中设置某些Window事件,则会影响所有的窗口,可以考虑自定义实现一个新的DialogWindow并注册到IOC中。

public interface IPopupDialogWindow : IDialogWindow
{
}

新建一个WPF window,并实现接口IPopupDialogWindow

新增一个DialogService,并继承自Prism中的DialogService

public class MyDialogService:DialogService{

    protected virtual TDialogWindow CreateDialogWindow<TDialogWindow>() where TDialogWindow : IDialogWindow
{
    return containerExtension.Resolve<TDialogWindow>();
}

protected virtual void ConfigureDialogWindowEventsWrapper(IDialogWindow dialogWindow, Action<IDialogResult> callback)
{
    BindingFlags eFlags = BindingFlags.Instance | BindingFlags.Public |
    BindingFlags.NonPublic;
    var ConfigureDialogWindowEventsMethod = this.GetType().BaseType.GetMethod("ConfigureDialogWindowEvents", eFlags);
    ConfigureDialogWindowEventsMethod.Invoke(this, new object[] { dialogWindow, callback });
}

protected virtual void ConfigureDialogWindowContentWrapper(string viewName, IDialogWindow window, IDialogParameters parameters)
{
    BindingFlags eFlags = BindingFlags.Instance | BindingFlags.Public |
    BindingFlags.NonPublic;
    var ConfigureDialogWindowContentMethod = this.GetType().BaseType.GetMethod("ConfigureDialogWindowContent", eFlags);

    ConfigureDialogWindowContentMethod.Invoke(this, new object[] { viewName, window, parameters });
}

public void ShowDialog<TDialogWindow>(string viewName, IDialogParameters parameters, Action<IDialogResult> callback) where TDialogWindow : IDialogWindow
{
    IDialogWindow dialogWindow = CreateDialogWindow<TDialogWindow>();

    ConfigureDialogWindowEventsWrapper(dialogWindow, callback);
    ConfigureDialogWindowContentWrapper(viewName, dialogWindow, parameters);

    dialogWindow.ShowDialog();
}
}

在Prism的IOC中注册Dialog

public partial class App
{
    protected override Window CreateShell()
    {
        return Container.Resolve<MainWindow>();
    }
    protected override void RegisterTypes(IContainerRegistry containerRegistry)
    {
        containerRegistry.RegisterDialog<NotificationDialog, NotificationDialogViewModel>();
    }
}

使用Dialog

var parameters = new DialogParameters();
parameters.Add("Key","Value");
//方式1:
_dialogService.ShowDialog("NotificationDialog", parameters, r =>
 {
     if (r.Result == ButtonResult.None)
         Title = "Result is None";
     else if (r.Result == ButtonResult.OK)
         Title = "Result is OK";
     else if (r.Result == ButtonResult.Cancel)
         Title = "Result is Cancel";
     else
         Title = "I Don't know what you did!?";
     //获取弹窗返回时的参数:
     r.Parameters.Get
 });
// 方式2:
var dialogResult=_dialogService.ShowDialog("NotificationDialog", parameters, r => r);
if(dialogResult == ButtonResult.Ok){
    ...
}
豫ICP备2021008859号-1