Mvvm 如何避免视图模型中的视图特定代码

Mvvm 如何避免视图模型中的视图特定代码,mvvm,Mvvm,我的应用程序有一个菜单选项,允许创建一个新帐户。菜单选项的命令绑定到我的ViewModel中的命令(NewAccountCommand)。当用户单击创建新帐户的选项时,应用程序将显示一个“新帐户”对话框,用户可以在其中输入名称、地址等数据。。。然后单击“确定”关闭对话框并创建新帐户 我知道ViewModel中的代码不正确,因为它创建了“新帐户”对话框并调用ShowDialog()。以下是虚拟机的一个片段: var modelResult = newAccountDialog.ShowDialo

我的应用程序有一个菜单选项,允许创建一个新帐户。菜单选项的命令绑定到我的ViewModel中的命令(NewAccountCommand)。当用户单击创建新帐户的选项时,应用程序将显示一个“新帐户”对话框,用户可以在其中输入名称、地址等数据。。。然后单击“确定”关闭对话框并创建新帐户

我知道ViewModel中的代码不正确,因为它创建了“新帐户”对话框并调用ShowDialog()。以下是虚拟机的一个片段:

 var modelResult = newAccountDialog.ShowDialog();
 if (modelResult == true)
 {
   //Create the new account             
 }

如何避免在虚拟机中创建和显示对话框,以便对虚拟机进行单元测试?

有不同的方法。一种常见的方法是使用某种形式的依赖项注入来注入对话服务,并使用该服务


这允许在运行时插入该服务的任何实现(即:一个不同的视图),并使您能够从ViewModel中解耦到视图。

在类似的场景中,我通常使用事件。该模型可以引发一个事件来询问信息,任何人都可以对此作出响应。视图将侦听事件并显示对话框

public class MyModel
{
    public void DoSomething()
    {
        var e = new SomeQuestionEventArgs();
        OnSomeQuestion(e);
        if (e.Handled)
            mTheAnswer = e.TheAnswer;
    }
    private string mTheAnswer;
    public string TheAnswer
    {
        get { return mTheAnswer; }
    }
    public delegate void SomeQuestionHandler(object sender, SomeQuestionEventArgs e);
    public event SomeQuestionHandler SomeQuestion;
    protected virtual void OnSomeQuestion(SomeQuestionEventArgs e)
    {
        if (SomeQuestion == null) return;
        SomeQuestion(this, e);
    }
}
public class SomeQuestionEventArgs
    : EventArgs
{
    private bool mHandled = false;
    public bool Handled
    {
        get { return mHandled; }
        set { mHandled = value; }
    }
    private string mTheAnswer;
    public string TheAnswer
    {
        get { return mTheAnswer; }
        set { mTheAnswer = value; }
    }
}
public class MyView
{
    private MyModel mModel;
    public MyModel Model
    {
        get { return mModel; }
        set
        {
            if (mModel != null)
                mModel.SomeQuestion -= new MyModel.SomeQuestionHandler(mModel_SomeQuestion);
            mModel = value;
            if (mModel != null)
                mModel.SomeQuestion += new MyModel.SomeQuestionHandler(mModel_SomeQuestion);
        }
    }
    void mModel_SomeQuestion(object sender, SomeQuestionEventArgs e)
    {
        var dlg = new MyDlg();
        if (dlg.ShowDialog() != DialogResult.OK) return;
        e.Handled = true;
        e.TheAnswer = dlg.TheAnswer;
    }
}

我喜欢这篇codeproject文章中解释的方法:

它基本上创建了一个WPF对话框控件,该控件可以嵌入到另一个窗口或用户控件的可视树中

然后,它使用一个样式触发器,每当对话框中有内容时,该触发器就会使对话框打开

public class MyModel
{
    public void DoSomething()
    {
        var e = new SomeQuestionEventArgs();
        OnSomeQuestion(e);
        if (e.Handled)
            mTheAnswer = e.TheAnswer;
    }
    private string mTheAnswer;
    public string TheAnswer
    {
        get { return mTheAnswer; }
    }
    public delegate void SomeQuestionHandler(object sender, SomeQuestionEventArgs e);
    public event SomeQuestionHandler SomeQuestion;
    protected virtual void OnSomeQuestion(SomeQuestionEventArgs e)
    {
        if (SomeQuestion == null) return;
        SomeQuestion(this, e);
    }
}
public class SomeQuestionEventArgs
    : EventArgs
{
    private bool mHandled = false;
    public bool Handled
    {
        get { return mHandled; }
        set { mHandled = value; }
    }
    private string mTheAnswer;
    public string TheAnswer
    {
        get { return mTheAnswer; }
        set { mTheAnswer = value; }
    }
}
public class MyView
{
    private MyModel mModel;
    public MyModel Model
    {
        get { return mModel; }
        set
        {
            if (mModel != null)
                mModel.SomeQuestion -= new MyModel.SomeQuestionHandler(mModel_SomeQuestion);
            mModel = value;
            if (mModel != null)
                mModel.SomeQuestion += new MyModel.SomeQuestionHandler(mModel_SomeQuestion);
        }
    }
    void mModel_SomeQuestion(object sender, SomeQuestionEventArgs e)
    {
        var dlg = new MyDlg();
        if (dlg.ShowDialog() != DialogResult.OK) return;
        e.Handled = true;
        e.TheAnswer = dlg.TheAnswer;
    }
}
因此,在xaml中,您所要做的就是(其中DialogViewModel是ViewModel中的一个属性):

因此,在单元测试中,您需要做的就是:

MyViewModel model = new MyViewModel();
model.DialogViewModel = new MyDialogViewModel();
model.DialogViewModel.InputProperty = "Here's my input";
//Assert whatever you want...
我个人在ViewModel中创建了一个ICommand属性,用于设置DialogViewModel属性,以便用户可以按下按钮打开对话框

所以我的ViewModel从不调用对话框,它只是实例化一个属性。视图将对此进行解释并显示一个对话框。这背后的美妙之处在于,如果您决定更改视图,并且可能不显示对话框,那么您的ViewModel一点也不需要更改。它将所有用户交互代码推送到视图中它应该位于的位置。创建wpf控件允许我在需要时重复使用它

有很多方法可以做到这一点,我发现这是一个对我有好处的方法

展示了如何实现这一点的具体示例

ViewModel示例应用程序显示了一个电子邮件客户端,您可以在其中打开“电子邮件帐户设置”对话框。它使用依赖注入(MEF),因此您仍然能够对ViewModel进行单元测试

希望这有帮助


jbe

这将要求您在视图usercontrol中编写代码,这不是亵渎,但如果可以避免的话,应该这样做。我喜欢这个想法。我想我会试试的。谢谢。这正是我发现MVVM模式更麻烦的地方之一。我不认为这一方面是从以前的技术(例如WinForms)演变而来的,编写数百行代码来提供简单的对话框功能对我来说并不合适。
MyViewModel model = new MyViewModel();
model.DialogViewModel = new MyDialogViewModel();
model.DialogViewModel.InputProperty = "Here's my input";
//Assert whatever you want...