C#之Prism🔥

2/7/2024 MVVMPrism

# 什么是Prism

Prism是一个微软的开源框架,旨在帮助开发人员构建具有高度可维护性和可扩展性的WPF应用程序。它不仅支持MVVM模式,而且还提供了很多组件来简化复杂应用的构建。通过Prism,我们可以更好地实现模块化、解耦和依赖注入。它提供了许多工具和扩展,使得MVVM模式能够更容易实现和管理。比如,Prism提供了事件聚合器(EventAggregator)用于解耦不同部分的交互、命令和视图之间的绑定等。源码地址:https://github.com/PrismLibrary/Prism (opens new window)

# 引入Prism

# a. 手动引入

1、创建WPF项目,在Nuget添加包Prism.DryIoc mixureSecure 2、改写App.xaml.cs

public partial class App : Application 
//改成
public partial class App : PrismApplication
//实现抽象接口
public partial class App : PrismApplication
{
	protected override Window CreateShell()
	{
		return Container.Resolve<MainWindow>();//启动页面,如果想要启动别的页面可以在这里改动
	}

	protected override void RegisterTypes(IContainerRegistry containerRegistry)
	{
		
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

3、改写App.xaml

<prism:PrismApplication x:Class="PrismDemo.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:PrismDemo"
             xmlns:prism="http://prismlibrary.com/"
            >
    <Application.Resources>
         
    </Application.Resources>
</prism:PrismApplication>
1
2
3
4
5
6
7
8
9
10

4、新建Views和ViewModels文件夹,将View和ViewModel放在各自对应的文件夹,文件夹的名字一定要是Views和ViewModels!!!,不然可能会View可能会找不到ViewModel mixureSecure 5、ViewModel继承BindableBase,实现绑定

public class MainWindowViewModel:BindableBase
{
	private string title = "PrismDemo";

	public string Title
	{
		get { return title; }
		set { SetProperty(ref title,value); }
	}

	public DelegateCommand<string> ButtonDelegateCommand {  get; set; } 


	public void ButtonCommand(string command)
	{
		switch (command)
		{
			case "测试1":
				Title = "测试1";
				MessageBox.Show($"测试1");
				break;
			case "测试2":
				MessageBox.Show($"测试2");
				break ;
			default:
				break;
		}
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

在View中添加prism:ViewModelLocator.AutoWireViewModel="True",他会在Viewmodels下找到符合的ViewModel

<Window x:Class="PrismDemo.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:PrismDemo"
        xmlns:prism="http://prismlibrary.com/"
        mc:Ignorable="d"
        Title="{Binding Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Height="450" Width="800"
        prism:ViewModelLocator.AutoWireViewModel="True"
        xmlns:viewmodel="clr-namespace:PrismDemo.ViewModels"
        d:DataContext="{d:DesignInstance Type =viewmodel:MainWindowViewModel}">
    <DockPanel LastChildFill="True">
        <TextBlock Text="{Binding Title }" FontSize="60" VerticalAlignment="Center" HorizontalAlignment="Center" DockPanel.Dock="Top"/>
        <Button  Content="测试" Command="{Binding ButtonDelegateCommand }" CommandParameter="测试1"/>
        <Button  Content="测试2" Command="{Binding ButtonDelegateCommand }" CommandParameter="测试2"/>
    </DockPanel>
</Window>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

至此手动引入就成功了。

# b. 安装Prism模板

VS工具栏=》扩展(X)=》管理扩展,安装Prism模板 mixureSecure 新建WPF-Prism项目,就可以实现上面的功能了 mixureSecure

# 属性和命令绑定

ViewModel继承BindableBase

public class MainWindowViewModel:BindableBase
{
	private string title = "PrismDemo";

	public string Title
	{
		get { return title; }
		set { SetProperty(ref title,value); }
	}

	public MainWindowViewModel()
    {
		ButtonDelegateCommand = new DelegateCommand<string>(ButtonCommand);
	}

	public DelegateCommand<string> ButtonDelegateCommand {  get; set; } 


	public void ButtonCommand(string command)
	{
		switch (command)
		{
			case "测试1":
				Title = "测试1";
				MessageBox.Show($"测试1");
				break;
			case "测试2":
				MessageBox.Show($"测试2");
				break ;
			default:
				break;
		}
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<Window x:Class="PrismDemo.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:PrismDemo"
        xmlns:prism="http://prismlibrary.com/"
        mc:Ignorable="d"
        Title="{Binding Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Height="450" Width="800"
        prism:ViewModelLocator.AutoWireViewModel="True"
        xmlns:viewmodel="clr-namespace:PrismDemo.ViewModels"
        d:DataContext="{d:DesignInstance Type =viewmodel:MainWindowViewModel}">
    <DockPanel LastChildFill="True">
        <TextBlock Text="{Binding Title }" FontSize="60" VerticalAlignment="Center" HorizontalAlignment="Center" DockPanel.Dock="Top"/>
        <Button  Content="测试" Command="{Binding ButtonDelegateCommand }" CommandParameter="测试1"/>
        <Button  Content="测试2" Command="{Binding ButtonDelegateCommand }" CommandParameter="测试2"/>
    </DockPanel>
</Window>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 区域

Prism的地区管理功能允许你在同一个视图中管理多个区域。区域是视图的容器,可以动态加载和卸载视图,这使得应用程序能够灵活地组织界面。 mixureSecure

新建ViewA和ViewB用户控件,在APP.xmal.cs中注册 mixureSecure

containerRegistry.RegisterForNavigation<ViewA>("ViewA");
containerRegistry.RegisterForNavigation<ViewB>("ViewB");
1
2
public class MainWindowViewModel:BindableBase
{
	private string title = "PrismDemo";

	public string Title
	{
		get { return title; }
		set { SetProperty(ref title,value); }
	}

	private readonly IRegionManager regionManager;


	public MainWindowViewModel(IRegionManager regionManager)
    {
        this.regionManager = regionManager;
		ButtonDelegateCommand = new DelegateCommand<string>(ButtonCommand);
	}

	public DelegateCommand<string> ButtonDelegateCommand {  get; set; } 


	public void ButtonCommand(string command)
	{
		regionManager.Regions["ContentRegion"].RequestNavigate(command);
		Title = command;
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<Window x:Class="PrismDemo.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:PrismDemo"
        xmlns:prism="http://prismlibrary.com/"
        mc:Ignorable="d"
        Title="{Binding Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Height="450" Width="800"
        prism:ViewModelLocator.AutoWireViewModel="True"
        xmlns:viewmodel="clr-namespace:PrismDemo.ViewModels"
        d:DataContext="{d:DesignInstance Type =viewmodel:MainWindowViewModel}">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <UniformGrid  Rows="1" Columns="10" Margin="10">
            <TextBlock Text="{Binding Title }" FontSize="12" Width="100" Height="20" VerticalAlignment="Top"/>
            <Button  Content="ViewA" Command="{Binding ButtonDelegateCommand }" CommandParameter="ViewA" Width="40" Height="20"  VerticalAlignment="Top"/>
            <Button  Content="ViewB" Command="{Binding ButtonDelegateCommand }" CommandParameter="ViewB" Width="40" Height="20"  VerticalAlignment="Top"/>
        </UniformGrid>
        <ContentControl Grid.Row="1" prism:RegionManager.RegionName="ContentRegion"/>
    </Grid>
</Window>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 导航

Prism中的导航主要用于实现应用程序中的视图之间的切换,特别是在多视图和模块化的应用程序中,它帮助管理不同视图之间的关系与切换。导航系统不仅支持视图之间的跳转,还支持传递参数、堆栈式导航和历史记录等功能,非常适合用于构建复杂的、交互丰富的用户界面。

# a.导航传参

要接收导航参数的控件以及视图,ViewModel实现INavigationAware接口

<UserControl x:Class="PrismDemo.Views.ViewA"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:PrismDemo.Views"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800"
            xmlns:prism="http://prismlibrary.com/"
            prism:ViewModelLocator.AutoWireViewModel="True"
            xmlns:viewmodel="clr-namespace:PrismDemo.ViewModels"
            d:DataContext="{d:DesignInstance Type =viewmodel:ViewAViewModel}">
    <Grid Background="red">
        <TextBlock Text="{Binding Text}" FontSize="60"/>
    </Grid>
</UserControl>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ViewAViewModel : BindableBase,INavigationAware
{
	private string text = "我是ViewA";

	public string Text
	{
		get { return text; }
		set { SetProperty(ref text, value); }
	}

	public bool IsNavigationTarget(NavigationContext navigationContext)
	{
		return true;
	}

	public void OnNavigatedFrom(NavigationContext navigationContext)
	{
		
	}

	public void OnNavigatedTo(NavigationContext navigationContext)
	{
		if (navigationContext.Parameters.Keys.Contains("Text"))
			Text = navigationContext.Parameters.GetValue<string>("Text");
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

要传递参数的控件,在导航调转时把参数以键值对的形式传进去

NavigationParameters parameters = new NavigationParameters();
parameters.Add("Text", "我是参数传进来的");
regionManager.Regions["ContentRegion"].RequestNavigate(command, parameters);
1
2
3

这样就可以完成导航参数的传递了。

# b.导航拦截

ViewModel实现IConfirmNavigationRequest接口

public class ViewAViewModel : BindableBase, IConfirmNavigationRequest
{
	private string text = "我是ViewA";

	public string Text
	{
		get { return text; }
		set { SetProperty(ref text, value); }
	}

	public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
	{
		bool navigationResult = true;
		if (MessageBox.Show("确认导航?", "温馨提示", MessageBoxButton.YesNo) == MessageBoxResult.No)
		{
			navigationResult = false;
		}
		continuationCallback(navigationResult);
	}

	public bool IsNavigationTarget(NavigationContext navigationContext)
	{
		return true;
	}

	public void OnNavigatedFrom(NavigationContext navigationContext)
	{

	}

	public void OnNavigatedTo(NavigationContext navigationContext)
	{
		if (navigationContext.Parameters.Keys.Contains("Text"))
			Text = navigationContext.Parameters.GetValue<string>("Text");
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
mixureSecure

# c.导航日志

public class MainWindowViewModel : BindableBase
{
	private string title = "PrismDemo";

	public string Title
	{
		get { return title; }
		set { SetProperty(ref title, value); }
	}

	private readonly IRegionManager regionManager;

	private IRegionNavigationJournal regionNavigationJournal;

	public MainWindowViewModel(IRegionManager regionManager)
	{
		this.regionManager = regionManager;
		ButtonDelegateCommand = new DelegateCommand<string>(ButtonCommand);

	}

	public DelegateCommand<string> ButtonDelegateCommand { get; set; }

	public void ButtonCommand(string command)
	{
		switch (command)
		{
			case "向前":
				GoForward();
				break;
			case "向后":
				GoBack();
				break;
			default:
				NavigationParameters parameters = new NavigationParameters();
				parameters.Add("Text", "我是参数传进来的");
				regionManager.Regions["ContentRegion"].RequestNavigate(command, (callback) =>
				{

					regionNavigationJournal = callback.Context.NavigationService.Journal;

				}, parameters);
				break;
		}
		
		Title = command;
	}

	private void GoBack()
	{
		if (regionNavigationJournal.CanGoBack)
			regionNavigationJournal.GoBack();
	}

	private void GoForward()
	{
		if (regionNavigationJournal.CanGoForward)
			regionNavigationJournal.GoForward();
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

# 对话服务

在 Prism 框架中,对话服务(Dialog Service) 主要用于在应用程序中弹出对话框(对话框通常是指带有一些交互界面的窗口,比如模态窗口、提示框、确认框等),用于显示信息、获取用户输入或者处理复杂的交互。它是基于 MVVM(Model-View-ViewModel) 模式的一个关键组件,允许你将对话框的管理和视图模型解耦,从而提升代码的可测试性和可维护性。 被打开的窗口要实现IDialogAware接口,并实现抽象方法,可以进行参数的传入传出

public class ViewBViewModel : BindableBase, IDialogAware
{
	private string text = "我是ViewB";

	public string Text
	{
		get { return text; }
		set { SetProperty(ref text, value); }
	}

	public DelegateCommand<string> ButtonDelegateCommand { get; set; }

	public DialogCloseListener RequestClose { get; }

	public ViewBViewModel()
	{
		ButtonDelegateCommand = new DelegateCommand<string>(ButtonCommand);

	}

	public bool CanCloseDialog()
	{
		return true;
	}

	public void OnDialogClosed()
	{
		////向外边传参时不能用这个
		//DialogParameters dialogParameters = new DialogParameters();
		//dialogParameters.Add("UserName", "zhangsan");
		//dialogParameters.Add("PWD", "123456");
		////返回模态框操作状态及参数
		//RequestClose.Invoke(dialogParameters, ButtonResult.OK);
	}

	public void OnDialogOpened(IDialogParameters parameters)
	{
		//加载传入来的参数
		Text = parameters.GetValue<string>("入参");

	}

	private void ButtonCommand(string command)
	{
		switch (command)
		{
			case "确认":
				Confrim();
				break;
			case "取消":
				Close();
				break;
			default:
				break;
		}
	}

	private void Close()
	{
		DialogParameters dialogParameters = new DialogParameters();
		dialogParameters.Add("UserName", "zhangsan");
		dialogParameters.Add("PWD", "123456");
		//返回模态框操作状态及参数
		RequestClose.Invoke(dialogParameters, ButtonResult.OK);
	}

	private void Confrim()
	{
		DialogParameters dialogParameters = new DialogParameters();
		dialogParameters.Add("UserName", "lisi");
		dialogParameters.Add("PWD", "166656");
		//返回模态框操作状态及参数
		RequestClose.Invoke(dialogParameters, ButtonResult.OK);
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<UserControl x:Class="PrismDemo.Views.ViewB"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:PrismDemo.Views"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800"
             xmlns:prism="http://prismlibrary.com/"
            prism:ViewModelLocator.AutoWireViewModel="True"
            xmlns:viewmodel="clr-namespace:PrismDemo.ViewModels"
        d:DataContext="{d:DesignInstance Type =viewmodel:ViewBViewModel}">
    <StackPanel Background="Yellow">
        <TextBlock Text="{Binding Text}" FontSize="60"/>
        <Button Content="确认" Command="{Binding ButtonDelegateCommand}" CommandParameter="确认" Width="40" Height="20" Margin="10"/>
        <Button Content="取消" Command="{Binding ButtonDelegateCommand}" CommandParameter="取消" Width="40" Height="20"  Margin="10"/>
    </StackPanel>
</UserControl>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

打开窗口的调用方通过IOC容器拿到IDialogService

public class MainWindowViewModel : BindableBase
{
	private string title = "PrismDemo";

	public string Title
	{
		get { return title; }
		set { SetProperty(ref title, value); }
	}

	private readonly IRegionManager regionManager;

	private IRegionNavigationJournal regionNavigationJournal;

	private IDialogService dialogService;

	public MainWindowViewModel(IRegionManager regionManager, IDialogService dialogService)
	{
		this.regionManager = regionManager;
		this.dialogService = dialogService;
		ButtonDelegateCommand = new DelegateCommand<string>(ButtonCommand);

	}

	public DelegateCommand<string> ButtonDelegateCommand { get; set; }


	public void ButtonCommand(string command)
	{
		switch (command)
		{
			case "向前":
				GoForward();
				break;
			case "向后":
				GoBack();
				break;
			case "打开窗口":
				OpenDialog();
				break;
			default:
				NavigationParameters parameters = new NavigationParameters();
				parameters.Add("Text", "我是参数传进来的");
				regionManager.Regions["ContentRegion"].RequestNavigate(command, (callback) =>
				{
					if(null != callback.Context)
						regionNavigationJournal = callback.Context.NavigationService.Journal;

				}, parameters);
				break;
		}

		Title = command;
	}

	private void GoBack()
	{
		if (regionNavigationJournal.CanGoBack)
			regionNavigationJournal.GoBack();
	}

	private void GoForward()
	{
		if (regionNavigationJournal.CanGoForward)
			regionNavigationJournal.GoForward();
	}

	private void OpenDialog()
	{
		DialogParameters parameters = new DialogParameters();
		parameters.Add("入参", "123");
		dialogService.ShowDialog("ViewB", parameters, r =>
		{
			if (r.Result == ButtonResult.None)
				Title = "Result is None";
			else if (r.Result == ButtonResult.OK)
			{
				Title = "Result is OK";
				var userName = r.Parameters["UserName"];
				var PWD = r.Parameters["PWD"];
			}
				
			else if (r.Result == ButtonResult.Cancel)
				Title = "Result is Cancel";
			else
				Title = "I Don't know what you did!?";
		});
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
mixureSecure

# 事件聚合器

在 Prism 中,事件聚合器(EventAggregator) 是实现 发布-订阅模式(Publish-Subscribe Pattern)的核心组件之一,它用于在应用程序的不同部分之间传递消息或事件。它解耦了事件的发布者和订阅者,使得它们不需要直接引用对方,从而实现更好的松耦合。

public class User
{
    public string Name { get; set; }
    public int Age { get; set; }
}

public class UserMessageEvent : PubSubEvent<User> { }
1
2
3
4
5
6
7

发布事件时传递一个 User 对象:

public class PublisherViewModel
{
    private readonly IEventAggregator _eventAggregator;

    public PublisherViewModel(IEventAggregator eventAggregator)
    {
        _eventAggregator = eventAggregator;
    }

    public void PublishUser()
    {
        var user = new User { Name = "Alice", Age = 25 };
        _eventAggregator.GetEvent<UserMessageEvent>().Publish(user);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

订阅时,处理传递的 User 对象:

public class SubscriberViewModel
{
    private readonly IEventAggregator _eventAggregator;

    public SubscriberViewModel(IEventAggregator eventAggregator)
    {
        _eventAggregator = eventAggregator;

        _eventAggregator.GetEvent<UserMessageEvent>().Subscribe(OnUserReceived);
    }

    private void OnUserReceived(User user)
    {
        Console.WriteLine($"Received user: {user.Name}, Age: {user.Age}");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

一次性订阅(Once):通过使用 Subscribe 方法的重载,你可以让事件订阅者仅在第一次触发时执行处理逻辑,之后不再处理。EventAggregator 支持线程选项,指定事件处理是在 UI 线程还是后台线程执行。比如你希望在 UI 线程中处理事件,可以指定 ThreadOption.UIThread。

eventAggregator.GetEvent<MyMessageEvent>().Subscribe(OnMessageReceived, ThreadOption.UIThread, false);
1

# 模块化

# a.新建模块

mixureSecure

添加ModuleAModule类,实现IModule接口,并且注册你想要的视图

public class ModuleAModule : IModule
{
	public void OnInitialized(IContainerProvider containerProvider)
	{
	}

	public void RegisterTypes(IContainerRegistry containerRegistry)
	{
		containerRegistry.RegisterForNavigation<ViewC>();
	}
}
1
2
3
4
5
6
7
8
9
10
11

这样模块就制作完毕。

# b.调用模块

mixureSecure

在主程序中重写CreateModuleCatalog方法,这个是通过特定目录下加载模块的,也就是将你模块编译好的dll放在该目录下即可

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

	protected override void RegisterTypes(IContainerRegistry containerRegistry)
	{
		containerRegistry.RegisterForNavigation<ViewA>("ViewA");
		containerRegistry.RegisterForNavigation<ViewB>("ViewB");
		
	}

	protected override IModuleCatalog CreateModuleCatalog()
	{
		return new DirectoryModuleCatalog() { ModulePath = @".\Modules" };
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

如果报错,可能是主项目与模块引用的Prism版本不同导致。