使用MVVM和棱镜在视图之间切换

时间:2011-04-23 23:58:06

标签: wpf mvvm view navigation prism

我是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开始的,但最终演变为棱镜。有关详细信息,请参阅我的上一个答案。

2 个答案:

答案 0 :(得分:2)

这是一个很好的问题 - 当我开始使用MVVM时,我遇到了类似的问题。我只使用MS的Prism / CAL库。

我相信你所寻找的是CAL中的Region的想法。它基本上是一个提供东西的命名容器控件。基本上,您可以在顶级UI片段中命名区域,就像主窗口一样。您的应用可能只有少量这些:可能是页眉,页脚和主窗口区域。 (无论如何,我就是这样做的。)然后,从后面的代码中,您可以通过区域管理器访问该区域,清除它并放入ViewModel。 ViewModel被映射到它的相应视图,并且显示新视图。

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

在某个地方,您还必须使用用于显示它们的视图注册ViewModels。

这是一个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时,MVVM会有很多东西需要考虑;一个陡峭的学习曲线,让一个简单的应用程序启动和运行。

答案 1 :(得分:1)

我来到这里是为J-Trana补充答案。

他让我对Prism感到好奇,我很高兴他做到了。这只是晃动。 过去几周我一直在阅读它,观看文档以及随之而来的快速入门示例。这些是我的主要参考资料。

所以,关于解决方案......

Prism Regions确实是我想要的。他提供的交换机是我实现的关键,但我稍微调整了一下。

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

public void LoadModule(string module) { 
            IModuleManager moduleManager = m_UnityContainer.Resolve(); 

            moduleManager.LoadModule(module); 
}

从我得到的,当这个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作为后面的Views代码的属性。
  • this.Container.RegisterType();
  • 我以这种方式注册我的模块:

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

        {

            ModuleName = ModuleNames.HelloWorldModule,

            ModuleType = HelloWorldType.AssemblyQualifiedName,

            InitializationMode = InitializationMode.OnDemand

        }); `

- 我的交换机在我的基础架构模块上。视图创建是拍摄到具有它的模块,这解决了我的问题。

我希望这不会太令人困惑,我真的想对我如何做这些事情发表一些意见......我这样做是对吗?有更优雅的方式吗? 这样的东西......

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

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