Prism 框架
IDialogService 集成 MaterialDesign
需求:通过 Prism 的对话框服务来管理 MaterialDesign 的 DialogHost 功能,实现既有Prism的框架注入服务,又具备 MaterialDesign 的界面,同时保持 Prism 的默认对话框功能(可拖动移动)。
Prism 8.0 和 9.0 接口变动
Prism 8.0 到 9.0 的命名空间以及 Dialog的接口有变动,需要分别处理
接口 | Prism 8.0 | Prism 9.0 |
---|---|---|
IDialogService |
命名空间:Prism.Services.Dialogs
|
命名空间:Prism.Dialogs
|
IDialogAware | 命名空间:Prism.Dialogs
|
命名空间:Prism.Dialogs
|
集成过程
IDialogAware 接口,注意命名空间以及 RequestClose
在 9.0 中的变动
定义 DialogServiceExtensions
对 IDialogService
扩展,该脚本在8.0 和 9.0 中仅命名空间名称不一样(注意将 using Prism.Services.Dialogs;
改为 using Prism.Dialogs;
)。
public static class DialogServiceExtensions
{
public static void ShowDialogHost(this IDialogService dialogService, string name,
string windowName)
{
if (!(dialogService is MaterialDialogService materialDialogService))
throw new NullReferenceException("DialogService must be a MaterialDialogService");
materialDialogService.ShowDialogHost(name, windowName, null, null
);
}
/// <summary>
/// Shows a modal dialog using a <see cref="MaterialDesignThemes.Wpf.DialogHost"/>.
/// </summary>
/// <param name="dialogService"></param>
/// <param name="name">The name of the dialog to show.</param>
/// <param name="parameters">The parameters to pass to the dialog.</param>
/// <param name="callback">The action to perform when the dialog is closed.</param>
/// <exception cref="NullReferenceException">Thrown when the dialog service is not a MaterialDialogService</exception>
public static void ShowDialogHost(this IDialogService dialogService, string name, IDialogParameters parameters, Action<IDialogResult> callback)
{
if (!(dialogService is MaterialDialogService materialDialogService))
throw new NullReferenceException("DialogService must be a MaterialDialogService");
materialDialogService.ShowDialogHost(name, parameters, callback);
}
/// <summary>
/// Shows a modal dialog using a <see cref="MaterialDesignThemes.Wpf.DialogHost"/>.
/// </summary>
/// <param name="dialogService"></param>
/// <param name="name">The name of the dialog to show.</param>
/// <param name="parameters">The parameters to pass to the dialog.</param>
/// <param name="callback">The action to perform when the dialog is closed.</param>
/// <param name="windowName">The name of the <see cref="MaterialDesignThemes.Wpf.DialogHost"/> that will contain the dialog control</param>
/// <exception cref="NullReferenceException">Thrown when the dialog service is not a MaterialDialogService</exception>
public static void ShowDialogHost(this IDialogService dialogService, string name,
IDialogParameters parameters, Action<IDialogResult> callback, string windowName)
{
if (!(dialogService is MaterialDialogService materialDialogService))
throw new NullReferenceException("DialogService must be a MaterialDialogService");
materialDialogService.ShowDialogHost(name, windowName, parameters, callback);
}
}
定义 MaterialDialogService
8.0 和 9.0 定义不同:
Prism 8.0 定义 MaterialDialogService
Prism 9.0 定义 MaterialDialogService
在 App.xaml.cs
中注册
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
//other...
containerRegistry.Register<IDialogService, MaterialDialogService>();
}
前端容器
<UserControl x:Class="OESTS.Modules.Share.Views.MainLayout"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:core="clr-namespace:OESTS.Core;assembly=OESTS.Core"
xmlns:Selectors="clr-namespace:OESTS.Modules.Share.Selectors"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid>
<materialDesign:DialogHost Identifier="WinName" >
<ContentControl HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0 1 0 0"
prism:RegionManager.RegionName="{x:Static core:RegionNames.WorkRegion}" />
</materialDesign:DialogHost>
</Grid>
</UserControl>
后端调用
/*弹出班级选项面板
* ContentView: 用户控件视图的名称
* WinName: 前端定义的 DialogHost Identifier
*/
_dialogService.ShowDialogHost("ContentView", parameters, (r) =>
{
dialogResult = r;
}, "WinName");
Prism 8.0 定义 MaterialDialogService
MaterialDialogService
public class MaterialDialogService : DialogService
{
private readonly IContainerExtension _containerExtension;
public MaterialDialogService(IContainerExtension containerExtension) : base(containerExtension)
{
_containerExtension = containerExtension;
}
public void ShowDialogHost(string name, IDialogParameters parameters, Action<IDialogResult> callback) =>
ShowDialogHost(name, null, parameters, callback);
public void ShowDialogHost(string name, string dialogHostName, IDialogParameters parameters, Action<IDialogResult> callback)
{
if (parameters == null)
parameters = new DialogParameters();
var content = _containerExtension.Resolve<object>(name);
if (!(content is FrameworkElement dialogContent))
{
throw new NullReferenceException("A dialog's content must be a FrameworkElement");
}
AutowireViewModel(dialogContent);
if (!(dialogContent.DataContext is IDialogAware dialogAware))
{
throw new ArgumentException("A dialog's ViewModel must implement IDialogAware interface");
}
var openedEventHandler = new DialogOpenedEventHandler((sender, args) =>
{
dialogAware.OnDialogOpened(parameters);
});
var closedEventHandler = new DialogClosedEventHandler((sender, args) =>
{
dialogAware.OnDialogClosed();
});
dialogAware.RequestClose += res =>
{
if (DialogHost.IsDialogOpen(dialogHostName))
{
callback?.Invoke(res);
DialogHost.Close(dialogHostName);
}
};
var dispatcherFrame = new DispatcherFrame();
if (dialogHostName == null)
{
_ = DialogHost.Show(dialogContent, openedEventHandler, null, closedEventHandler)
.ContinueWith(_ => dispatcherFrame.Continue = false); ;
}
else
{
_ = DialogHost.Show(dialogContent, dialogHostName, openedEventHandler, null, closedEventHandler)
.ContinueWith(_ => dispatcherFrame.Continue = false);
}
try
{
// tell users we're going modal
ComponentDispatcher.PushModal();
Dispatcher.PushFrame(dispatcherFrame);
}
finally
{
// tell users we're going non-modal
ComponentDispatcher.PopModal();
}
dialogAware.RequestClose -= callback;
}
private static void AutowireViewModel(object viewOrViewModel)
{
if (viewOrViewModel is FrameworkElement view && view.DataContext is null && ViewModelLocator.GetAutoWireViewModel(view) is null)
{
ViewModelLocator.SetAutoWireViewModel(view, true);
}
}
}
Prism 9.0 定义 MaterialDialogService
MaterialDialogService
public class MaterialDialogService : DialogService
{
private readonly IContainerExtension _containerExtension;
public MaterialDialogService(IContainerExtension containerExtension) : base(containerExtension)
{
_containerExtension = containerExtension;
}
public void ShowDialogHost(string name, IDialogParameters parameters, Action<IDialogResult> callback) =>
ShowDialogHost(name, null, parameters, callback);
public void ShowDialogHost(string name, string dialogHostName, IDialogParameters parameters, Action<IDialogResult> callback)
{
if (parameters == null)
parameters = new DialogParameters();
var content = _containerExtension.Resolve<object>(name);
if (!(content is FrameworkElement dialogContent))
{
throw new NullReferenceException("A dialog's content must be a FrameworkElement");
}
AutowireViewModel(dialogContent);
if (!(dialogContent.DataContext is IDialogAware dialogAware))
{
throw new ArgumentException("A dialog's ViewModel must implement IDialogAware interface");
}
var openedEventHandler = new DialogOpenedEventHandler((sender, args) =>
{
dialogAware.OnDialogOpened(parameters);
});
var closedEventHandler = new DialogClosedEventHandler((sender, args) =>
{
dialogAware.OnDialogClosed();
});
Action<IDialogResult> requestCloseHandler = (r) =>
{
if (DialogHost.IsDialogOpen(dialogHostName))
{
callback?.Invoke(r);
DialogHost.Close(dialogHostName);
}
};
DialogUtilities.InitializeListener(dialogAware, requestCloseHandler);
var dispatcherFrame = new DispatcherFrame();
if (dialogHostName == null)
{
_ = DialogHost.Show(dialogContent, openedEventHandler, null, closedEventHandler)
.ContinueWith(_ => dispatcherFrame.Continue = false); ;
}
else
{
_ = DialogHost.Show(dialogContent, dialogHostName, openedEventHandler, null, closedEventHandler)
.ContinueWith(_ => dispatcherFrame.Continue = false);
}
try
{
// tell users we're going modal
ComponentDispatcher.PushModal();
Dispatcher.PushFrame(dispatcherFrame);
}
finally
{
// tell users we're going non-modal
ComponentDispatcher.PopModal();
}
}
private static void AutowireViewModel(object viewOrViewModel)
{
if (viewOrViewModel is FrameworkElement view && view.DataContext is null && ViewModelLocator.GetAutoWireViewModel(view) is null)
{
ViewModelLocator.SetAutoWireViewModel(view, true);
}
}
}
注入模块中的服务
- 服务是在模块中注册
- 不能在主窗体的 MainWindowViewModel 中注入服务,会报错,需要在导航的子页面中去注入。
- 在Loaded 事件中导航到子页面
以使用 ESUI 模块为例。
(1)创建一个WPF项目,并引用和店家模块
using ESUI;
using ESUITest.Services;
using ESUITest.Views;
using Prism.Ioc;
using Prism.Modularity;
using Prism.Regions;
using Prism.Unity;
using System;
using System.Windows;
namespace ESUITest
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : PrismApplication
{
protected override Window CreateShell()
{
return Container.Resolve<MainWindow>();
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterSingleton<IESUITestService, ESUITestService>();
containerRegistry.RegisterForNavigation<MainView>();
}
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Uri aero = new Uri("/PresentationFramework.Classic, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35;component/themes/classic.xaml", UriKind.Relative);
Resources.MergedDictionaries.Add(Application.LoadComponent(aero) as ResourceDictionary);
}
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
moduleCatalog.AddModule<ESUIModule>();
}
}
}
MainWindow.xaml
<Window x:Class="ESUITest.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:ESUITest"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d"
Title="{Binding Title}" Height="900" Width="1600">
<Grid>
<ContentControl prism:RegionManager.RegionName="{x:Static local:RegionNames.ContentRegion}" />
</Grid>
</Window>
MainWindow.cs
using Prism.Regions;
using System.Windows;
namespace ESUITest.Views
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private readonly IRegionManager _regionManager;
public MainWindow(IRegionManager regionManager)
{
InitializeComponent();
this.Loaded += MainWindow_Loaded;
_regionManager = regionManager;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
_regionManager.RequestNavigate(RegionNames.ContentRegion, "MainView");
}
}
}
MainViewModel.cs
using ESUI.MenuViewModels;
using ESUI.Mvvm;
using ESUI.Services.api;
using ESUITest.Services;
using Prism.Regions;
namespace ESUITest.ViewModels
{
internal class MainViewModel : BindableBase, IDestructible, INavigationAware, IConfirmNavigationRequest
{
private ATS_SystemMenuVM _mainMenu;
public ATS_SystemMenuVM MainMenu
{
get { return _mainMenu; }
set { SetProperty(ref _mainMenu, value); }
}
public MainViewModel(IRegionManager regionManager, IESUITestService eSUITestService, IESService esService) : base(regionManager)
{
MainMenu = esService.CreateATS_SystemMenuVM("系统");
}
}
}