WPF - 用户控件是否应该拥有自己的ViewModel?

时间:2009-12-21 10:29:11

标签: wpf model-view-controller viewmodel

我有一个由几个用户控件组成的窗口,并且想知道每个用户控件是否有自己的ViewModel,或者整个窗口是否只有一个ViewModel?

5 个答案:

答案 0 :(得分:32)

绝对,积极

NO

您的UserControl应该具有专门为他们设计的ViewModel。事实上,这是一种代码味道。它不会立即破坏您的应用程序,但在您使用它时会让您感到痛苦。

UserControl只是使用合成创建Control的简单方法。 UserControls仍然是控件,因此应该只关注UI的问题。

当您为UserControl创建ViewModel时,您要么在那里放置业务或UI逻辑。使用ViewModels包含UI逻辑是不正确的,因此如果这是您的目标,请丢弃您的VM并将代码放在该控件的代码隐藏中。如果您将业务逻辑放在UserControl中,很可能您正在使用它来隔离应用程序的各个部分,而不是简化控件创建。控件应该简单,并且具有设计它们的单一目的。

为UserControl创建ViewModel时,您还可以通过DataContext中断数据的自然流动。这是您将经历最痛苦的地方。为了演示,请考虑这个简单的例子。

我们有一个包含People的ViewModel,每个都是Person类型的实例。

public class ViewModel
{
    public IEnumerable<Person> People { get; private set; }
    public ViewModel()
    {
        People = PeopleService.StaticDependenciesSuckToo.GetPeople();
    }
}

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

在我们的窗口中显示人员列表是微不足道的。

<Window x:Class="YoureDoingItWrong.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:l="clr-namespace:YoureDoingItWrong"
        Title="Derp">
    <Window.DataContext>
        <l:ViewModel />
    </Window.DataContext>
    <Window.Resources>
        <DataTemplate DataType="{x:Type l:Person}">
            <l:PersonView />
        </DataTemplate>
    </Window.Resources>
    <ListView ItemsSource="{Binding People}" />
</Window>

列表会自动为Person选择正确的项目模板,并使用PersonView向用户显示此人的信息。

什么是PersonView?它是一个UserControl,旨在显示此人的信息。它是一个人的显示控件,类似于TextBlock是文本的显示控件。它旨在绑定一个人,因此工作顺利。请注意上面的窗口中ListView如何将每个Person实例传输到PersonView,在PersonView中它成为该视觉子树的DataContext。

<UserControl x:Class="YoureDoingItWrong.PersonView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <StackPanel>
        <Label>Name</Label>
        <TextBlock Text="{Binding Name}" />
        <Label>Age</Label>
        <TextBlock Text="{Binding Age}" />
    </StackPanel>
</UserControl>

为了使其顺利运行,UserControl 的ViewModel必须是为设计的Type的实例。当你通过做蠢事如

来打破这个
public PersonView()
{
    InitializeComponent();
    this.DataContext = this; // omfg
}

public PersonView()
{
    InitializeComponent();
    this.DataContext = new PersonViewViewModel();
}

你打破了模特的简洁性。通常在这些情况下,您最终会得到令人憎恶的变通方法,其中最常见的是为DataContext实际应该创建一个伪DataContext属性。现在你不能将一个绑定到另一个,所以你最终会遇到像

这样糟糕的黑客攻击
public partial class PersonView : UserControl
{        
    public PersonView()
    {
        InitializeComponent();
        var vm = PersonViewViewModel();
        // JUST KILL ME NOW, GET IT OVER WITH 
        vm.PropertyChanged = (o, e) =>
        {
            if(e.Name == "Age" && MyRealDataContext != null)
                MyRealDataContext.Age = vm.PersonAge;
        };
        this.DataContext = vm; 
    }
    public static readonly DependencyProperty MyRealDataContextProperty =
        DependencyProperty.Register(
            "MyRealDataContext",
            typeof(Person),
            typeof(PersonView),
            new UIPropertyMetadata());
    public Person MyRealDataContext
    {
        get { return (Person)GetValue(MyRealDataContextProperty); }
        set { SetValue(MyRealDataContextProperty, value); }
    }
}

您应该将UserControl视为一个更复杂的控件。 TextBox是否有自己的ViewModel?不。您将VM的属性绑定到控件的Text属性,控件在其UI中显示您的文本。

MVVM不代表“无代码隐藏”。将用户控件的UI逻辑放在代码隐藏中。如果它太复杂以至于您需要在用户控件中使用业务逻辑,那么这表明它太过笼统。简化!

将MVCon中的UserControl想象成这样 - 对于每个模型,您都有一个UserControl,它旨在将该模型中的数据呈现给用户。您可以在任何想要向用户显示该模型的地方使用它。它需要一个按钮吗?在UserControl上公开ICommand属性,并让您的业务逻辑绑定到它。您的业​​务逻辑是否需要知道内部发生的事情?添加路由事件。

通常情况下,在WPF中,如果你发现自己在问某些为什么会有什么伤害,那是因为你不应该这样做。

答案 1 :(得分:11)

这不是是或否是问题。这取决于是否有额外的视图模型为您提供更好的可维护性或可测试性。添加视图模型是没有意义的,如果它没有获得任何东西。您需要根据特定用例来衡量开销是否值得。

答案 2 :(得分:6)

我想说每个用户控件都应该有自己的ViewModel,因为这样你将来可以在新的星座中重用ViewModel / UserControl对。

据我了解,您的窗口是用户控件的复合,因此您始终可以创建一个ViewModel,它为每个用户控件组成所有单独的ViewModel。这将为您提供两全其美的优势。

答案 3 :(得分:2)

  

[应该]每个用户控件都应具有自己的ViewModel,或者整个窗口仅应具有一个ViewModel?

不幸的是,对该问题的最高评价答案具有误导性,并且基于我在其他问题中交换的评论,从而为尝试学习WPF的人们提供了糟糕的指导。该答案回答:

  

您的用户控件应该具有专门为其设计的ViewModel。

问题是,不是问的问题

我会同意这样一种普遍的观点,即当您编写UserControl时,控件的公共API不应同时创建专门设计用于该控件的视图模型类型。客户端代码必须能够使用所需的任何视图模型。

但是,这并不排除“每个用户控件[可能]都有自己的ViewMomdel” 的想法。我可以想到至少有两个明显的方案,答案是“是的,每个用户控件的视图模型”:

  1. 用户控件是项目演示者(例如ItemsControl)中数据模板的一部分。在这种情况下,视图模型将与每个单独的数据元素相对应,并且视图模型对象与呈现该视图模型对象的用户控件之间将存在一对一的对应关系。

    在这种情况下,视图模型对象不是“专门为他们设计的” (因此与有问题的答案没有矛盾),但是每个用户控件都有自己的视图模型( 实际问题的答案:“是的,每个用户控件可能都有自己的视图模型”。

  2. 用户控件的实现受益于甚至需要为用户控件专门设计的视图模型数据结构。这种视图模型的数据结构不会暴露给客户端代码。这是一个实现细节,因此将使用用户控件从客户端代码中隐藏。但是,肯定仍然是由用户控制的“专门为设计的”视图模型数据结构。

    这种情况显然根本没有问题,这直接与要求相矛盾。 “您的用户控件应该具有专门为其设计的ViewModel。”

现在,我不认为答案的作者曾打算排除这两种情况。但是问题是,尝试学习WPF的人可能没有足够的上下文来认识差异,因此,基于这个强调,高度支持且具有误导性的答案,关于用户控件和视图模型的理解可能不正确。 / p>

我希望通过提出这一替代观点作为澄清点,并以一种不太狭narrow的方式回答原始问题,那些在学习更多有关WPF的过程中发现此问题的人将有更好的背景,并且更好的主意,以及何时可以针对用户控件实现视图模型以及何时不应该实现。

答案 4 :(得分:1)

我猜您的应用程序正在进行某种视图合成,因此如果您使用户控件拥有自己的视图模型,您可以更自由地将它们嵌入其他主机窗口而无需更改窗口全局视图模型。

作为额外的奖励,如果应用程序需求出现,您的应用程序将更适合演变为Prism或Caliburn框架提供的更具体系结构的组合模型。