使用带有棱镜的 MVVM 在视图之间切换

Posted

技术标签:

【中文标题】使用带有棱镜的 MVVM 在视图之间切换【英文标题】:Changing between views using MVVM with prism 【发布时间】:2011-08-11 16:26:19 【问题描述】:

我是 WPF 新手,但根据我的阅读,构建应用程序的正确方法是在同一窗口上切换视图。 我的意思是类似于带有菜单和显示视图的工作区的“框架”。

到目前为止,我一直在关注这个,http://jesseliberty.com/2011/01/06/windows-phone-from-scratch%E2%80%93mvvm-light-toolkit-soup-to-nuts-3/ 但这是针对 WP7 的,我无法在 WPF 应用程序上使用 NavigationService。

我可以说我想要的最简单的事情是,mainwindow.xaml 上有一个显示按钮的视图,当我按下该按钮时,我希望在同一个窗口上显示一个新视图(以及旧视图消失)。

实现类似的正确方法是什么?

编辑:这始于使用 mvvm-light,但最终演变为 prism。有关详细信息,请参阅我的上一个答案。

【问题讨论】:

这是我的答案,我解释了如何使用DataTemplateSelector:***.com/questions/5309099/… 执行此操作。在进行一些更改后,我的解决方案也可以应用于 Silverlight。 【参考方案1】:

这是一个很好的问题——当我开始使用 MVVM 时,我也有类似的问题。我只使用 MS 的 Prism/CAL 库。

我相信您正在寻找的是 CAL 中的区域的想法。它基本上是一个显示事物的命名容器控件。基本上,您可以在*** UI 片段中命名一个区域,例如您的主窗口。您的应用程序可能包含其中的一小部分:可能是页眉、页脚和主窗口区域。 (反正我就是这样做的。)。然后从后面的代码中,您可以通过区域管理器访问该区域,清除它,然后放入您的 ViewModel。 ViewModel 被映射到它的适当视图,瞧,新视图出现了。

从编码的角度来看,我通常看到它是这样分解的: 你有某种 NavigationController,它有一个或两个方法来清除区域并显示一个新的 ViewModel->View,还有像 GoToPageX() 这样的方法。这抽象了区域管理器。然后每个页面都有一个 ViewModel,每个页面都有一个 View。每个 ViewModel 通过依赖注入接收 NavigationController(但如果您不使用 DI,则可以创建新的)。然后在 ViewModel 上,它公开一个映射到按钮并调用 NavigationController 的 Command。

您还必须在某个地方注册 ViewModel 和用于显示它们的 View。

这是一个 NavigationController 的示例:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Practices.Prism.Regions;
using Microsoft.Practices.Unity;
using KSheets.CoreModule.Presenter;
using Zephyr.Core.Logging;
using KSheets.CoreSheets.Sheet;
using System.IO;

namespace KSheets.CoreModule.Switch

    public class Switchboard : ISwitchboard
    
        private ILoggingService<ISwitchboard> m_Logger;
        private IRegionManager m_RegionManager;
        private IUnityContainer m_UnityContainer;

        public Switchboard(
            ILoggingService<ISwitchboard> loggingService,
            IRegionManager regionManager,
            IUnityContainer unityContainer
            )
        
            if (loggingService == null) throw new ArgumentNullException("loggingService");
            if (regionManager == null) throw new ArgumentNullException("regionManager");
            if (unityContainer == null) throw new ArgumentNullException("unityContainer");

            m_RegionManager = regionManager;
            m_UnityContainer = unityContainer;
            m_Logger = loggingService;
        

        public void GoHome()
        
            m_Logger.Log("Going home");

            var worksheetEditor = m_UnityContainer.Resolve<IWorksheetEditor>();
            worksheetEditor.Initialize();
            LoadView(RegionNames.EditorRegion, worksheetEditor);

            var batchExporter = m_UnityContainer.Resolve<IExportBatchPresenter>();
            LoadView(RegionNames.ExporterRegion, batchExporter);
        

        private void LoadView(string regionName, object newView)
        
            var region = m_RegionManager.Regions[regionName]; 
            var oldViews = region.Views;
            foreach (var oldView in oldViews)
                region.Remove(oldView);
            region.Add(newView);
            region.Activate(newView);
        
    

这是一个以编程方式向 ViewModel 注册 View 的示例。许多人在 XAML 中执行此操作,但您也可以在代码中执行此操作,如果您使用依赖注入,IMO 会更好,因为您可以在加载模块时同时注册视图和依赖注入。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;

namespace Zephyr.WPF.Utils

    public static class ResourceUtils
    
        public static void RegisterView<T, U>()
        
            DataTemplate template = new DataTemplate();
            FrameworkElementFactory factory = new FrameworkElementFactory(typeof(U));
            template.DataType = typeof(T).FullName;
            template.VisualTree = factory;
            Application.Current.Resources.Add(new DataTemplateKey(typeof(T)), template);
        
    


        private void RegisterViews()
        
            ResourceUtils.RegisterView<WorksheetDisplay, WorksheetDisplayView>();
            ResourceUtils.RegisterView<ProblemSelector, ProblemSelectorView>();
            ResourceUtils.RegisterView<WorksheetEditor, WorksheetEditorView>();
            ResourceUtils.RegisterView<ExportBatchPresenter, ExportBatchView>();
        

归根结底,您需要一小段可感知 UI 的代码(或一个 UI 控件,它可以侦听从非 UI 代码发送的消息,该代码知道 small 位关于UI 像区域名称)并允许您将 ViewModel 粘合到位。但是,此代码通常非常简单,除了开箱即用的组件外,绝对不需要任何代码隐藏。当您实际上必须第一次在 WPF 中实现 MVVM 时,它需要考虑很多;启动和运行一个简单的应用程序的陡峭学习曲线。

【讨论】:

感谢您的热心回答!你是我想要的。我一直在阅读很多关于棱镜以及如何用它来做事的文章。我找到了这些很棒的系列。我认为它涵盖了所有基础知识。但是,我仍在尝试了解基础知识。我现在的另一个简单疑问是; - 我需要一个 regionManager - 我还需要一个 ViewModel 管理器;这叫做 IoC 对吧?哪个与服务定位器差不多?这两个管理器都可以通过 DI 或 mvvm-light 提供的“服务定位器”获得,对吗?对不起,菜鸟问题:) 谢谢! developmentalmadness.com/archive/2009/11/02/… 我说的系列... 是的,您正在寻找的是一个 IoC(控制反转)容器。是的,非常接近 ServiceLocator。我以前用过Unity,很流畅。任你选——我要说的只是我不知道我是否会从 MEF 开始,因为它完成了一些不同的事情。一旦你启动并运行一两个屏幕,整个事情就会变得更容易...... 您好!过去几天我一直在学习 prism,现在是时候使用您的交换机解决方案了。不幸的是,我不确定如何使用它。我正在为我的程序做一个模块化结构。我有一个接口,一个 shell 和两个其他模块(一个 loginModule 和一个 helloWorld,后者在成功登录后启动)。使用来自交换机的 loadView,我可以加载新视图并且效果很好,但前提是我创建了视图。我的问题是我是否应该放置交换机,因为他也需要创建新视图...如果这变得令人困惑,我很抱歉... 我忘了添加...循环依赖正在成为我试图解决这个问题的方式的一个问题。为什么?因为我已将交换机放在我的基础架构模块上,并添加了引用以从其他模块获取视图。这在一定程度上有效,但我觉得这是一种更好的方法。我认为这个问题是您显示寄存器视图部分的原因,但我不知道如何使用它......有人可以帮我吗?无论如何,非常感谢大家!【参考方案2】:

我是来补充J-Trana的答案。

他真的让我对 Prism 产生了好奇,我很高兴他做到了。这只是摇滚。 在过去的几周里,我一直在阅读它,观看文档和随附的快速入门示例。这些是我的主要参考资料。

那么,关于解决方案...

Prism Regions 确实是我想要的。他提供的 Switchboard 是我实现的关键,但我对其进行了一些调整。

首先我在其中传递了 UnityContainer,这样我可以获得更大的灵活性。 (或者我认为它给了我。) 其次,我创建了一个类似这样的方法 LoadModule(因为我有一个模块化结构)。

公共无效LoadModule(字符串模块) IModuleManager moduleManager = m_UnityContainer.Resolve(); moduleManager.LoadModule(模块);

据我所知,当这个 LoadModule 执行我的模块的 Initialize() 方法时,它们是这样的......

public void Initialize()
    

        Switchboard switchboard = Switchboard.GetSwitchboard();

        IUnityContainer container = switchboard.GetCatalog();

        switchboard.LoadView(RegionNames.ShellMainRegion, container.Resolve<HelloWorldView>());

    

这行得通!

一些值得注意的提示: - `//为视图提供上下文(ViewModel)的属性。

    [Dependency]
    public HelloWorldViewModel HelloWorldViewModel
    
        set
        
            this.DataContext = value;
        
    
将 ViewModel 连接到 View 作为 View 背后代码的属性。 this.Container.RegisterType(); 我以这种方式注册我的模块:

` Type HelloWorldType = typeof(HelloWorldModule); this.ModuleCatalog.AddModule(new ModuleInfo()

        

            ModuleName = ModuleNames.HelloWorldModule,

            ModuleType = HelloWorldType.AssemblyQualifiedName,

            InitializationMode = InitializationMode.OnDemand

        ); `

- 我的总机在我的基础设施模块上。视图创建被上传到拥有它的模块,这解决了我的问题。

我希望这不会太令人困惑,并且我真的很想了解我是如何做这些事情的……我这样做是“正确”的方式吗?有没有更优雅的方式? 诸如此类...

无论如何,我希望这能帮助并激励那些试图学习棱镜的人。

PS:这个代码标签是如何工作的?我可以更改标签吗? mvvm-light 不再有意义了。

【讨论】:

以上是关于使用带有棱镜的 MVVM 在视图之间切换的主要内容,如果未能解决你的问题,请参考以下文章

在 MVVM wpf 中切换视图

在 UserControl WPF MVVM caliburn 内的 UserControl 之间切换

wpf的棱镜vs mvvm灯

具有多个视图相同 ViewModel 的棱镜导航

命令绑定在带有棱镜的 WPF 中不起作用

使用 TabControl (MVVM) 显示不同的 ViewModel