Prism/MVVM (MEF/WPF):从模块中公开导航 [例如菜单]

Posted

技术标签:

【中文标题】Prism/MVVM (MEF/WPF):从模块中公开导航 [例如菜单]【英文标题】:Prism/MVVM (MEF/WPF): Exposing navigation [Menu's for example] from modules 【发布时间】:2011-05-09 21:19:01 【问题描述】:

我开始使用 MEF 和 WPF 首次涉足 Prism v4/MVVM 世界。我已经成功构建了一个 shell,并且使用 MEF,我能够发现和初始化模块。但是,我不确定为这些模块公开的视图提供导航的正确方法。

例如,假设其中一个模块公开了三个视图,我想在菜单控件上显示到这些视图的导航。到目前为止,我已经成功地公开了一个基于MenuItem 的视图,并且这个MenuItem 包含子MenuItem 控件,从而提供了一个可以使用的命令层次结构。太好了。

问题是,这感觉不对。我现在在我的模块中声明导航(以及外壳)必须支持使用菜单。如果我想改用ToolBar 甚至Ribbon 怎么办。然后我将不得不更改我的所有模块以公开外壳的相应控件类型。

我环顾四周,在一些网站上提到使用“服务”来提供导航,在模块初始化期间,导航选项被添加到服务中,而该服务又被 shell 用来显示这个以它想要的任何格式导航(ToolBarTreeViewRibbonMenuItem 等) - 但我找不到任何实际这样做的例子。

为了透视所有这些,我最终希望能够从菜单和/或其他导航控件(可能是Ribbon)中选择视图,然后在 TabControl 中按需打开这些视图。我已经能够在模块初始化时在TabControl 中创建视图,现在我需要下一步。

我需要知道的是:什么是公开导航选项的正确方法,而不是坚持由 shell 支持特定控件,如果服务是要走的路,那么如何有人会将其放在 Prism/MVVM 模式中吗?

提前感谢您提供的任何见解。

【问题讨论】:

【参考方案1】:

我想你有一个包含通用接口的主模块。 您可以创建一个简单的界面,例如

public interface IMenuService 
    void AddItem(string name, Action action);
    IEnumerable<MenuItemViewModel> GetItems  get; 

创建 1 个实现和单个实例。

public class MenuService : IMenuService 

    private readonly IList<MenuItemViewModel> items = new List<MenuItemViewModel>();

    void AddItem(string name, Action action) 
        items.Add(new MenuItemViewModel 
            Name = name,
            Action = action
        );
    

    IEnumerable<MenuItemViewModel> GetItems 
        get  return list.AsEnumerable(); 
    

在您的模块中,使用 MEF 解析此实例并调用 AddItem() 来注册您的视图。 Action 属性是激活视图或执行其他任何操作的简单委托。

然后在您的 shell 或任何视图中,您只需要调用 GetItems 属性来填充您的菜单。

【讨论】:

我有点喜欢这个,因为它是一个完全通用的选项,让外壳决定如何显示项目。也就是说,我对这个主题有进一步的想法,实际上已经走了另一条路线,我将作为单独的答案记录下来。谢谢。 不要忘记您可以将MenuItemViewModel 安装到 MenuItems(可检查或具有子项)。您将不得不制作一个更具体的界面。但这是一个例子。玩得开心。【参考方案2】:

在考虑了更多之后,我得出了以下结论,我觉得这会影响我处理这个问题的方式......

无论如何,模块都需要部分了解 shell 布局 - 也就是说,shell 公开了许多区域,并且模块需要了解这些区域(按名称以及预期显示的内容)以便在请求功能时正确填充它们(通过在区域内注册视图或作为对用户操作的反应)。

因此,模块需要设计为与 shell 交互以将内容放置到命名区域中,因此,我认为模块不应该公开 shell 支持的任何类型的导航是没有理由的。

因此,我的模块(当前)公开了一个“RibbonView”(一个 RibbonTab),其中包含必要的图标、按钮和命令等,以公开模块的功能。每个“RibbonView”都注册到外壳的“RibbonRegion”,以及排序提示,然后在外壳中呈现。

如果将来我选择更新我的 shell 以使用最新的+最好的导航控件(无论可能在 x 年的时间内),那么我只需要更新每个模块以公开与新导航集成的必要项目,因为我正在加载到一个新的 shell,然后我可以相应地更新我的视图注册。

我只是希望我这样做不会违反复合应用程序的任何原则,但这表示我还没有找到一种可以在没有一些“解释”的情况下在实际场景中实际实现的模式。

我很想知道是否有人对此有任何意见。

【讨论】:

【参考方案3】:

我也遇到过同样的情况,我认为解决方案在于区分接口和实现。例如,您可以在执行给定功能的模块中设计视图。这就是它所做的一切。一旦您在特定上下文中使用或使用它,您就已经跨入了实现。现在,理想情况下,视图不知道它是如何实现的,当然也不知道 Shell 中的命名区域。因此,在一个模块中将视图放入区域中是不允许的。

为了解决这个问题,我选择将此责任委托给第三方组件 LayoutManager。 LayoutManager 位于 Shell 和 Module 之间,并定义“去哪里”。它是一个具体的实现,并且真正定义了实现。 Shell 和 Module 视图都保持通用。

看看:http://rgramann.blogspot.com/2009/08/layout-manager-for-prism-v2.html

这可能会给你一些关于这个问题的想法。

希望对你有帮助。

【讨论】:

我完全同意,通过了解 shell 区域,我的模块与特定的实现相关联,但到目前为止,我一直在努力寻找如何规避这一点的想法。尽管您的文章没有直接解决我的情况(您只有一个区域,并且您完全了解正在加载的视图以便将它们放入您的配置中),但它给了我一些关于如何绕过的想法问题。谢谢。【参考方案4】:

article 使用抽象 (IMenuItem) 来表示您的菜单选择的 ViewModel。您如何实际呈现这些导入的对象实际上取决于主机应用程序。该示例使用 WPF 菜单,但您当然可以以任何您想要的方式呈现它,因为 IMenuItem 已经足够抽象了。

如果您将IMenuItem 更改为INavigationItem,您将得到您想要的。

在那篇文章中,当特定导航项被通知它已“运行”时,它通常会为文档或“pad”实例化一个 ViewModel,并将其传递给 ILayoutManager 服务。它具有可插拔的架构,因此您可以将 LayoutManager 服务换成不同的布局引擎(默认是 AvalonDock 的包装器)。

【讨论】:

以上是关于Prism/MVVM (MEF/WPF):从模块中公开导航 [例如菜单]的主要内容,如果未能解决你的问题,请参考以下文章

WPF MVVM,Prism,Command Binding

在 WPF PRISM/MVVM 应用程序中避免内存泄漏的最佳方法是啥

Prism - MVVM模式下,StackPanel中增加和删除View(UserControl)

Prism MVVM:“没有为类型定义无参数构造函数”将 ViewModel 绑定到 View 时出错

Prism BindableBase 和 Commands 的介绍

Prism框架的优点