这是一个很好的问题——当我开始使用 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 时,它需要考虑很多;启动和运行一个简单的应用程序的陡峭学习曲线。