使用MVVM模式与WPF设计数据

10

我正在我们的WPF应用程序中使用MVVM模式,以便可以进行全面的单元测试。 MVVM模式本身运作良好,但我在努力使该模式适应一种方式,以便我可以使用WPF的设计时数据支持。

由于我正在使用Prism,因此ViewModel实例通常会被注入到视图的构造函数中,如下所示:

public MyView(MyViewModel viewModel)
{
    DataContext = viewModel;
}

然后,ViewModel所需的依赖项将被注入到构造函数中,如下所示

public class MyViewModel
{
    public MyViewModel(IFoo foo, IBar bar)
    {
        // ...
    }

    // Gets and sets the model represented in the view
    public MyModel { get; set; }

    // Read-only properties that the view data binds to
    public ICollectionView Rows { get; }
    public string Title { get; }

    // Read-write properties are databound to the UI and are used to control logic
    public string Filter { get; set; }
}

总的来说,这个方法运行得非常好,只是在设计数据方面有点问题——我想避免把设计数据专用的类编译到我的发布程序集中,因此我选择使用 {d:DesignData} 方法而不是 {d:DesignInstance} 方法,但是为了让它正确地工作,我的 ViewModel 现在需要一个无参数的构造函数。另外,我还经常需要将其他属性更改为具有 setter 或可修改集合以便能够在 XAML 中设置这些属性。

public class MyViewModel
{
    public MyViewModel()
    {
    }

    public MyViewModel(IFoo foo, IBar bar)
    {
        // ...
    }

    // Gets and sets the model represented in the view
    public MyModel { get; set; }

    // My read-only properties are no longer read-only
    public ObservableCollection<Something> Rows { get; }
    public string Title { get; set; }

    public string Filter { get; set; }
}

这让我感到担忧:

  • 我有一个无参数构造函数,从未想过要调用它,也没有经过单元测试。
  • 有一些属性的setters只应该由ViewModel自己调用。
  • 现在我的ViewModel中混杂着应该由视图修改的属性和不应该修改的属性 - 这使得一眼看去很难判断哪段代码负责维护任何给定的属性。
  • 在设计时设置某些属性(例如为了在Filter文本上查看样式)实际上可能会触发ViewModel逻辑!(因此我的ViewModel还需要容忍在设计时缺少其他依赖项)。

在WPF MVVM应用程序中,有没有更好的方法以一种不会损害我的ViewModel的方式获得设计时数据?

或者,我应该以一种不同的方式构建我的ViewModel,以便它具有更简单的属性,并将逻辑分离到其他地方。

2个回答

2
首先,我建议您观看这个视频,Brian Lagunas在其中提供了关于MVVM的多个最佳实践。至少可以肯定的是,Brian参与了Prism的开发,因为他的名字出现在nuget包信息中。没有进一步的检查。

就我而言,我只使用Prism的部分内容,我的Model和ViewModel总是提供空构造函数(就像Brian展示的那样),数据上下文在视图的XAML中分配,并且我设置属性值如下:

<MyView.DataContext>
  <MyViewModel />
</MyView.DataContext>

并且

public void BringSomethingNew()
{      
  var myView = new View();
  (myView.DataContext as ViewModel).Model = myModel;

  UseMyView();
}

这种方法的另一个好处是ViewModel只在设计时和运行时以相同的路径创建一次,因此您可以创建更少的对象并节省GC的成本。我认为这很优雅。

关于setter,如果将其设置为private,设计数据仍将起作用,例如:

public string MyProp { get; private set; }

好的,您可以根据自己的方便进行定制化以管理NotifyPropertyChange,但是您已经有了这个想法。

现在,我还没有解决办法来管理ObesrvableCollection(我也面临同样的问题,尽管在XAML中放置多个值有时会起作用...???),是的,我同意您必须处理属性未设置的情况,例如在构造函数中设置默认值。

希望这可以帮到您。


-1

我也使用过NUnit测试与WPF和MVVM实现。 不过,我的版本与您的相反。 您是先创建视图,然后创建控制它的模型。

在我的版本中,我首先创建MVVM模型,可以进行单元测试,而不必担心任何可视化设计...如果模型出错了,可视化实现也会出错。

在我的MVVM模型中,我有一个方法叫做“GetTheViewWindow”。因此,当我从我的MVVM基线派生时,每个视图模型都有自己的视图负责。因此,通过虚拟方法,每个实例在应用于生产时都将执行其自己的新视图窗口。

public class MyMVVMBase
{
   private MyViewBaseline currentView;

   public MyMVVMBase()
   { // no parameters required }

   public virtual void GetTheViewWindow()
   { throw new exception( "You need to define the window to get"; ) }
}

public class MyXYZInstanceModel : MyMVVMBase
{
   public override void GetTheViewWindow()
   {
      currentView = new YourActualViewWindow();
   }
}

希望这可以作为你遇到问题的替代方案。

2
你的虚拟机对视图有依赖关系,这对我来说有点奇怪。 - Greg D
@GregD,不是对我说。 我应该能够拥有这个模型,可以查询数据,在通过公开getter / setter向任何外部“视图”设置标志时呈现。 我只是想说,在这个例子中,它并不需要视图,但如果我想启动一个视图,每个视图模型都有自己的目的,例如维护屏幕,交易头/详细处理等。 如果有相应的视图,我只需挂钩说...去调用与此mvvm处理程序相关联的视图。 - DRapp
@Justin,你能给我解释一下你所说的“设计时”数据是什么意思吗?我的模型管理器创建了必要的数据库连接,并具有相应的方法来“获取”数据并将其提供为列表、数据表、getter/setter等。因此,任何“VIEW”都可以绑定到整个模型管理器。模型管理器还具有“ICommand”处理程序,用于像添加、保存、取消这样的操作,以将数据更改推回服务器。 - DRapp
@Justin,说得好,我尊重你需要预运行时数据来从视觉设计师的角度模拟界面。 - DRapp
3
  1. 这不是 MVVM 模式!您的视图模型不应该知道 View 的存在。
  2. 我没有看到任何地方提到这如何解决原帖作者使用设计数据时遇到的问题。
- Quark Soup
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接