我有一个由多个用户控件组成的窗口,并且想知道每个用户控件是否有自己的视图模型,还是整个窗口应该只有一个视图模型?
6 回答
绝对的,积极的
不
你的 UserControls 不应该有专门为他们设计的 ViewModels。事实上,这是一种代码异味。它不会立即破坏您的应用程序,但会在您使用它时给您带来痛苦。
UserControl 是使用组合创建控件的简单方法。UserControls 仍然是控件,因此应该只关注 UI 问题。
当您为您的 UserControl 创建 ViewModel 时,您要么将业务或 UI 逻辑放在那里。使用 ViewModel 包含 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 向用户显示此人的信息。
什么是人视图?它是一个用户控件,旨在显示人员的信息。它是一个人的显示控件,类似于 TextBlock 是文本的显示控件。它被设计为绑定一个人,因此可以顺利运行。请注意,在上面的窗口中,ListView 如何将每个 Person 实例传输到 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 逻辑放在代码隐藏中。如果它太复杂以至于您需要用户控件内部的业务逻辑,那表明它过于包容。简化!
像这样考虑 MVVM 中的 UserControls——对于每个模型,您都有一个 UserControl,它旨在将模型中的数据呈现给用户。您可以在任何想要向用户展示该模型的地方使用它。它需要一个按钮吗?在您的 UserControl 上公开 ICommand 属性并让您的业务逻辑绑定到它。您的业务逻辑是否需要了解内部发生的事情?添加路由事件。
通常,在 WPF 中,如果您发现自己问为什么做某事很痛苦,那是因为您不应该这样做。
这不是一个是或否的问题。这取决于拥有额外的视图模型是否为您提供更好的可维护性或可测试性。如果它没有为您带来任何好处,那么添加视图模型是没有意义的。您需要衡量开销对于您的特定用例是否值得。
我会说每个用户控件都应该有自己的 ViewModel,因为这将允许您在未来的新星座中重用 ViewModel/UserControl 对。
据我了解,您的窗口是用户控件的组合,因此您始终可以创建一个 ViewModel,该 ViewModel 为每个用户控件组成所有单独的 ViewModel。这将为您提供两全其美的体验。
[应该] 每个用户控件都有自己的 ViewModel 还是整个窗口应该只有一个 ViewModel?
不幸的是,这个问题的最高投票答案具有误导性,并且基于我在其他问题中交换的评论,为尝试学习 WPF 的人提供了糟糕的指导。该答案回复:
你的 UserControls 不应该有专门为他们设计的 ViewModels。
问题是,这不是被问到的问题。
我同意这样一种普遍观点,即当您编写. 时UserControl
,控件的公共 API 不应涉及创建专门设计用于该控件的视图模型类型。客户端代码必须能够使用它想要的任何视图模型。
但是,这并不排除“每个用户控件 [可能] 有自己的 ViewMomdel”的想法。至少有两个明显的场景我可以想到答案是“是的,每个用户控件的视图模型”:
用户控件是项目展示器(例如
ItemsControl
)中数据模板的一部分。在这种情况下,视图模型将对应于每个单独的数据元素,并且在视图模型对象和呈现该视图模型对象的用户控件之间将存在一一对应的关系。
在这种情况下,视图模型对象不是“专门为他们设计的”(因此与有问题的答案没有矛盾),但肯定是每个用户控件都有自己的视图模型(做出实际问题的答案“是的,每个用户控件都可能有自己的视图模型”)。用户控件的实现受益于,甚至需要专门为用户控件设计的视图模型数据结构。这个视图模型数据结构不会暴露给客户端代码;这是一个实现细节,因此将使用用户控件对客户端代码隐藏。但是,那肯定仍然是“专为”该用户控件设计的视图模型数据结构。
这种情况显然根本没有问题,这与“您的 UserControls 不应该有专门为它们设计的 ViewModels”的说法直接矛盾。
现在,我不相信该答案的作者的意图是排除这些情况中的任何一种。但问题是,试图学习 WPF 的人可能没有足够的上下文来识别差异,因此基于这个强调、高度赞成和误导性的答案,可能会错误地概括用户控件和视图模型。
我希望通过提出这个替代观点作为澄清点,并以不那么狭隘的方式回答原始问题,那些在更多地了解 WPF 的同时发现这个问题的人将有更好的背景,更好的想法和何时可以实现特定于用户控件的视图模型,何时不应该实现。
我猜你的应用程序正在做某种视图组合,所以如果你让你的用户控件拥有自己的视图模型,你将有更多的自由将它们嵌入到其他主机窗口中,而无需更改窗口全局视图模型。
作为额外的奖励,如果出现应用程序需求,您的应用程序将更适合演变为由 Prism 或 Caliburn 框架提供的架构更完善的组合模型。
我会倾向于视图模型。
请记住,所有这些模式都旨在将您的代码分段,以使其在未来更易于维护。包括MVVM。视图有一定的责任,视图模型也是如此,模型也是如此。一个新的开发人员可以进来,可以识别这种模式,会更好地了解在哪里找到和维护东西。比一堆意大利面要好。
所以,在这里面,如果你有正确地属于用户控件的逻辑,它正确地属于虚拟机,那为什么不呢?
但这里有一个警告。请记住 UserControl 是什么。它是一小段用户界面,可以从一个地方重用到另一个地方。您的虚拟机应该是相同的 - 可重复使用。您想要结束的最后一件事是 vm 在一种情况下以一种方式运行,而在另一种情况下以不同方式运行。
请注意,没有谈论技术。我只是在谈论模式的逻辑结构。