WPF MVVM Prism 扩展

记录以下学习过程:https://www.bilibili.com/video/BV1nY411a7T8

创建 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
默认的规则是ViewsViewModels文件夹下分别存放ViewViewModel

  • 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 LightMessenger,使用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);
}

可以传递复杂对象,所以如果需要复用某个消息,可以给消息对象添加一个NameToken属性用于判断是否执行

效果

懒得演示了

使用 Microsoft.Toolkit.Mvvm 传递参数

新建一个WPF项目
Microsoft.Toolkit.Mvvm中,MVVM LightMessenger变成了IMessenger接口
IMessenger接口有两个实现类WeakReferenceMessengerStrongReferenceMessenger,区别大概是自动和手动回收对象,这里就不深入探究了
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 LightMicrosoft.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);发送消息,通过注册消息的回调函数接收
posted @ 2022-06-11 15:55  .NET好耶  阅读(590)  评论(0编辑  收藏  举报