MVVM+;ViewModel调用的视图特定功能的实现

MVVM+;ViewModel调用的视图特定功能的实现,mvvm,separation-of-concerns,Mvvm,Separation Of Concerns,以下是我想解决的“问题”: 我有许多“仅查看”特定功能,例如: 在运行时更改视图的ResourcesDictionary(用于将外观从黑色更改为蓝色或其他颜色) 保存和恢复特定于视图的设置,如视图大小或用户设置的栅格属性 所有这些功能都与ViewModel无关,因为它们实际上是特定于视图的,可能只适用于ViewModel的一个客户端(视图)(如果ViewModel有多个客户端)。上面的例子只是我想要实现的大量功能中的两个,所以我需要一个更通用的解决方案,而不是只适合这两个例子的解决方案 在

以下是我想解决的“问题”:

我有许多“仅查看”特定功能,例如:

  • 在运行时更改视图的ResourcesDictionary(用于将外观从黑色更改为蓝色或其他颜色)
  • 保存和恢复特定于视图的设置,如视图大小或用户设置的栅格属性
所有这些功能都与ViewModel无关,因为它们实际上是特定于视图的,可能只适用于ViewModel的一个客户端(视图)(如果ViewModel有多个客户端)。上面的例子只是我想要实现的大量功能中的两个,所以我需要一个更通用的解决方案,而不是只适合这两个例子的解决方案

在考虑解决方案时,我采用了以下两种方法

  • 创建从DependencyObject继承的ViewBase。我不喜欢这个解决方案,因为它在某种程度上打破了MVVM模式的想法,即视图没有代码落后。为了调用这个方法,我需要引用ViewModel中的视图,这也否定了分离关注点的思想
  • 创建IView接口。就像第一次接近一样肮脏。每个视图都需要实现IView,therfor有代码隐藏。此外,ViewModel需要“以某种方式”了解IView实现以调用其方法
  • 将ViewModel的属性绑定到视图的触发器、行为和命令。这种方法似乎是最好的,但我认为我会很快在使用限制下运行,因为有些功能可能无法与这种方法配合使用。例如,仅仅将resourceDictionary绑定到视图可能不起作用,因为正确显示新资源需要合并。再说一遍……我只能在ViewModel中查看特定的功能/信息(如resourcesdictionary),但只有ViewModel的特定客户端使用此属性
如果你们中的任何人已经有了同样的问题,并且为我的问题找到了一个智能/平滑(并且大部分是通用的;)的解决方案,这将是非常棒的

谢谢你说的

我有许多“仅查看”特定功能,例如:

这让我觉得你把“什么”和“如何”混为一谈了。我会解释我的意思

您的应用程序要求是什么:

  • 更改应用程序的肤色
  • 保存和还原
    • 大小
    • 网格属性
我认为上述内容与您的ViewModel有关,您的VM应该包含简单或复杂的属性,这些属性可以告诉您的视图它想要做什么,例如

public class SettingsViewModel
{
  public Color Skin { get;set;}
  public Size ViewSize {get;set;}
  public GridProperties GridProperties {get;set;}

  public void Save() {//TODO:Add code}
  public void Restore() {//TODO:Add code}
}
您的视图将绑定到该视图模型并实现“如何”

如果您正在创建一个web应用程序,那么how将采用ViewModel并创建html。如果您使用的是WPF,那么您将绑定到XAML中的那些属性并创建UI(这可能会导致您切换出ResourceDictionary等)

另一件帮助我的事情是认识到视图和ViewModel之间的不对称关系。在它最纯粹的形式中,ViewModel应该对视图一无所知,但是视图应该知道它需要知道的关于ViewModel的一切

这就是分离关注点背后的全部要点

对您的“解决方案”的回应:

  • 您的第一个选项违反了MVVM原则,您阅读了吗
  • 我相信这将帮助您根据ViewModel进行视图选择
  • 我不知道您会遇到什么“限制”,但WPF非常强大,并且会有可用的解决方案

我同意视图特定功能应保留在视图中(保存并恢复窗口大小,将焦点设置为特定控件等)

但我不同意IView接口的引入是“肮脏的”。Martin Fowler在《企业应用程序体系结构模式》一书中描述了一种称为的常见设计模式。此外,只要代码与视图特定功能相关,代码隐藏就不是“邪恶的”。不幸的是,这是MVVM社区中常见的误解


如果您对IView接口方法的引入进行了更改,那么您可能会发现很有趣。它通过这个接口解决了我们的问题。您将在示例应用程序中看到这一点。

在不引入视图和视图模型之间耦合的情况下,最简单的方法是使用Messenger(在某些框架中也称为中介)。ViewModel只是广播一条“更改主题”消息,视图订阅该消息。使用MVVM Light中的
Messenger
类,您可以执行以下操作:

消息定义

public class ThemeChangeMessage
{
    private readonly string _themeName;
    public ThemeChangeMessage(string themeName)
    {
        _themeName = themeName;
    }

    public string ThemeName { get { return _themeName; } }
}
视图模型

Messenger.Default.Send(new ThemeChangeMessage("TheNewTheme");
查看隐藏代码

public MyView()
{
    InitializeComponent();
    Messenger.Defaut.Register<ThemeChangeMessage>(ChangeTheme);
}

private void ChangeTheme(ThemeChangeMessage msg)
{
    ApplyNewTheme(msg.ThemeName);
}
publicmyview()
{
初始化组件();
Messenger.Defaut.Register(变更主题);
}
私有void ChangeTheme(ThemeChangeMessage消息)
{
ApplyNewTheme(msg.ThemeName);
}

我早就接受了这样一种思维方式,即模式是为人设计的,而不是为模式设计的。很多时候,你会看到MVVM不适合的情况,为了解决这个问题,非常聪明的人想出了一些方法来绕过它,同时保持纯MVVM的外观

但是,如果你同意我的观点,或者你只是想保持简单,另一种方法是允许ViewModel引用视图;当然是通过接口,否则这将是一种糟糕的编程实践。现在的问题是,如何将视图放入viewmodel

最简单的方法是在视图的dataContextChanged事件中执行此操作。但是,如果您想尝试不同的方法,那么使用附加属性或依赖属性将视图注入viewmodel如何

我已经在许多WPF上成功地使用了这种技术
public IMyView InjectedView { set { _injectedView = value; } }
public MyUserControl : IMyView
{
    public static readonly DependencyProperty ThisProperty = 
         DependencyProperty.Register("This", typeof(IMyView), typeof(MyUserControl)); 

    public MyUserControl() 
    {
       SetValue(ThisProperty, this);
    } 
    public IMyView This { get { return GetValue(ThisProperty); } set { /* do nothing */ } } 
}
<MyUserControl This="{Binding InjectedView, Mode=OneWayToSource}"/>