WPF MVVM Prism 扩展
创建 Prism WPF 项目
创建最基本的 WPF 项目
使用 NuGet 安装Prism.DryIoc
修改App.xaml.cs
文件,只需修改继承类为PrismApplication
public partial class App : PrismApplication
此时会报错,因为App.xaml
文件有问题
添加prism
引用,并且修改Application
标签
<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/"
StartupUri="MainWindow.xaml">
<Application.Resources />
</prism:PrismApplication>
接下来,回到App.xaml.cs
文件,并重写方法
public partial class App : PrismApplication
{
protected override Window CreateShell()
{
//返回首页窗口
return Container.Resolve<MainWindow>();
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
//注册依赖注入
}
}
这里还有一个注意点,因为我们在App.xaml.cs
中的重写方法中指定打开的窗口
但是App.xaml
中,我们使用了StartupUri="MainWindow.xaml"
,会导致打开两个窗口,记得删掉这个属性
然后就可以运行了
在View
中使用prism:ViewModelLocator.AutoWireViewModel="True"
自动绑定ViewModel
自动绑定ViewModel
首先是根据App.xaml
重写override void ConfigureViewModelLocator()
方法,使用ViewModelLocationProvider.Register
方法注册的映射的ViewModel
其次就是根据命名规则自动创建ViewModel
默认的规则是Views
和ViewModels
文件夹下分别存放View
和ViewModel
View
的命名规范:一个Main
页面,文件名为MainView
ViewModel
的命名规范:一个Main
页面的ViewModel
,文件名为MainViewModel
这个默认规则可以更改
protected override void ConfigureViewModelLocator()
{
base.ConfigureViewModelLocator();
}
Prism 区域
MainView
,这个页面的功能就是点击按钮,显示不同的界面
<Window
x:Class="PrismDemo.Views.MainView"
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:local="clr-namespace:PrismDemo"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prism="http://prismlibrary.com/"
Title="MainWindow"
Width="800"
Height="450"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<Button
Width="100"
Height="30"
Margin="5"
Command="{Binding OpenCommand}"
CommandParameter="AView"
Content="打开A页面" />
<Button
Width="100"
Height="30"
Margin="5"
Command="{Binding OpenCommand}"
CommandParameter="BView"
Content="打开B页面" />
<Button
Width="100"
Height="30"
Margin="5"
Command="{Binding OpenCommand}"
CommandParameter="CView"
Content="打开C页面" />
</StackPanel>
<ContentControl Grid.Row="1" prism:RegionManager.RegionName="ContentRegion" />
</Grid>
</Window>
MainViewModel
,依赖注入区域管理器,可以使用区域管理器更改ContentControl
public class MainViewModel : BindableBase
{
private readonly IRegionManager _regionManager;
public MainViewModel(IRegionManager regionManager)
{
this._regionManager = regionManager;
}
private DelegateCommand<string> _openCommand;
public DelegateCommand<string> OpenCommand =>
_openCommand ?? (_openCommand = new DelegateCommand<string>(ExecuteOpenCommand));
void ExecuteOpenCommand(string parameter)
{
//通过依赖注入的导航,为指导的区域创建用户控件
//Regions集合就是我们在View中定义的 prism:RegionManager.RegionName
this._regionManager.Regions["ContentRegion"].RequestNavigate(parameter);
}
}
App.xaml.cs
,在这里注册导航
public partial class App : PrismApplication
{
protected override Window CreateShell()
{
//返回首页窗口
return Container.Resolve<MainView>();
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
//注册依赖注入
//注册导航,默认以类型来命名区域的名称,可以通过参数指定区域的名称以及传递的参数
containerRegistry.RegisterForNavigation<AView>();
containerRegistry.RegisterForNavigation<BView>();
containerRegistry.RegisterForNavigation<CView>();
}
}
AView
BView
CView
都是用户控件,里面都只有一个控件
<TextBlock FontSize="80" Text="这是X页面" />
效果
点击不同的按钮切换区域,可以通过命令的参数切换区域显示的用户控件,我就懒得整gif了
注意
RegionManager
算是一种容器,依赖注入的都是同一个RegionManager
,在不同的窗口中,区域名可以有相同的,相同名称的区域会被放进ItemMetadataCollection
集合中,RequestNavigate()
正常,但是RegisterViewWithRegion()
会导致多次执行初始化区域view操作,只使用RequestNavigate()
的话,只有在第一次调用时会创建区域view,但貌似始终只有一个区域view,所以这个应该是错误的使用方式,也许应该当成单例模式使用- 调用
CreateRegionManager()
可以创建一个新的RegionManager
,不过似乎没有什么用
Prism 模块化
在解决方案下,新建两个WPF
项目,项目属性设置为的输出类型
更改为类库
删掉模块的App.xaml
App.xaml.cs
MainWindow.xaml
MainWindow.xaml.cs
AssemblyInfo.cs
文件
在这两个模块中新建Views
文件夹,并且各自新建一个View
用户控件
<TextBlock FontSize="40" Text="这是 Module X 的X页面" />
在模块的根目录下新建类ModuleXProfile
,实现IModule
接口,这样就会识别为模块
记得使用 NuGet 安装Prism.DryIoc
public class ModuleAProfile : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
//注册导航
containerRegistry.RegisterForNavigation<AView>();
}
}
public class ModuleBProfile : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
//注册导航
containerRegistry.RegisterForNavigation<BView>();
}
}
在原先WPF
项目的App.xaml.cs
中重写方法,注册模块,并且注释掉之前注册的导航
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
//注册依赖注入
//注册导航,默认以类型来命名区域的名称,可以通过参数指定区域的名称以及传递的参数
//containerRegistry.RegisterForNavigation<AView>();
//containerRegistry.RegisterForNavigation<BView>();
//containerRegistry.RegisterForNavigation<CView>();
}
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
//注册模块
moduleCatalog.AddModule<ModuleAProfile>();
moduleCatalog.AddModule<ModuleBProfile>();
base.ConfigureModuleCatalog(moduleCatalog);
}
效果
点击不同的按钮切换到模块的区域,效果与上一个类似
当前项目结构
动态加载模块
在原先WPF
项目的App.xaml.cs
中重写方法,注释掉之前注册的导航
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
//注册模块
//moduleCatalog.AddModule<ModuleAProfile>();
//moduleCatalog.AddModule<ModuleBProfile>();
base.ConfigureModuleCatalog(moduleCatalog);
}
protected override IModuleCatalog CreateModuleCatalog()
{
//通过读取目录的方式加载模块
return new DirectoryModuleCatalog() { ModulePath = @".\Modules" };
}
之后在原先WPF
项目bin\Debug\net6.0-windows
目录下新建Modules
文件夹
并把两个模块的bin\Debug\net6.0-windows
目录下的dll
文件复制到原先WPF
项目bin\Debug\net6.0-windows\Modules
目录下
再运行项目的结果是一样的
当前目录结构
Prism 导航功能
在XAML
中,我们可以使用prism:ViewModelLocator.AutoWireViewModel="True"
来自动绑定ViewModel
但在实际情况中,我们一般强制绑定
比方说,我在ModuleA
中新建一个AViewModel
,然后这样注册导航,就不需要在XAML
中使用prism:ViewModelLocator.AutoWireViewModel="True"
public void RegisterTypes(IContainerRegistry containerRegistry)
{
//注册导航
containerRegistry.RegisterForNavigation<AView, AViewModel>();
}
传递参数
看一下MainViewModel
里的OpenCommand
RequestNavigate()
有一个重载,可以传递参数,这个参数是个字典
void ExecuteOpenCommand(string parameter)
{
NavigationParameters map = new NavigationParameters();
map.Add("Title", "Hello World");
//通过依赖注入的导航,为指导的区域创建用户控件
//Regions集合就是我们在View中定义的 prism:RegionManager.RegionName
this._regionManager.Regions["ContentRegion"].RequestNavigate(parameter, map);
}
在导航目标View
中绑定一个值,用于验证结果
<TextBlock FontSize="40" Text="{Binding Title}" />
在导航目标ViewModel
实现INavigationAware
接口
private string title;
public string Title
{
get { return title; }
set { SetProperty(ref title, value); }
}
/// <summary>
/// 每次重新导航的时候,是否重用原来的 View 和 ViewModel 实例
/// </summary>
/// <param name="navigationContext"></param>
/// <returns></returns>
public bool IsNavigationTarget(NavigationContext navigationContext)
{
return false;
}
/// <summary>
/// 拦截导航请求(从当前View跳到其它View时触发)
/// </summary>
/// <param name="navigationContext"></param>
public void OnNavigatedFrom(NavigationContext navigationContext)
{
}
/// <summary>
/// 获取参数
/// </summary>
/// <param name="navigationContext"></param>
public void OnNavigatedTo(NavigationContext navigationContext)
{
//一定要确保数据存在,否则会报错
//navigationContext.Parameters.GetValue<string>("Title");
//应该先判断
if (true == navigationContext.Parameters.ContainsKey("Title"))
{
this.Title = navigationContext.Parameters.GetValue<string>("Title");
}
}
关于拦截请求,还有一个方法
ViewModel
也可以继承IConfirmNavigationRequest
,这个接口也继承INavigationAware
接口
/// <summary>
/// 也是拦截请求,在 OnNavigatedFrom 之前执行
/// </summary>
/// <param name="navigationContext"></param>
/// <param name="continuationCallback"></param>
/// <exception cref="NotImplementedException"></exception>
public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
{
bool result = true;
if (MessageBox.Show("确认导航?", "提示", MessageBoxButton.YesNo) == MessageBoxResult.No)
{
result = false;
}
continuationCallback(result);
}
导航日志
修改MainView.xaml
,新增一个返回
按钮
<Window
x:Class="PrismDemo.Views.MainView"
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:local="clr-namespace:PrismDemo"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prism="http://prismlibrary.com/"
xmlns:viewmodels="clr-namespace:PrismDemo.ViewModels"
Title="MainWindow"
Width="800"
Height="450"
d:DataContext="{d:DesignInstance Type=viewmodels:MainViewModel}"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<Button
Width="100"
Height="30"
Margin="5"
Command="{Binding OpenCommand}"
CommandParameter="AView"
Content="打开A页面" />
<Button
Width="100"
Height="30"
Margin="5"
Command="{Binding OpenCommand}"
CommandParameter="BView"
Content="打开B页面" />
<Button
Width="100"
Height="30"
Margin="5"
Command="{Binding OpenCommand}"
CommandParameter="CView"
Content="打开C页面" />
<Button
Width="100"
Height="30"
Margin="5"
Command="{Binding BackCommand}"
CommandParameter="CView"
Content="返回" />
</StackPanel>
<ContentControl Grid.Row="1" prism:RegionManager.RegionName="ContentRegion" />
</Grid>
</Window>
修改MainViewModel
,新增命令
在OpenCommand
命令中添加导航日志,在BackCommand
命令执行返回
public class MainViewModel : BindableBase
{
private readonly IRegionManager _regionManager;
public MainViewModel(IRegionManager regionManager, IRegionNavigationJournal navigationJournal)
{
this._regionManager = regionManager;
this._navigationJournal = navigationJournal;
}
//导航日志
private IRegionNavigationJournal _navigationJournal;
private DelegateCommand<string> _openCommand;
public DelegateCommand<string> OpenCommand =>
_openCommand ?? (_openCommand = new DelegateCommand<string>(ExecuteOpenCommand));
void ExecuteOpenCommand(string parameter)
{
NavigationParameters map = new NavigationParameters();
map.Add("Title", "Hello World");
//通过依赖注入的导航,为指导的区域创建用户控件
//Regions集合就是我们在View中定义的 prism:RegionManager.RegionName
this._regionManager.Regions["ContentRegion"].RequestNavigate(parameter, callback =>
{
//跳转成功
if (true == (bool)callback.Result)
{
this._navigationJournal = callback.Context.NavigationService.Journal;
}
}, map);
}
private DelegateCommand _backCommand;
public DelegateCommand BackCommand =>
_backCommand ?? (_backCommand = new DelegateCommand(ExecuteBackCommand));
void ExecuteBackCommand()
{
if (true == this._navigationJournal.CanGoBack)
{
this._navigationJournal.GoBack();
}
}
}
效果
就是可以一直返回,我就懒得截图了
Prism 对话服务(弹窗)
我在ModuleA
中新建一个用户控件MyDialogView
<UserControl
x:Class="ModuleA.Views.MyDialogView"
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:local="clr-namespace:ModuleA.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodels="clr-namespace:ModuleA.ViewModels"
Width="500"
Height="200"
d:DataContext="{d:DesignInstance Type=viewmodels:MyDialogViewModel}"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<TextBlock
Grid.Row="0"
FontSize="20"
Text="提示" />
<TextBlock
Grid.Row="1"
VerticalAlignment="Center"
FontSize="50"
Text="Hello World" />
<StackPanel
Grid.Row="2"
HorizontalAlignment="Right"
Orientation="Horizontal">
<Button
Width="80"
Height="50"
Margin="10"
Command="{Binding ConfirmCommand}"
Content="确认"
FontSize="20" />
<Button
Width="80"
Height="50"
Margin="10"
Command="{Binding CancelCommand}"
Content="取消"
FontSize="20" />
</StackPanel>
</Grid>
</UserControl>
弹窗必须要有ViewModel
,实现IDialogAware
接口
public class MyDialogViewModel : BindableBase, IDialogAware
{
public MyDialogViewModel()
{
}
public string Title { get; set; }
public event Action<IDialogResult> RequestClose;
/// <summary>
/// 是否能够关闭
/// </summary>
/// <returns></returns>
public bool CanCloseDialog()
{
return true;
}
/// <summary>
/// 关闭弹窗时执行,一般用于传递操作结果和参数
/// 由于确定和取消按钮的事件,这里会再执行一次,但是不影响执行的结果
/// 所以这个函数可以不写东西
/// </summary>
public void OnDialogClosed()
{
//DialogParameters map = new DialogParameters();
//map.Add("Value", "Hello");
////通过 RequestClose 返回窗口操作的结果
//RequestClose?.Invoke(new DialogResult(ButtonResult.OK, map));
}
/// <summary>
/// 打开弹窗时执行,一般用于获取参数
/// </summary>
/// <param name="parameters"></param>
public void OnDialogOpened(IDialogParameters parameters)
{
if (true == parameters.ContainsKey("Title"))
{
this.Title = parameters.GetValue<string>("Title");
}
}
private DelegateCommand _confirmCommand;
public DelegateCommand ConfirmCommand =>
_confirmCommand ?? (_confirmCommand = new DelegateCommand(ExecuteConfirmCommand));
void ExecuteConfirmCommand()
{
DialogParameters map = new DialogParameters();
map.Add("Value", "Hello");
//通过 RequestClose 返回窗口操作的结果
RequestClose?.Invoke(new DialogResult(ButtonResult.OK, map));
}
private DelegateCommand _cancelCommand;
public DelegateCommand CancelCommand =>
_cancelCommand ?? (_cancelCommand = new DelegateCommand(ExecuteCancelCommand));
void ExecuteCancelCommand()
{
//通过 RequestClose 返回窗口操作的结果
RequestClose?.Invoke(new DialogResult(ButtonResult.No));
}
}
然后在ModuleAProfile
中添加注册弹窗
public class ModuleAProfile : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
//注册导航
containerRegistry.RegisterForNavigation<AView, AViewModel>();
//注册弹窗
containerRegistry.RegisterDialog<MyDialogView, MyDialogViewModel>();
}
}
MainView
添加一个按钮
<Button
Width="100"
Height="30"
Margin="5"
Command="{Binding OpenDialogCommand}"
CommandParameter="MyDialogView"
Content="打开弹窗" />
MainViewModel
添加命令和弹窗服务
//弹窗服务
private readonly IDialogService _dialogService;
private DelegateCommand<string> _openDialogCommand;
public DelegateCommand<string> OpenDialogCommand =>
_openDialogCommand ?? (_openDialogCommand = new DelegateCommand<string>(ExecuteOpenDialogCommand));
void ExecuteOpenDialogCommand(string parameter)
{
DialogParameters map = new DialogParameters();
map.Add("Title", "测试弹窗");
this._dialogService.ShowDialog(parameter, map, callback =>
{
//判断结果,并进行点击“确定”的后续操作
if (callback.Result == ButtonResult.OK)
{
if (true == callback.Parameters.ContainsKey("Value"))
{
string result = callback.Parameters.GetValue<string>("Value");
}
}
});
}
效果
根据打开弹窗的命令及参数,打开对应的弹窗,返回结果,再执行后续操作,降低耦合
Prism 发布订阅
功能上类似于MVVM Light
的Messenger
,使用Prism
的事件聚合器
在ModuleA
项目下创建一个文件夹Events
新建一个MessageEvent
,实现PubSubEvent<T>
,T
为消息的类型,可以为复杂类型
public class MessageEvent : PubSubEvent<string>
{
}
事件聚合器的使用方式是依赖注入
我这里就在MyDialogViewModel
中使用
private readonly IEventAggregator _aggregator;
public MyDialogViewModel(IEventAggregator aggregator)
{
this._aggregator = aggregator;
}
点击取消按钮就发布一个消息
void ExecuteCancelCommand()
{
//向 MessageEvent 发布一个 Hello 消息
this._aggregator.GetEvent<MessageEvent>().Publish("Hello");
//通过 RequestClose 返回窗口操作的结果
//RequestClose?.Invoke(new DialogResult(ButtonResult.No));
}
订阅消息
我在MyDialogView
中使用事件聚合器订阅消息
public MyDialogView(IEventAggregator aggregator)
{
InitializeComponent();
//这个参数就是消息
aggregator.GetEvent<MessageEvent>().Subscribe(arg =>
{
MessageBox.Show($"接收到消息:{arg}");
});
}
这样的效果就是,当界面创建的时候就会订阅事件,发布消息就会接收,并弹出提示
也可以取消订阅
private readonly IEventAggregator _aggregator;
public MyDialogView(IEventAggregator aggregator)
{
InitializeComponent();
this._aggregator = aggregator;
//这个参数就是消息
this._aggregator.GetEvent<MessageEvent>().Subscribe(this.ShowMessage);
}
private void ShowMessage(string arg)
{
MessageBox.Show($"接收到消息:{arg}");
//可以在这里取消订阅
this._aggregator.GetEvent<MessageEvent>().Unsubscribe(this.ShowMessage);
}
可以传递复杂对象,所以如果需要复用某个消息,可以给消息对象添加一个Name
或Token
属性用于判断是否执行
效果
懒得演示了
使用 Microsoft.Toolkit.Mvvm 传递参数
新建一个WPF
项目
在Microsoft.Toolkit.Mvvm
中,MVVM Light
的Messenger
变成了IMessenger
接口
IMessenger
接口有两个实现类WeakReferenceMessenger
和StrongReferenceMessenger
,区别大概是自动和手动回收对象,这里就不深入探究了
TestViewModel.cs
public class TestViewModel : ObservableObject
{
public TestViewModel()
{
//注册消息接收者
//第一个泛型为 消息类型
//第二个泛型为 Token类型
//第一个参数为消息接收者,一般就是 this,object 类型
//第二个参数为Token,泛型
//第三个参数是为事件处理器,有两个参数,消息接收者和消息
WeakReferenceMessenger.Default.Register<string, string>(this, "Token1", (recipient, message) => this.Show(message));
}
private void Show(string value)
{
MessageBox.Show(value);
}
}
主界面MainWindow.xaml
,只有一个按钮
<Window x:Class="WpfApp1.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:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Button Width="200" Height="50" Click="Button_Click">Click</Button>
</Grid>
</Window>
主界面绑定TestViewModel
,给按钮绑定一个事件,打开一个新的窗口TestView.xaml
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new TestViewModel();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
new TestView().ShowDialog();
}
}
新窗口TestView.xaml
,同样只有一个按钮
<Window
x:Class="WpfApp1.Views.TestView"
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:local="clr-namespace:WpfApp1.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="TestView"
Width="800"
Height="450"
mc:Ignorable="d">
<Grid>
<Button
Width="200"
Height="50"
Click="Button_Click">
Click
</Button>
</Grid>
</Window>
给按钮绑定一个事件,这里发送一个消息
public partial class TestView : Window
{
public TestView()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
//发送消息
//第一个泛型为 消息类型
//第二泛型为 Token类型
//第一个参数为消息,泛型
//第二个参数为Token,泛型
WeakReferenceMessenger.Default.Send<string, string>("我的消息", "Token1");
}
}
使用 MVVM Light 传递参数
新建一个WPF
项目,使用NuGet
安装MvvmLight
MvvmLight
是旧版的Microsoft.Toolkit.Mvvm
,所以代码很类似
MainWindow
,还是只有一个按钮
<Window
x:Class="WpfApp2.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:local="clr-namespace:WpfApp2"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<Grid>
<Button
Width="200"
Height="50"
Click="Button_Click">
Click
</Button>
</Grid>
</Window>
绑定TestViewModel
和事件
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new TestViewModel();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
new TestView().ShowDialog();
}
}
TestViewModel
public class TestViewModel : ViewModelBase
{
public TestViewModel()
{
//注册消息接收者
//泛型为 消息类型
//第一个参数为消息接收者,一般就是 this,object 类型
//第二个参数为Token
//第三个参数是为事件处理器,传递消息参数
Messenger.Default.Register<string>(this, "Token1", this.Show);
}
private void Show(string value)
{
MessageBox.Show(value);
}
}
TestView
<Window
x:Class="WpfApp2.Views.TestView"
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:local="clr-namespace:WpfApp2.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="TestView"
Width="800"
Height="450"
mc:Ignorable="d">
<Grid>
<Button
Width="200"
Height="50"
Click="Button_Click">
Click
</Button>
</Grid>
</Window>
绑定事件
public partial class TestView : Window
{
public TestView()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
//发送消息
//第一个泛型为 消息类型
//第一个参数为消息,泛型
//第二个参数为Token
Messenger.Default.Send<string>("我的消息", "Token1");
}
}
效果是一致的
总结
Prism
有三种传递参数的方式
NavigationParameters
:这个是使用RegisterForNavigation<TView,TViewModel>()
给注册导航的区域传递参数的方式,在ViewModel
中实现IConfirmNavigationRequest
接口,在OnNavigatedTo(NavigationContext navigationContext)
中接收参数DialogParameters
:这个是使用RegisterDialog<TView,TViewModel>()
给注册对话(弹窗)的区域传递参数的方式,在ViewModel
中实现IDialogAware
接口,在OnDialogOpened(IDialogParameters parameters)
中接收参数IEventAggregator
:这个是事件聚合器,不需要注册,在Events
目录下新建类,实现PubSubEvent<TMessage>
接口,在ViewModel
中使用依赖注入IEventAggregator
来使用,使用this._aggregator.GetEvent<TEvent>().Publish(TMessage消息对象);
发布消息,使用this._aggregator.GetEvent<MessageEvent>().Subscribe(arg=>{});
接收消息
MVVM Light
和Microsoft.Toolkit.Mvvm
是一样的
MVVM Light
:使用Messenger.Default.Register<TMessage>(this, Token, ()=>{});
注册消息,使用Messenger.Default.Send<TMessage>(Message, Token);
发送消息,通过注册消息的回调函数接收Microsoft.Toolkit.Mvvm
:使用WeakReferenceMessenger.Default.Register<TMessage, TToken>(this, Token, (recipient, message) => (message)=>{});
注册消息,使用WeakReferenceMessenger.Default.Send<TMessage, TToken>(Message,Token);
发送消息,通过注册消息的回调函数接收