1

我想了解构建 MVVM 解决方案的最佳实践。

目前我已经为我的 View 和我的 ViewModel 创建了一个单独的项目。我的 View 项目引用了我的 ViewModel 项目。到目前为止,一切都很好。在实现导航时,我的 ViewModel 类需要访问 RootFrame 才能进行导航,但 RootFrame 位于 View 项目中的 App.xaml 中。所以我在这里有一个循环依赖问题。

我应该使用推荐的结构吗?我可以将这一切归为一个大项目,但为了将 View 和 ViewModel 解耦,我觉得拥有单独的项目是一种更好的方法。

4

3 回答 3

3

在实现导航时,我的 ViewModel 类需要访问 RootFrame

这是一个错误的假设。

您可以使用消息代理(单个对象)负责在发布者(ViewModel)和订阅者(​​一些负责打开视图的对象)之间分发消息。

大多数 MVVM 框架都有这样的代理。

关于依赖

Broker 的唯一职责是引发事件。所以,一般来说,它公开了几个可以被发布者调用的方法和几个可以被订阅者注册的事件。

在 MVVM 中,您可以使用此机制让 ViewModel 引发表明应打开 View 的事件以及订阅此事件的 View Manager。View Manager 应该能够实例化 View 并附加正确的 ViewModel。

为了防止 ViewManager 需要对所有 Views 和 ViewModels 的引用,您可以传递给事件逻辑名称(只是一个字符串)并让 View Manager 通过使用反射或配置文件中的静态列表来查找匹配的 View(Model) 类型.

这样您就不需要任何循环引用引用。事实上,当您发现您需要与 MVVM 中的正确依赖关系相反的引用时,您应该首先怀疑设置,然后考虑为 View 和/或 ViewModels 使用基类。

于 2013-08-24T07:11:19.703 回答
1

MVVM 没有最佳实践,因为它是一种设计模式,每个人都根据自己的喜好以不同的方式实现。我见过很多不同的实现,但从未在单独的项目中看到视图和视图模型。我建议将它们保存在同一个项目的不同文件夹中,并将它们放在不同的命名空间中。

例如,您的视图模型可以进入 ViewModels 文件夹并位于命名空间 MyProject.ViewModels

您的视图可以进入 Views 文件夹并位于命名空间 MyProject.Views

如果您正在使用模型,它们也是如此

于 2013-08-24T06:53:31.933 回答
0

在这篇文章中查看 Rachel 的好答案。我也喜欢将我的视图和视图模型分开,因为这样我就知道我什么时候搞砸了 MVVM 基本规则。

您的 ViewModel 不应该有任何对 View 的引用,但 View 必须有对 ViewModel 的引用。例如,考虑我的自定义 SplashScreen 工厂(两个重要的行是“ var viewModel... ”和“ var splashScreen... ”):

namespace MyCompany.Factories
{
    using System.Threading;
    using MyCompany.Views;
    using MyCompany.ViewModels;

    public static class SplashScreenFactory
    {
        public static SplashScreenViewModel CreateSplashScreen(
            string header, string title, string initialLoadingMessage, int minimumMessageDuration)
        {
            var viewModel = new SplashScreenViewModel(initialLoadingMessage, minimumMessageDuration)
            {
                Header = header,
                Title = title
            };

            Thread thread = new Thread(() =>
            {
                var splashScreen = new SplashScreenView(viewModel);
                splashScreen.Topmost = true;
                splashScreen.Show();

                splashScreen.Closed += (x, y) => splashScreen.Dispatcher.InvokeShutdown();

                System.Windows.Threading.Dispatcher.Run();
            });

            thread.SetApartmentState(ApartmentState.STA);
            thread.IsBackground = true;
            thread.Start();

            return viewModel;
        }
    }
}

Factory 项目引用了MyCompany.ViewModelsMyCompany.Views。Views 项目只有对MyCompany.ViewModels的引用。

我们首先从调用方启动 ViewModel(在这种情况下,从 SplashScreenFactory 启动;或者如果您愿意,也可以从 App.xaml.cs 启动;出于本讨论之外的原因,我更喜欢使用我自己的 Bootstrapper 类)。

然后我们通过将 ViewModel 引用传递给 View 的构造函数来启动 View。这称为依赖注入。您可能还需要为 Window 类编写一个 Behavior,以便从您的 ViewModel 中关闭窗口。所以现在我可以从我的 Bootstrapper 类中执行以下操作:

/// <summary>
/// Called from this project's App.xaml.cs file, or from the Deals Main Menu
/// </summary>
public class Bootstrapper
{
    private SplashScreenViewModel _splashScreenVM;

    public Bootstrapper()
    {
        // Display SplashScreen
        _splashScreenVM = SplashScreenFactory.CreateSplashScreen(
            "MyCompany Deals", "Planning Grid", "Creating Repositories...", 200);

        // Overwrite GlobalInfo parameters and prepare an AuditLog to catch and log errors
        ApplicationFactory.AuditedDisplay(
            Assembly.GetExecutingAssembly().GetName(),
            () =>
            {
                // Show overwritten version numbers from GlobalInfo on SplashScreen
                _splashScreenVM.VersionString = string.Format("v{0}.{1}.{2}",
                    GlobalInfo.Version_Major, GlobalInfo.Version_Minor, GlobalInfo.Version_Build);

                // Initiate ViewModel with new repositories
                var viewModel = new PlanningGridViewModel(new MyCompany.Repositories.PlanningGridHeadersRepository(),
                    new MyCompany.Repositories.PlanningGridLinesRepository(),
                    _splashScreenVM);

                // Initiate View with ViewModel as the DataContext
                var view = new PlanningGridView(viewModel);

                // Subscribe to View's Activated event
                view.Activated += new EventHandler(Window_Activated);

                // Display View
                view.ShowDialog();
            });
    }

    /// <summary>
    /// Closes SplashScreen when the Window's Activated event is raised.
    /// </summary>
    /// <param name="sender">The Window that has activated.</param>
    /// <param name="e">Arguments for the Activated event.</param>
    private void Window_Activated(object sender, EventArgs e)
    {
        _splashScreenVM.ClosingView = true;
    }

请注意,我可以通过订阅 View 的 Activated 事件来控制 SplashScreen 的 View,然后使用“ClosingView”布尔 INotifyProperty 关闭视图,该 INotifyProperty 依次设置 ​​View 的“Close”DependencyProperty(听起来很复杂,但一旦你了解了 DependencyProperties ,它变得简单)。

关键是,不要试图从 RootFrame 驱动您的导航。只需 view.Show() RootFrame,然后从 RootFrameViewModel 控制其余部分。

希望这可以帮助 :-)

于 2013-10-04T10:17:47.953 回答