【问题标题】:Decouple the screens without magic strings解耦没有魔术字符串的屏幕
【发布时间】:2011-01-15 10:41:01
【问题描述】:

我的 WPF 项目将这样组织:

Screens
   Group1
      Screen1
         View.xaml
         ViewModel.cs
   Group2
      Screen2
         View.xaml
         ViewModel.cs

为了从Screen2 中显示Screen1,我将使用如下内容:ScreenManager.Show("Group1.Screen1") 这在Screens.Group1.Screen1 命名空间中查找(使用反射)View 和 ViewModel 并实例化它们。

如何在不耦合Screen1Screen2 的情况下消除魔术字符串(我不希望Screen2 中的类使用Screen1 命名空间)。我也想要某种屏幕发现(自动完成/智能感知)

或者也许通过某种方式(自动测试)来验证对ScreenManager.Show 的所有调用都是有效的。

更新: 我想出了这个:

public class ScreenNames
{
    public Group1Screens Group1;

    public class Group1Screens
    {
        public ScreenName Screen1;
    }
}

public sealed class ScreenName
{
    private ScreenName() { }
}

public class ScreenManager : IScreenManager
{
    public void Show(Expression<Func<ScreenNames, ScreenName>> x) {}
}

用法:

screenManager.Show(x=>x.Group1.Screen1);

不理想,但我认为违反 DRY 仍然比魔术字符串好。而且我可以自动测试(通过反射)所有调用都是有效的。

【问题讨论】:

  • 为什么 Screen2 需要知道 Screen1?屏幕管理器不存在于每个屏幕之外吗?通过 Intellisense,您是说在开发过程中,当您开始键入 ScreenManager.Show() 时,您希望每个屏幕的名称都出现在下拉列表中?屏幕列表是静态的还是动态的(在运行时加载)?
  • 最终我会传递一个参数; ScreenManager 将是 ViewModel 的一个属性;对于智能感知,我认为静态列表是必须的:/ 我想我可以有这样的东西:ScreenManager.Show(x=>x.Group1.Screen1)
  • ScreenManager.Show(x=>x.Group1.Screen1) 之类的用法意味着我必须保留和维护单独的屏幕列表,但我认为如果我想要智能感知,没有其他办法
  • 酷,我喜欢用 lambda 表达式获取 Intellisense 的方法。

标签: c# wpf architecture mvvm magic-string


【解决方案1】:

您不需要 WPF 中的所有 ScreenManager 内容,因为 DataTemplate 引擎可以使用纯标记为您处理这些。

您可以使用 ContentPresenter 和一堆 DataTemplate 简单地对应用程序的特定区域进行数据绑定。将该区域绑定到“根”ViewModel 的属性,并让“根”ViewModel 实现 INotifyPropertyChanged,以便 WPF 知道您是否更改了该区域中的 ViewModel。

public class RootViewModel : INotifyPropertyChanged
{
    public object Screen1ViewModel { get; }

    public object Screen2ViewModel { get; }
}

将一个 ContentPresenter 控件数据绑定到 Screen1ViewModel 属性使用

<ContentControl Content="{Binding Path=Screen1ViewModel}" />

下一个也是类似的。当您需要更改 Screen1 的内容时,您只需从代码中重新分配 Screen1ViewModel,由于引发的 PropertyChanged 事件,WPF 将拾取它并将新的 ViewModel 绑定到新的 View。

DataTemplates 可能就这么简单:

<Window.Resources>
    <DataTemplate DataType="{x:Type foo:MyViewModel}">
        <self:MyControl />
    </DataTemplate>
    <DataTemplate DataType="{x:Type foo:MyOtherViewModel}">
        <self:MyOtherControl />
    </DataTemplate>
</Window.Resources>

如果您不熟悉它,this article on MVVM in WPF 是一个很好的介绍。

【讨论】:

  • 感谢您的回答,我不确定我是否完全理解,但它仍然没有回答我的问题:如何保持 ViewModel 的解耦?我不希望 Screen2 ViewModel 直接使用 Screen1
  • @Mark:我非常喜欢这种方法,但我不确定这是否是 Catalin 所追求的。在我看来,这几乎听起来像是他想要实现各种导航并能够从一个页面转到另一个页面。如果我理解你的方法,你建议通过数据绑定切换视图模型,这真的很酷,但我不知道这是否是他所追求的。顺便说一句,你能推荐任何好的在线阅读,通过你想改变 ViewModel 的例子吗?我总是在 View:ViewModel:Model 之间保持 1:1:1 的关系,并且从不更改 ViewModel(因为我不知道它的优点)。
  • @Catalin DICU:在这个模型中,Screen1ViewModel 对 Screen2ViewModel 一无所知,反之亦然。 RootViewModel 显然知道两者,但请注意,这两个属性都声明为 System.Object 类型,表示它们实际上可以是任何东西。如果您不想将有关 VM 类型的知识硬连接到 RootViewModel,可以使用 DI 将 VM 注入 RootViewModel。这会给它一个像RootViewModel(object vm1, object vm2)这样的构造函数。
  • @Dave:我不知道除了我已经链接到的 MVVM 文章之外的任何其他文章,但是凭借那篇文章中的知识(以及我刚刚写的),这真的不是很难.
  • @Mark:我同意这并不难。我只是想不出我真正想要更改 View 的 ViewModel 的示例。 :) 可能只是我缺乏经验。如果你能给我一个简单的例子,我将不胜感激!
【解决方案2】:

最后我使用 T4 代码生成来生成我的ScreenNames 类。我通过修改这段代码做到了这一点:Auto generate strong typed navigation class for all user controls in ASP.NET web application

【讨论】:

    猜你喜欢
    • 2013-02-08
    • 1970-01-01
    • 1970-01-01
    • 2012-06-21
    • 2011-12-05
    • 1970-01-01
    • 2011-09-30
    • 1970-01-01
    • 2014-10-29
    相关资源
    最近更新 更多