对 Prism 框架在 .NET Core 8.0 和 WPF 环境中的详细说明,涵盖其核心功能、常用方法、实用方法、经典案例以及具体实例代码。内容基于 Prism 9.0(支持 .NET 8.0),提供深入解析、完整示例和最佳实践,确保满足你对详细性和实用性的需求。文中将结合前述 IEventAggregator 示例,扩展更多场景,并提供更复杂的代码实现。


1. Prism 框架详细说明

1.1 什么是 Prism?

Prism 是一个开源框架,旨在帮助开发者构建松耦合、可维护、可测试的 XAML 应用程序,特别适用于 WPF、.NET MAUI 和 Uno Platform。它基于 MVVM(Model-View-ViewModel)模式,提供以下核心功能:

  • 模块化开发:将应用拆分为独立模块,支持动态加载。

  • 依赖注入(DI):内置 IoC 容器(如 Unity、DryIoc),管理服务和 ViewModel。

  • 导航:支持区域导航(Region Navigation),实现复杂 UI 切换。

  • 事件聚合器(EventAggregator):实现模块间松耦合通信。

  • MVVM 支持:提供 BindableBase 和 DelegateCommand,简化属性绑定和命令实现。

1.2 核心组件

  • IModule:定义模块,负责初始化和注册。

  • IRegionManager:管理 UI 区域,注入视图。

  • IEventAggregator:跨模块事件通信。

  • IContainerRegistry/IContainerProvider:依赖注入容器,注册和解析服务。

  • IDialogService:管理模态对话框。

  • INavigationAware:处理导航生命周期。

1.3 优势

  • 模块化:支持插件式架构,易于扩展。

  • 可测试性:松耦合设计便于单元测试。

  • 灵活性:支持多种 IoC 容器和导航模式。

  • 社区支持:由 .NET Foundation 维护,文档丰富(https://prismlibrary.com/docs/)。

1.4 适用场景

  • 中大型 WPF 应用,如企业级管理系统、仪表盘应用。

  • 需要模块化或插件式架构的项目。

  • 强调可维护性和可测试性的团队开发。


2. 常用方法

以下是 Prism 的常用方法,按组件分类,附带简要说明。

2.1 IModule

  • OnInitialized(IContainerProvider):模块初始化,注入视图或执行启动逻辑。

  • RegisterTypes(IContainerRegistry):注册模块特定的服务、视图或 ViewModel。

    csharp

    public void RegisterTypes(IContainerRegistry containerRegistry)
    {
        containerRegistry.RegisterSingleton<IMyService, MyService>();
    }

2.2 IRegionManager

  • RegisterViewWithRegion(string, Type):将视图注册到指定区域。

    csharp

    regionManager.RegisterViewWithRegion("ContentRegion", typeof(Views.MyView));
  • RequestNavigate(string, string, NavigationParameters):导航到指定视图。

    csharp

    regionManager.RequestNavigate("ContentRegion", "MyView", new NavigationParameters { { "id", 123 } });

2.3 IEventAggregator

  • GetEvent<TEvent>():获取事件实例。

    csharp

    var event = _eventAggregator.GetEvent<MessageSentEvent>();
  • Publish(T):发布事件。

    csharp

    _eventAggregator.GetEvent<MessageSentEvent>().Publish("Hello");
  • Subscribe(Action<T>, ThreadOption, bool, Predicate<T>):订阅事件。

    csharp

    _eventAggregator.GetEvent<MessageSentEvent>().Subscribe(msg => Console.WriteLine(msg), ThreadOption.UIThread);

2.4 IDialogService

  • ShowDialog(string, DialogParameters, Action<IDialogResult>):显示模态对话框。

    csharp

    _dialogService.ShowDialog("MyDialog", new DialogParameters(), result => { /* Handle result */ });

2.5 BindableBase

  • SetProperty<T>(ref T, T, string):设置属性并触发通知。

    csharp

    private string _title;
    public string Title
    {
        get => _title;
        set => SetProperty(ref _title, value);
    }

2.6 DelegateCommand

  • Execute():执行命令逻辑。

  • CanExecute():控制命令是否可用。

    csharp

    public DelegateCommand SaveCommand { get; }
    SaveCommand = new DelegateCommand(Save, CanSave);

3. 实用方法

以下是 Prism 的实用方法,解决常见开发问题。

3.1 动态模块加载

使用 DirectoryModuleCatalog 动态加载模块:

csharp

protected override IModuleCatalog CreateModuleCatalog()
{
    return new DirectoryModuleCatalog { ModulePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Modules") };
}

3.2 导航错误处理

捕获导航失败:

csharp

regionManager.RequestNavigate("ContentRegion", "MyView", result =>
{
    if (!result.Result)
        MessageBox.Show($"Navigation failed: {result.Error.Message}");
});

3.3 事件订阅取消

使用 ISubscriptionToken 管理订阅:

csharp

private readonly ISubscriptionToken _token;
_token = _eventAggregator.GetEvent<MessageSentEvent>().Subscribe(OnMessage);
public void Dispose()
{
    _eventAggregator.GetEvent<MessageSentEvent>().Unsubscribe(_token);
}

3.4 区域作用域

创建子区域作用域:

csharp

var scopedRegionManager = regionManager.Regions["SubRegion"].RegionManager;
scopedRegionManager.RequestNavigate("SubRegion", "SubView");

3.5 批量属性更新

优化多属性变更通知:

csharp

public void UpdateProperties(string newTitle, int newCount)
{
    bool changed = false;
    changed |= SetProperty(ref _title, newTitle, nameof(Title));
    changed |= SetProperty(ref _count, newCount, nameof(Count));
    if (changed)
        RaisePropertyChanged(nameof(IsValid));
}

4. 经典案例

以下是 Prism 在实际项目中的经典应用场景。

4.1 企业级仪表盘

  • 需求:多模块仪表盘,支持动态加载图表模块(如销售、库存)。

  • 实现:

    • 每个图表模块实现 IModule,注册视图到仪表盘区域。

    • 使用 IEventAggregator 同步数据更新。

    • 支持延迟加载,优化启动时间。

4.2 插件式编辑器

  • 需求:文本编辑器支持插件(如语法高亮、代码格式化)。

  • 实现:

    • 使用 DirectoryModuleCatalog 加载插件 DLL。

    • 插件通过 IEventAggregator 通信,注入工具栏视图。

    • 提供模态对话框配置插件。

4.3 导航式业务应用

  • 需求:CRM 系统,支持多页面导航(如客户列表、订单详情)。

  • 实现:

    • 使用 IRegionManager 管理主区域和子区域。

    • 实现 INavigationAware 处理导航参数和生命周期。

    • 使用 IDialogService 弹出确认对话框。


5. 具体实例代码

以下是一个完整的 Prism WPF 示例,展示 模块化、动态加载、导航、事件通信和 对话框 的综合应用,扩展前述 IEventAggregator 示例。

5.1 项目结构

plaintext

MyWpfApp/
├── MyWpfApp.Core/                    # 核心类库(.NET 8.0)
│   ├── Events/
│   │   └── MessageSentEvent.cs
│   ├── Services/
│   │   └── IMyService.cs
│   └── ViewModels/
│       └── ViewModelBase.cs
├── MyWpfApp.Wpf/                     # WPF 项目(.NET 8.0)
│   ├── App.xaml
│   ├── Views/
│   │   └── MainWindow.xaml
│   ├── ViewModels/
│   │   └── MainWindowViewModel.cs
│   └── Modules/                      # 动态模块目录
├── MyWpfApp.Modules.ModuleA/         # 模块 A(.NET 8.0)
│   ├── Views/
│   │   ├── MessageListView.xaml
│   │   ├── MessageSenderView.xaml
│   │   └── SettingsDialogView.xaml
│   ├── ViewModels/
│   │   ├── MessageListViewModel.cs
│   │   ├── MessageSenderViewModel.cs
│   │   └── SettingsDialogViewModel.cs
│   └── ModuleAModule.cs
└── MyWpfApp.sln

5.2 安装 Prism

bash

dotnet add package Prism.Wpf --version 9.0.537
dotnet add package Prism.Unity

5.3 核心类库

  1. MessageSentEvent.cs:

csharp

using Prism.Events;

namespace MyWpfApp.Core.Events
{
    public class MessageSentEvent : PubSubEvent<string> { }
}
  1. IMyService.cs:

csharp

namespace MyWpfApp.Core.Services
{
    public interface IMyService
    {
        string ProcessMessage(string message);
    }

    public class MyService : IMyService
    {
        public string ProcessMessage(string message) => $"Processed: {message}";
    }
}
  1. ViewModelBase.cs:

csharp

using Prism.Mvvm;

namespace MyWpfApp.Core.ViewModels
{
    public abstract class ViewModelBase : BindableBase
    {
        // 公共逻辑
    }
}

5.4 主应用程序

  1. App.xaml.cs:

csharp

using Prism.Ioc;
using Prism.Modularity;
using Prism.Unity;
using System.IO;
using System.Windows;

namespace MyWpfApp.Wpf
{
    public partial class App : PrismApplication
    {
        protected override Window CreateShell()
        {
            return Container.Resolve<Views.MainWindow>();
        }

        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
            containerRegistry.RegisterSingleton<Core.Services.IMyService, Core.Services.MyService>();
        }

        protected override IModuleCatalog CreateModuleCatalog()
        {
            var catalog = new DirectoryModuleCatalog { ModulePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Modules") };
            return catalog;
        }
    }
}
  1. MainWindow.xaml:

xaml

<Window x:Class="MyWpfApp.Wpf.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="Prism Demo" Height="600" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal" Margin="5">
            <Button Content="Load Module A" Command="{Binding LoadModuleCommand}" CommandParameter="ModuleA" Margin="5"/>
            <TextBlock Text="{Binding LoadingMessage}" VerticalAlignment="Center"/>
        </StackPanel>
        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="2*"/>
                <ColumnDefinition Width="3*"/>
            </Grid.ColumnDefinitions>
            <ContentControl prism:RegionManager.RegionName="MessageListRegion"/>
            <ContentControl Grid.Column="1" prism:RegionManager.RegionName="MessageSenderRegion"/>
        </Grid>
    </Grid>
</Window>
  1. MainWindowViewModel.cs:

csharp

using Prism.Commands;
using Prism.Modularity;
using Prism.Mvvm;
using System;
using System.Threading.Tasks;

namespace MyWpfApp.Wpf.ViewModels
{
    public class MainWindowViewModel : BindableBase
    {
        private readonly IModuleManager _moduleManager;
        private bool _isLoading;
        private string _loadingMessage;

        public bool IsLoading
        {
            get => _isLoading;
            set => SetProperty(ref _isLoading, value);
        }

        public string LoadingMessage
        {
            get => _loadingMessage;
            set => SetProperty(ref _loadingMessage, value);
        }

        public DelegateCommand<string> LoadModuleCommand { get; }

        public MainWindowViewModel(IModuleManager moduleManager)
        {
            _moduleManager = moduleManager;
            LoadModuleCommand = new DelegateCommand<string>(AsyncLoadModule);
            _moduleManager.LoadModuleCompleted += ModuleManager_LoadModuleCompleted;
        }

        private async void AsyncLoadModule(string moduleName)
        {
            IsLoading = true;
            LoadingMessage = $"Loading {moduleName}...";
            try
            {
                await Task.Run(() => _moduleManager.LoadModule(moduleName));
            }
            catch (Exception ex)
            {
                LoadingMessage = $"Failed to load {moduleName}: {ex.Message}";
            }
        }

        private void ModuleManager_LoadModuleCompleted(object sender, LoadModuleCompletedEventArgs e)
        {
            IsLoading = false;
            LoadingMessage = e.Error == null ? $"{e.ModuleInfo.ModuleName} loaded" : $"Error: {e.Error.Message}";
        }
    }
}

5.5 Module A

  1. ModuleAModule.cs:

csharp

using Prism.Ioc;
using Prism.Modularity;
using Prism.Regions;

namespace MyWpfApp.Modules.ModuleA
{
    public class ModuleAModule : IModule
    {
        public void OnInitialized(IContainerProvider containerProvider)
        {
            var regionManager = containerProvider.Resolve<IRegionManager>();
            regionManager.RegisterViewWithRegion("MessageListRegion", typeof(Views.MessageListView));
            regionManager.RegisterViewWithRegion("MessageSenderRegion", typeof(Views.MessageSenderView));
        }

        public void RegisterTypes(IContainerRegistry containerRegistry)
        {
            containerRegistry.Register<Views.MessageListView>();
            containerRegistry.Register<Views.MessageSenderView>();
            containerRegistry.RegisterDialog<Views.SettingsDialogView, ViewModels.SettingsDialogViewModel>();
            containerRegistry.Register<ViewModels.MessageListViewModel>();
            containerRegistry.Register<ViewModels.MessageSenderViewModel>();
        }
    }
}
  1. MessageListView.xaml:

xaml

<UserControl x:Class="MyWpfApp.Modules.ModuleA.Views.MessageListView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Filter:" VerticalAlignment="Center" Margin="5"/>
            <TextBox Text="{Binding Filter, UpdateSourceTrigger=PropertyChanged}" Width="100" Margin="5"/>
        </StackPanel>
        <ListBox ItemsSource="{Binding Messages}" Grid.Row="1" Margin="5"/>
    </Grid>
</UserControl>
  1. MessageListViewModel.cs(扩展前述代码):

csharp

using MyWpfApp.Core.Events;
using Prism.Events;
using Prism.Mvvm;
using System;
using System.Collections.ObjectModel;

namespace MyWpfApp.Modules.ModuleA.ViewModels
{
    public class MessageListViewModel : BindableBase, IDisposable
    {
        private readonly IEventAggregator _ea;
        private ISubscriptionToken _subscriptionToken;
        private ObservableCollection<string> _messages;
        private string _filter = "Brian";

        public ObservableCollection<string> Messages
        {
            get => _messages;
            set => SetProperty(ref _messages, value);
        }

        public string Filter
        {
            get => _filter;
            set
            {
                if (SetProperty(ref _filter, value))
                    UpdateSubscription();
            }
        }

        public MessageListViewModel(IEventAggregator ea)
        {
            _ea = ea;
            Messages = new ObservableCollection<string>();
            UpdateSubscription();
        }

        private void UpdateSubscription()
        {
            _ea.GetEvent<MessageSentEvent>().Unsubscribe(_subscriptionToken);
            _subscriptionToken = _ea.GetEvent<MessageSentEvent>().Subscribe(
                MessageReceived,
                ThreadOption.UIThread,
                false,
                filter => filter.Contains(_filter));
        }

        private void MessageReceived(string message)
        {
            Messages.Add(message);
            if (Messages.Count > 100)
                Messages.RemoveAt(0);
        }

        public void Dispose()
        {
            _ea.GetEvent<MessageSentEvent>().Unsubscribe(_subscriptionToken);
        }
    }
}
  1. MessageSenderView.xaml:

xaml

<UserControl x:Class="MyWpfApp.Modules.ModuleA.Views.MessageSenderView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Message:" VerticalAlignment="Center" Margin="5"/>
            <TextBox Text="{Binding MessageToSend, UpdateSourceTrigger=PropertyChanged}" Width="200" Margin="5"/>
            <Button Content="Send" Command="{Binding SendMessageCommand}" Margin="5"/>
        </StackPanel>
        <Button Grid.Row="1" Content="Open Settings" Command="{Binding OpenSettingsCommand}" Margin="5"/>
        <TextBlock Grid.Row="2" Text="{Binding ProcessedMessage}" Margin="5"/>
    </Grid>
</UserControl>
  1. MessageSenderViewModel.cs:

csharp

using MyWpfApp.Core.Events;
using MyWpfApp.Core.Services;
using Prism.Commands;
using Prism.Events;
using Prism.Mvvm;
using Prism.Services.Dialogs;

namespace MyWpfApp.Modules.ModuleA.ViewModels
{
    public class MessageSenderViewModel : BindableBase
    {
        private readonly IEventAggregator _ea;
        private readonly IMyService _myService;
        private readonly IDialogService _dialogService;
        private string _messageToSend;
        private string _processedMessage;

        public string MessageToSend
        {
            get => _messageToSend;
            set => SetProperty(ref _messageToSend, value);
        }

        public string ProcessedMessage
        {
            get => _processedMessage;
            set => SetProperty(ref _processedMessage, value);
        }

        public DelegateCommand SendMessageCommand { get; }
        public DelegateCommand OpenSettingsCommand { get; }

        public MessageSenderViewModel(IEventAggregator ea, IMyService myService, IDialogService dialogService)
        {
            _ea = ea;
            _myService = myService;
            _dialogService = dialogService;
            SendMessageCommand = new DelegateCommand(SendMessage);
            OpenSettingsCommand = new DelegateCommand(OpenSettings);
        }

        private void SendMessage()
        {
            if (!string.IsNullOrEmpty(MessageToSend))
            {
                _ea.GetEvent<MessageSentEvent>().Publish(MessageToSend);
                ProcessedMessage = _myService.ProcessMessage(MessageToSend);
                MessageToSend = string.Empty;
            }
        }

        private void OpenSettings()
        {
            var parameters = new DialogParameters { { "CurrentFilter", "Brian" } };
            _dialogService.ShowDialog("SettingsDialogView", parameters, result =>
            {
                if (result.Result == ButtonResult.OK)
                {
                    var newFilter = result.Parameters.GetValue<string>("NewFilter");
                    ProcessedMessage = $"Settings updated: Filter = {newFilter}";
                }
            });
        }
    }
}
  1. SettingsDialogView.xaml:

xaml

<Window x:Class="MyWpfApp.Modules.ModuleA.Views.SettingsDialogView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Settings" Height="200" Width="300" WindowStartupLocation="CenterOwner">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <TextBlock Text="Message Filter:" Margin="5"/>
        <TextBox Text="{Binding Filter, UpdateSourceTrigger=PropertyChanged}" Margin="5" Grid.Row="1"/>
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Row="2">
            <Button Content="OK" Command="{Binding OkCommand}" Margin="5"/>
            <Button Content="Cancel" Command="{Binding CancelCommand}" Margin="5"/>
        </StackPanel>
    </Grid>
</Window>
  1. SettingsDialogViewModel.cs:

csharp

using Prism.Commands;
using Prism.Mvvm;
using Prism.Services.Dialogs;

namespace MyWpfApp.Modules.ModuleA.ViewModels
{
    public class SettingsDialogViewModel : BindableBase, IDialogAware
    {
        private string _filter;

        public string Filter
        {
            get => _filter;
            set => SetProperty(ref _filter, value);
        }

        public DelegateCommand OkCommand { get; }
        public DelegateCommand CancelCommand { get; }

        public event Action<IDialogResult> RequestClose;

        public SettingsDialogViewModel()
        {
            OkCommand = new DelegateCommand(OnOk);
            CancelCommand = new DelegateCommand(OnCancel);
        }

        private void OnOk()
        {
            var result = new DialogResult(ButtonResult.OK, new DialogParameters { { "NewFilter", Filter } });
            RequestClose?.Invoke(result);
        }

        private void OnCancel()
        {
            RequestClose?.Invoke(new DialogResult(ButtonResult.Cancel));
        }

        public bool CanCloseDialog() => true;

        public void OnDialogClosed() { }

        public void OnDialogOpened(IDialogParameters parameters)
        {
            Filter = parameters.GetValue<string>("CurrentFilter");
        }
    }
}

5.6 运行效果

  1. 启动应用,点击“Load Module A”加载模块。

  2. 左侧显示 MessageListView,右侧显示 MessageSenderView。

  3. 在 MessageSenderView 输入消息(如 “Hello from Brian”),点击“Send”,消息通过 IEventAggregator 发布。

  4. MessageListView 仅显示包含过滤器(如 “Brian”)的消息。

  5. 点击“Open Settings”弹出对话框,修改过滤器,更新 MessageListView 的订阅。

  6. MyService 处理消息并显示在 MessageSenderView。


6. 更详细的示例:嵌套导航和动态模块管理

以下是一个更复杂的示例,展示 嵌kit区域导航 和 动态模块管理,进一步扩展功能。

6.1 项目结构扩展

plaintext

MyWpfApp.Modules.ModuleB/
├── Views/
│   ├── DashboardView.xaml
│   ├── ChartView.xaml
├── ViewModels/
│   ├── DashboardViewModel.cs
│   ├── ChartViewModel.cs
└── ModuleBModule.cs

6.2 Module B 实现

  1. ModuleBModule.cs:

csharp

using Prism.Ioc;
using Prism.Modularity;
using Prism.Regions;

namespace MyWpfApp.Modules.ModuleB
{
    public class ModuleBModule : IModule
    {
        public void OnInitialized(IContainerProvider containerProvider)
        {
            var regionManager = containerProvider.Resolve<IRegionManager>();
            regionManager.RegisterViewWithRegion("MessageSenderRegion", typeof(Views.DashboardView));
        }

        public void RegisterTypes(IContainerRegistry containerRegistry)
        {
            containerRegistry.Register<Views.DashboardView>();
            containerRegistry.Register<Views.ChartView>();
            containerRegistry.Register<ViewModels.DashboardViewModel>();
            containerRegistry.Register<ViewModels.ChartViewModel>();
        }
    }
}
  1. DashboardView.xaml:

xaml

<UserControl x:Class="MyWpfApp.Modules.ModuleB.Views.DashboardView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:prism="http://prismlibrary.com/">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Button Content="Show Chart" Command="{Binding ShowChartCommand}" Margin="5"/>
        <ContentControl Grid.Row="1" prism:RegionManager.RegionName="ChartRegion"/>
    </Grid>
</UserControl>
  1. DashboardViewModel.cs:

csharp

using Prism.Commands;
using Prism.Mvvm;
using Prism.Regions;

namespace MyWpfApp.Modules.ModuleB.ViewModels
{
    public class DashboardViewModel : BindableBase
    {
        private readonly IRegionManager _regionManager;

        public DelegateCommand ShowChartCommand { get; }

        public DashboardViewModel(IRegionManager regionManager)
        {
            _regionManager = regionManager;
            ShowChartCommand = new DelegateCommand(ShowChart);
        }

        private void ShowChart()
        {
            var scopedRegionManager = _regionManager.CreateRegionManager();
            var region = _regionManager.Regions["ChartRegion"];
            region.RegionManager = scopedRegionManager;
            scopedRegionManager.RequestNavigate("ChartRegion", "ChartView");
        }
    }
}
  1. ChartView.xaml:

xaml

<UserControl x:Class="MyWpfApp.Modules.ModuleB.Views.ChartView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <TextBlock Text="{Binding ChartData}" FontSize="20" Background="LightBlue" Margin="10"/>
</UserControl>
  1. ChartViewModel.cs:

csharp

using MyWpfApp.Core.Events;
using Prism.Events;
using Prism.Mvvm;

namespace MyWpfApp.Modules.ModuleB.ViewModels
{
    public class ChartViewModel : BindableBase
    {
        private string _chartData = "Chart Data";
        public string ChartData
        {
            get => _chartData;
            set => SetProperty(ref _chartData, value);
        }

        public ChartViewModel(IEventAggregator ea)
        {
            ea.GetEvent<MessageSentEvent>().Subscribe(UpdateChart, ThreadOption.UIThread);
        }

        private void UpdateChart(string message)
        {
            ChartData = $"Chart updated with: {message}";
        }
    }
}

6.3 动态模块管理

扩展 MainWindowViewModel 支持模块卸载:

csharp

public DelegateCommand<string> UnloadModuleCommand { get; }

public MainWindowViewModel(IModuleManager moduleManager)
{
    _moduleManager = moduleManager;
    LoadModuleCommand = new DelegateCommand<string>(AsyncLoadModule);
    UnloadModuleCommand = new DelegateCommand<string>(UnloadModule);
    _moduleManager.LoadModuleCompleted += ModuleManager_LoadModuleCompleted;
}

private void UnloadModule(string moduleName)
{
    // 假设支持卸载(需自定义 IModuleManager 扩展)
    LoadingMessage = $"Unloading {moduleName} not supported in default Prism.";
}

6.4 运行效果

  1. 加载 Module A 显示消息列表和发送界面。

  2. 加载 Module B 显示仪表盘,点击“Show Chart”在子区域显示图表。

  3. Module A 发送消息,Module B 的图表通过 IEventAggregator 更新。

  4. 动态调整 Module A 的消息过滤器,影响消息显示。


7. 性能优化和注意事项

7.1 性能优化

  • 延迟加载:如前述,使用 InitializationMode.OnDemand。

  • 异步操作:模块初始化和导航使用 async/await。

  • 视图缓存:实现 IRegionMemberLifetime 复用视图:

    csharp

    public bool KeepAlive => true;
  • 事件清理:确保 IDisposable 取消事件订阅。

7.2 注意事项

  • 线程安全:始终使用 ThreadOption.UIThread 或 Dispatcher 操作 UI。

  • 模块依赖:避免模块间直接引用,使用 IEventAggregator 或共享服务。

  • XAML 性能:减少复杂绑定,使用 OneWay 模式。

  • 测试:为 ViewModel 和服务编写单元测试:

    csharp

    [Fact]
    public void MessageListViewModel_AddsFilteredMessage()
    {
        var ea = new EventAggregator();
        var vm = new MessageListViewModel(ea);
        ea.GetEvent<MessageSentEvent>().Publish("Hello from Brian");
        Assert.Single(vm.Messages);
        Assert.Equal("Hello from Brian", vm.Messages[0]);
    }

8. 参考资料


9. 总结

  • Prism 核心:提供模块化、导航、事件通信和 MVVM 支持,适合复杂 WPF 应用。

  • 常用方法:RegisterViewWithRegion、RequestNavigate、GetEvent 等简化开发。

  • 实用方法:动态加载、错误处理、区域作用域增强灵活性。

  • 经典案例:仪表盘、插件编辑器、导航应用展示 Prism 能力。

  • 实例代码:提供完整示例,覆盖模块化、导航、事件和对话框。

  • 优化:延迟加载、异步操作和视图缓存确保高性能。

如果需要更具体功能(如动态主题、复杂数据绑定)或进一步代码调试,请告诉我!

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐