WPF/Prism:从两个不同的视图访问同一个虚拟机实例

WPF/Prism: Access to the same VM-instance from two different views

我目前正在处理我的第一个 Prism 项目,但遇到了以下问题:

我的项目由两个区域组成,一个 ContentRegion 和一个 MenuRegion,每个区域都应该访问相同的 ViewModel 实例。

在 MenuRegion 中,活动 ViewModel 的一些方法应该是可选的。 在下面的示例中,应该能够从 ContentRegion 和 MenuRegion 中触发 Save 方法。

问题是我最初创建了两个不同的 ContentAViewModel 实例,菜单的 Save 方法无法访问我的 ContentRegion 的当前数据。

我试过将 ViewModels 注册为单例,但不幸的是这不起作用并且可能违反了 Prism 原则。

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

        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
            
        }

        protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
        {
            moduleCatalog.AddModule<ModuleA>();
        }
}
public class MainWindowViewModel : BindableBase
{
        private readonly IRegionManager _regionManager;

        public MainWindowViewModel(IContainerExtension container, IRegionManager regionManager)
        {
            _regionManager = regionManager;

            _regionManager.RequestNavigate("ContentArea", "ContentAView");
            _regionManager.RequestNavigate("MenuArea", "MenuA");
        }
}

<Window
    x:Class="PrismProject.Views.MainWindow"
    xmlns:prism="http://prismlibrary.com/"
    AllowsTransparency="True"
    Background="Transparent"
    xmlns:core="clr-namespace:PrismProject.Core;assembly=PrismProject.Core"
    ResizeMode="CanResizeWithGrip"
    WindowStyle="None">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>

       <Grid>
           <Grid.ColumnDefinitions>
               <ColumnDefinition Width="50" />
               <ColumnDefinition Width="300" />
               <ColumnDefinition Width="*" />
           </Grid.ColumnDefinitions>

          <!--[...]-->

           <ContentControl
               Grid.Column="1"
               Panel.ZIndex="10"
               prism:RegionManager.RegionName="{x:Static core:RegionNames.MenuRegion}" />

          <!--[...]-->
       </Grid>
       
       <ContentControl
           Grid.Row="1"
           prism:RegionManager.RegionName="{x:Static core:RegionNames.ContentRegion}" />
</Window>

public class ModuleA : IModule
{
        private readonly IRegionManager _regionManager;

        public CalculatingModule(IRegionManager regionManager)
        {
            _regionManager = regionManager;
        }

        public void OnInitialized(IContainerProvider containerProvider)
        {
            _regionManager.RegisterViewWithRegion(RegionNames.ContentRegion, typeof(ContentAView));
            _regionManager.RegisterViewWithRegion(RegionNames.ContentRegion, typeof(ContentBView));
            _regionManager.RegisterViewWithRegion(RegionNames.MenuRegion, typeof(MenuA));
            _regionManager.RegisterViewWithRegion(RegionNames.MenuRegion, typeof(MenuB));
        }

        public void RegisterTypes(IContainerRegistry containerRegistry)
        {
            //containerRegistry.RegisterSingleton<ContentAViewModel>();
            //containerRegistry.RegisterSingleton<ContentBViewModel>();

            ViewModelLocationProvider.Register<MenuA, ContentAViewModel>();
            ViewModelLocationProvider.Register<MenuB, ContentBViewModel>();
        }
}
public class ContentAViewModel: RegionViewModelBase
{
        private readonly IRegionManager _regionManager;

        public ContentAViewModel(IRegionManager regionManager) : base(regionManager)
        {
            SaveCommand = new(Save);
            _regionManager = regionManager;
        }
        
        private void Save()
        {
            // logic
        }
}

<UserControl
    x:Class="PrismProject.Modules.ModuleA.Views.ContentAView"
    xmlns:prism="http://prismlibrary.com/"
    prism:ViewModelLocator.AutoWireViewModel="True">
    <Grid>
       <Button 
          Command="{Binding SaveCommand}"
          Content="Save"/>

          <!--[...]-->
    </Grid>
</UserControl>
<UserControl
    x:Class="PrismProject.Modules.ModuleA.Menus.MenuA"
    xmlns:prism="http://prismlibrary.com/"
    prism:ViewModelLocator.AutoWireViewModel="True">
    <Grid>
       <Menu>                
          <MenuItem
              Command="{Binding SaveCommand}"
              Header="Save"/>

          <!--[...]-->
       </Menu>
    </Grid>
</UserControl>

如何在仅使用一个 ViewModel 实例的情况下将同一个 ViewModel 分配给多个视图?

我必须做同样的事情。我使用了 Brian Lagunas(Prism 的作者之一)在他的一个在线视频中展示的一种优雅的技术。来自一个优秀的教程网站,我不会命名以免听起来像托儿,但如果您 google“Prism 问题和解决方案:加载相关视图”,您会找到它。我强烈推荐观看它。但是我会在这里给你概述这个技术。

(在我的例子中,我模块的每个主要视图都需要与 Prism 需要自动放入我定义的另一个区域的“工具”视图共享其视图模型)

  1. 创建一个属性(我称之为 PageToolAttribute)以“伴随”视图的名称命名
  2. 将属性应用于您的“主”视图,该视图应与伴随视图共享其 view-model。提供您希望在其他 Prism 区域中显示的“伴随视图”的名称。
  3. 创建一个派生自 Prism 的 RegionBehavior class 的 class,它(在其对 OnAttach 的覆盖中)挂钩到 Prism 区域的 ActiveViews.CollectionChanged 事件。在处理程序中,您询问新添加的视图是否具有 PageToolAttribute.
  4. 如果新添加的视图 支持该属性,您的事件处理程序将获取它的名称并创建它的新实例,并赋予它与 DataContext 相同的属性视图(即您要共享的 view-model),最后将其他区域导航到新添加的视图。
  5. 最后,在您的 Prism Aap 中,您覆盖 ConfigureDefaultRegionBehaviors 函数并调用 AddIfMissing(在提供的 IRegionBehaviorFactory 上)以添加您的属性。

要接受的内容很多,需要对 Prism 进行一些深入的研究,但代码量却出乎意料地少,而且它的工作原理非常出色。我现在有 7 个不同的模块,每个模块都有自己的配套“工具”视图,每次都会导航。