使用带有 prism 的 MVVM 在视图之间进行更改

发布于 2024-11-03 11:17:59 字数 567 浏览 0 评论 0原文

我是 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。请参阅我的最后一个答案以了解更多详细信息。

I'm new to WPF, but from what I've read, a correct way to build applications is to switch up views on the same window.
What I mean is something like a "frame" with a menu and a workspace where views are shown.

So far I've been following this, http://jesseliberty.com/2011/01/06/windows-phone-from-scratch%E2%80%93mvvm-light-toolkit-soup-to-nuts-3/
but that is for WP7 and I can't use the NavigationService on a WPF app.

I could say that the simplest thing I want is, the mainwindow.xaml has a view on it that displays a button, when I press that button I want a new view to be displayed on the same window (and the old view to disappear).

What is the correct way of implementing something like that?

EDIT: This started by using mvvm-light, but eventually evolved to prism. See my last answer to further details.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

陌路黄昏 2024-11-10 11:17:59

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

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

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

在某个地方,您还必须将 ViewModel 与用于显示它们的视图一起注册。

这是一个 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 注册视图的示例。许多人在 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 代码发送的消息,该代码了解一点关于 UI 的信息,例如区域名称)并允许您将 ViewModel 粘合到位。但是,此代码通常非常少,并且除了开箱即用的组件之外,绝对不需要隐藏代码。当您第一次在 WPF 中实际实现 MVVM 时,需要了解很多内容;启动并运行一个简单的应用程序需要一个陡峭的学习曲线。

This is a great question - I had similar questions when I started using MVVM. I use just the Prism/CAL libraries from MS.

I believe what you're looking for is the idea of a Region in CAL. It's basically a named container control that presents things. Basically, you name a region in a top-level UI piece, like your main window. Your app probably has a small number of these: maybe a header, footer, and main window regions. (That's how I've done it anyways.). Then from code behind you can access the region through a region manager, clear it, and drop in your ViewModel. The ViewModel gets mapped to it's appropriate View and voila the new View appears.

From a coding standpoint, I've usually seen it broken up like this:
You have some sort of a NavigationController that has a method or two from clearing the region and showing a new ViewModel->View and also has methods like GoToPageX(). This abstracts the region manager. Then you've got a ViewModel for each page and a View for each page. Each ViewModel takes in the NavigationController via dependency injection (but you can create new ones if you're not using DI). Then on the ViewModel, it exposes a Command which gets mapped to the button and calls the NavigationController.

Somewhere you've also got to register the ViewModels with the Views used to show them.

Here's an example of a 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);
        }
    }
}

Here's an example of registering a View with a ViewModel programmatically. Many folks do this in XAML but you can also do it in code which IMO works better if you're using dependency injection as you can register your views and your dependency injection stuff all at the same time when your module loads.

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>();
        }

At the end of the day, you need a small bit of code that is UI aware (or a UI control that listens to messages sent from non-UI code that knows a small bit about the UI like region names) and allows you to glue the ViewModel into place. However, this code is usually super minimal and definitely requires no code-behind except for out of box components. MVVM is kind of a lot to take in when you actually have to implement it in WPF the first time; a steep learning curve for getting a simple app up and running.

秋风の叶未落 2024-11-10 11:17:59

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

他确实让我对棱镜产生了好奇,我很高兴他做到了。这太棒了。
在过去的几周里,我一直在阅读有关它的内容,观看文档以及它附带的快速入门示例。这些是我的主要参考资料。

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

棱镜区域确实是我想要的。他提供的总机是我实现的关键,但我对其进行了一些调整。

首先,我在其中传递了 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 连接到视图,作为视图背后代码的属性。
  • this.Container.RegisterType();
  • 我这样注册我的模块:

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

        {

            ModuleName = ModuleNames.HelloWorldModule,

            ModuleType = HelloWorldType.AssemblyQualifiedName,

            InitializationMode = InitializationMode.OnDemand

        }); `

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

我希望这不会太令人困惑,我真的想要一些关于我如何做这些事情的评论...我这样做的方式“正确”吗?有更优雅的方式吗?
像这样的东西......

无论如何,我希望这可以帮助和激励那些正在尝试学习棱镜的人。

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

I came here to complement the answer by J-Trana.

He really got me curious for the Prism and I'm glad he did. This just rocks.
I've spent the last few weeks reading about it, watching the docs and the quickstart examples that come with it. These were my main references.

So, about the solution...

Prism Regions was indeed what I wanted. The Switchboard provided by him was key in my implementation but I tweaked it a little.

First I passed the UnityContainer inside it, so I could get more flexibility. (Or so I think it gives me.)
Second, I've created a method LoadModule (because I have a modular structure) like this.

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

            moduleManager.LoadModule(module); 
}

From what I got, when this LoadModule executes the Initialize() method of a my modules, which are something like this...

public void Initialize()
    {

        Switchboard switchboard = Switchboard.GetSwitchboard();

        IUnityContainer container = switchboard.GetCatalog();

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

    }

And this works!

Some tips worth of note:
- `//The property that provides context(ViewModel) to the view.

    [Dependency]
    public HelloWorldViewModel HelloWorldViewModel
    {
        set
        {
            this.DataContext = value;
        }
    }
  • That connects a ViewModel to a View as a property on the Views' code behind.
  • this.Container.RegisterType();
  • I register my modules this way:

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

        {

            ModuleName = ModuleNames.HelloWorldModule,

            ModuleType = HelloWorldType.AssemblyQualifiedName,

            InitializationMode = InitializationMode.OnDemand

        }); `

- And my switchboard is on my infrastructure module. The View creation is shoot up to the module that has it and this solved my problem.

I hope this isn't too confusing, and I really would like some comments on how I'm doing this stuff... Am I doing this the "right" way? Is there a more elegant way?
Stuff Like that...

Anyway, I hope this helps and motivates people that are trying to learn prism.

PS: How does this code tag work? And can I change the tags? mvvm-light makes no sense anymore.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文