【问题标题】:How can I test a ViewModel that needs to coordinate the creation and hosting of UserControls?如何测试需要协调 UserControls 的创建和托管的 ViewModel?
【发布时间】:2020-03-01 15:13:44
【问题描述】:

我正在开发一个演示 MVVM 项目,其中我有一个带有 ViewModel 的 WPF MainWindow,它需要协调不同 UserControls 的创建和托管。如果 ViewModel 不应该包含 WPF 元素的任何部分,我不知道该怎么做。我知道这是一个相当广泛的设计问题,但我是 WPF/TDD 的新手,我很难找到一条清晰的路径来了解如何在没有一些创建和绑定代码的情况下创建 UserControl 并将其绑定到 ViewModel ViewModel。

从我读到的内容来看,在 MainViewModel 中公开一个绑定到 ContentControl 的 UserControl 属性并不是要走的路。如何在 MainView 模型中抽象出 UserControls 的创建和绑定,以便进行测试?

有效但不可测试:

<ContentControl Grid.Row="2" Content="{Binding UserControl}" />

public class MainWindowViewModel
{
    public void ShowHome()
    {
        SomeUserControl uc = new SomeUserControl();
        uc.DataContext = new SomeUserControlViewModel();
        UserControl = uc;
    }

    public void ShowKeypad()
    {
        SomeOtherUserControl uc = new SomeOtherUserControl();
        uc.DataContext = new SomeOtherUserControlViewModel();
        UserControl = uc;
    }

    public UserControl UserControl {get; private set;}
}



【问题讨论】:

    标签: c# wpf mvvm


    【解决方案1】:

    只需使用 DataTemplate。让 Wpf 为您选择视图。

    <ContentControl Grid.Row="2" Content="{Binding UserControl}" />
    
    public class MainWindowViewModel
    {
      public void ShowHome()
      {
        MyViewmodelChoosedInMain  = new SomeViewModel();
      }
    
     public void ShowKeypad()
     {
        MyViewmodelChoosedInMain  = new SomeOtherViewModel();
     }
    
    //better use an Interface instead of object type ;)
    //you also need to implement and call INotifyPropertyChanged of course
     public object MyViewmodelChoosedInMain {get; private set;}
    }
    
    //in your ResourceDictionary create the DataTemplates
    <DataTemplate DataType="{x:Type SomeViewModel}">
        <MySomeViewmodelView />
    </DataTemplate>
    <DataTemplate DataType="{x:Type SomeOtherViewModel}">
        <MySomeOtherViewmodelView />
    </DataTemplate>
    

    【讨论】:

    • 对于初学者,提及模板也可以在页面资源或父控件的资源部分可能会有所帮助。
    • 最好的地方是 app.xaml ResourceDictionary。
    • 我更喜欢这样。 :-)
    【解决方案2】:

    您可以做几件事。

    1. 我创建了许多项目,其中启动时的视图创建了可见性设置为隐藏的控件。然后虚拟机创建/拥有一个定义应用程序不同状态的状态属性。随着该属性的更改(通过INotifyPropertyChanged),屏幕上的控件会出现或隐藏。

    2. 无论是否使用 #1,都可以创建视图可以处理但由 VM 或其他地方启动的命令。因此保持关注点分离。


    #1

    定义Enum

    public enum OperationState
    {
        Select = 1,
        Routine,
        Alignment,
        SerialNumber,
    }
    

    在虚拟机上定义状态

    private OperationState _State;
    
    public OperationState State
    {
        get => _State;
        set { _State = value; OnPropertyChanged(nameof(State)); }
    }
    

    根据需要设置状态,例如State = Select

    根据状态控制可见度

    <Control:AlignmentProcessing 
                ProjectContainer="{Binding CurrentContainer, Mode=TwoWay}"
                Visibility="{Binding State, Converter={StaticResource VisibilityStateConverter},
                                            ConverterParameter=Alignment}"/>
    

    上述控件仅在Alignment 状态下可见。

    转换器代码

    /// <summary>
    /// Take the current state, passed in as value which is bound, and check it against
    /// the parameter passed in. If the names match, the control should be visible,
    /// if not equal, then the control (window really) should be collapsed.
    /// </summary>
    public class OperationStateToVisibility : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return (value != null) &&
                   (parameter != null) &&
                   value.ToString().Equals(parameter.ToString(), StringComparison.OrdinalIgnoreCase)
                ? Visibility.Visible : Visibility.Collapsed;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        => throw new NotImplementedException();
    
    }
    

    #2 否则在 VM 上执行命令操作,例如:

    public ICommand ShowControl1 { get; set; }
    

    然后在 View 上订阅命令(假设 VM 持有当前 VM):

        VM.ShowControl1 = new Commanding((o) =>
        {
            SomeUserControl uc = new SomeUserControl();
            uc.DataContext = new SomeUserControlViewModel();
            UserControl = uc;
        }
    

    然后当命令被执行(从虚拟机)时,视图就开始工作了。

     ShowControl1.Execute(null);
    

    我在我的博客Xaml: MVVM Example for Easier Binding提供了一个重要的例子

    【讨论】:

    • 人们将 MVVM 视为一种宗教,而不是教条。它只是 GUI 到业务处理到数据库之间的关注点分离。它曾经被称为三层方法。因此,您是否希望您需要做才能使其工作,但将主要组件保留在它们的层内;就这些。这是我的看法,我可能错了。
    【解决方案3】:

    视图模型应该包含任何具有 MVVM 模式的控件。视图模型只是要显示的数据的状态。

    例如:

    • 要显示的记录
    • 标题
    • 图片
    • 可以删除
    • 可以编辑

    在 WPF 中,控件可以绑定到这些属性以进行显示。

    视图模型与视图无关(它们不关心哪个视图正在使用它)。因此,其中不会包含对 UI 控件的实际引用。

    要测试实际的 UI,您可以编写“编码的 UI 测试”。在 Web 应用程序中,有 Selenium 框架允许您编写单元测试以与浏览器中的 UI 组件进行交互。

    我很确定那里有一个类似的框架用于 WPF UI 测试。

    编辑:有一个名为 Appium 的框架,允许您编写 UI 和您拥有的底层 MVVM 设置之间的集成测试。

    http://appium.io/docs/en/drivers/windows/

    【讨论】:

    • 我已经能够按照您的指南测试所有 UserControl 视图模型。我遇到的问题是 MainView 模型,我不知道该方法,因为它非常了解它需要创建和协调的视图。我以前使用过 Selenium,但在这种情况下,我没有进行集成测试。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-02
    • 1970-01-01
    • 2019-06-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多