【问题标题】:How to do multiple shells in my Prism app (like MS Office)?如何在我的 Prism 应用程序(如 MS Office)中执行多个 shell?
【发布时间】:2011-07-13 17:10:26
【问题描述】:

我尝试创建一个具有 MS Office 窗口行为的应用程序,例如 Word/Excel。用户打开应用程序,点击新建时,将出现一个与应用程序外观相同的全新窗口。

到目前为止我发现的最接近的是:Link

但是,这里的 shell 是在应用程序启动时显示的。如何通过命令来做到这一点,或者可能有完全不同的方式来实现这一点?

编辑: 我现在还发现了以下内容:Link,但是在哪里以及如何调用此代码?

【问题讨论】:

    标签: wpf mvvm prism


    【解决方案1】:

    创建多个 shell 是正确的想法。您只需要妥善处理细节。

    何时以及如何创建新的 Shell

    当然,Prism 方法是让DelegateCommand 处理新外壳的创建。考虑到这个命令严格来说并不属于任何特定的 ViewModel(我会说它具有应用程序范围的范围),对我来说,拥有一个带有 CreateNewShellCommand 静态属性的 public static class ApplicationWideCommands 感觉更好。然后,您可以使用 {x:Static} 从 XAML 绑定到它,或者根据需要从代码隐藏中执行它。

    这个命令需要处理两件事:

    1. 创建新的Window(实际上是Shell
    2. 为新 shell 实例化一个新的IRegionManager,这样现有 shell 中的区域与新 shell 中的区域之间的区域名称不会冲突
    3. 指示新 shell 中的区域属于新的IRegionManager

    我会先解决这个问题,因为它更容易解释。

    给你的新 Shell 一个新的 RegionManager

    在 Prism 中声明区域时,除了区域名称之外,您还可以声明要使用的区域管理器。通常你不需要这样做,但在这里我们需要选择使用哪个RegionManager,因为区域名称在单个区域管理器的范围内必须是唯一的。由于区域名称是在视图的 XAML 中硬编码的,并且以另一种方式分配它们会很痛苦,我们需要更改等式的另一半:每个 shell 使用的区域管理器实例。所以在Shell.xaml里面可能会有这样的东西:

    <ContentControl
      regions:RegionManager.RegionManager="{Binding RegionManager}"
      regions:RegionManager.RegionName="ExampleRegion"
    />
    

    这将指示每个 shell 中的“WorkspaceRegion”属于绑定提供的IRegionManager。由于shell通常没有DataContext,我们可以在shell类本身中声明RegionManager属性:

    public partial class Shell : Window
    {
        public Shell(IRegionManager regionManager)
        {
            this.RegionManager = regionManager;
            InitializeComponent();
        }
    
        public IRegionManager RegionManager { get; private set; }
    }
    

    所以现在我们只需要确保每个Shell 实例都有自己的RegionManager。对于“第一个”shell,这将由BootStrapper 完成。 (下面的代码使用 DI 容器来解析对象,示例使用UnityContainer。如果您使用 MEF 进行依赖注入,只需在心理上转换为等效代码即可。)

    protected override DependencyObject CreateShell()
    {
        // I am assuming you have a reference to the DI container
        var regionManager = this.Container.Resolve<IRegionManager>();
        return new Shell(regionManager);
    }
    

    对于其他shell,将由CreateNewShellCommand

    private static ExecuteCreateNewShellCommand()
    {
        // I am assuming you have a reference to the DI container
       var regionManager = this.Container.Resolve<IRegionManager>();
       ver newRegionManager = regionManager.CreateRegionManager();
       var shell = new Shell(newRegionManager);
    
       // The rest is easy, for example:
       shell.Show();
    }
    

    这里有一个重要的警告: RegionManager 作为单例注册到容器中。这意味着每当您解析IRegionManager 您将返回相同的实例。出于这个原因,我们通过调用IRegionManager.CreateRegionManager 方法创建一个新实例(这适用于 Prism v4;我不确定 v2)。

    此时,您知道如何创建任意数量的新 Shell 实例并相应地连接区域。

    UI 构成细节

    您需要注意的最后一个细节是托管在每个 shell 中的所有区域,无论其可视化树有多深,都必须绑定到同一个 RegionManager

    这意味着您必须像我们在上面的ContentControl 示例中那样为应用程序中所有视图中的所有区域显式设置要使用的区域管理器。幸运的是,这很容易做到,因为:

    1. 所有视图最终都将成为视觉树中 Shell 的后代
    2. Shell 已经将正确的RegionManager 公开为属性,因此我们可以绑定到它

    你会这样做:

    <ItemsControl
      regions:RegionManager.RegionManager="{Binding RegionManager, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Shell}}}"
      regions:RegionManager.RegionName="AnotherRegion"
    />
    

    一切就绪!

    你现在应该准备好了。

    【讨论】:

    • 谢谢,帮了大忙,下面是我的实现
    • 这个答案有一些事实上的不准确之处......“UI 组成细节”部分是不必要的。将区域名称分配给控件时,将自动创建一个区域。创建的区域会自动添加到逻辑树上离该区域最近的区域管理器。这就是作用域区域管理器的工作方式。因此,您需要做的就是确保您的新 shell 实例有自己的区域管理器。或者在每个 shell 中创建一个唯一命名的区域,并带有一个作用域区域管理器实例。无论哪个漂浮你的船。
    • 我可以看到这将如何为 shell 分配一个新的区域管理器,但我不太清楚这将如何帮助。我的理解是,一个模块通常会在构建它(模块)时向传递给它的区域管理器注册其视图。因此,我认为您需要为传入唯一创建的区域管理器的每个 shell 初始化正确的模块。这是如何工作的?
    • 我遵循了与@Jon 提到的完全相同的步骤,但与 shell2 绑定的模块存在问题。在模块中,我将模块与区域相关联。例如, public void Initialize() { var menuRegion = this._regionManager.Regions["MenuRegion"]; menuRegion.Add(new MenuView(), "MenuRegion"); }。当我将模块添加到模块目录时,此代码会自动执行。不幸的是,我的第二个外壳还没有准备好/显示,这会引发异常。我该如何处理这种情况?
    • 您的解决方案将有效,但仅适用于单个组装。此外,它破坏了 IoC,因为命令单例需要引用 shell。这样,就不可能将模块拆分为单独的程序集,因为这会导致与外壳程序集的循环引用。为了消除耦合,管理新 shell 的命令服务应该被注入到模块中。
    【解决方案2】:

    这里是我现在用来创建具有多个 EventAggregator 的多个 Shell 的实现。我在引导程序中传递容器。在我的特殊情况下,子模块侦听事件以获取此 Shell 操作的路径参数。然后子模块可以根据参数详细信息在路径位置创建节点数据文件或从路径位置加载。

    public static class AppCommands
        {
            public static Container;
    
            public static ICommand NewCommand = new DelegateCommand(CreateShell);
    
            private static void CreateShell(object state)
            {
                var regionManager = Container.Resolve<IRegionManager>();
                var newRegionManager = regionManager.CreateRegionManager();
                var neweventAggregator = new EventAggregator();
                Container.RegisterInstance<EventAggregator>(neweventAggregator);
                var shell = new Shell(newRegionManager, neweventAggregator);
    
                shell.Show();
                SomeEventParameter parameter = new SomeEventParameter ();
                //Add sth to the parameter here
    
                neweventAggregator .GetEvent<SomeEvent>().Publish(parameter);
            }
        }
    

    【讨论】:

      猜你喜欢
      • 2016-02-20
      • 1970-01-01
      • 1970-01-01
      • 2018-08-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多