【问题标题】:MVVM Solution structureMVVM 解决方案结构
【发布时间】:2013-08-24 06:33:00
【问题描述】:

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

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

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

【问题讨论】:

    标签: c# xaml mvvm


    【解决方案1】:

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

    这是一个错误的假设。

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

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

    关于依赖项

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

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

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

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

    【讨论】:

    • 您好 Erno,鉴于我的解决方案结构,我不确定消息代理是否会有所帮助。即使我将代理打包在自己的项目中,我相信我会在项目之间产生循环依赖,因为代理项目需要对 View 和 ViewModel 项目的引用,而 ViewModel 项目需要对代理项目的引用。您能否验证或提供一个示例,其中经纪人会在我目前拥有的解决方案结构中提供帮助?
    • 谢谢尔诺。你有任何样品可以链接我吗?
    • 只需寻找有关 MVVM Light Messenger 的教程,例如:dotnet.dzone.com/articles/mvvm-light-whats-messenger Googleing 也会产生很多结果
    • 谢谢尔诺。我使用了一个信使类来帮助导航并将项目分开。对此感兴趣的人使用此参考:jesseliberty.com/2011/01/06/…
    • 是的,Jesse 的博客是一个不错的资源。请注意,当您重命名视图(名称或命名空间)时,此特定解决方案中的代码很难重构
    【解决方案2】:

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

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

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

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

    【讨论】:

    • 我会等着看是否有办法为视图和视图模型创建单独的项目。如果不可能,那么 MVVM 似乎并不是真正的模块化架构,因为对视图的彻底检查仍然需要您在项目中进行修补;而在自己的项目中拥有视图将使此类大修变得更加容易。
    【解决方案3】:

    this post 中查看 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;
            }
        }
    }
    

    Factories 项目同时引用了 MyCompany.ViewModelsMyCompany.Views。 Views 项目只有对 MyCompany.ViewModels 的引用。

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

    然后我们通过将 ViewModel 引用传递给 View 的构造函数来启动 View。这称为Dependency Injection。您可能还需要为 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 控制其余部分。

    希望这会有所帮助:-)

    【讨论】:

      猜你喜欢
      • 2019-03-06
      • 1970-01-01
      • 2015-12-15
      • 2015-06-08
      • 2011-12-24
      • 2010-09-05
      • 2010-10-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多