wpf应用程序mvvm如何正确构建



我想学习MVVM,但遇到了一些麻烦。我是xaml和c#的新手。

到目前为止我所拥有的:

  • 一个person类,用于定义个人对象:姓名、年龄和其他信息

  • 模型类people拥有一个私有链表(人员列表),其中还包含getremoveadd等方法,并进行一些计算

  • 一个viewmodel类,在xaml-behind代码和模型之间执行强制转换/解析/转换。

  • 一个xaml后台代码文件mainWindow.xaml.cs,它监听按钮点击等,并从viewModel类调用方法,并进行一些简单的绑定,如total.Content = objModelView.getTotal()

我没有使用INotifyPropertyChanged ObservableCollection,仍然在努力思考它。虽然我的程序可以实现我想要的功能,但我不知道如何更好地构建它。

基本上我有两个主要问题:

  1. 我在网上看到了一些例子,人们在viewmodel中存储/启动项目列表,难道我不应该把列表保存在模型中吗?所有数据都应该存储在模型中
  2. 假设我将所有项目(在模型类的列表中)显示到dataGrid上。现在在我的程序中:mainWindow.xaml.cs会检测到按钮点击,然后它会要求viewModel将其存储在模型中,如果没有错误,那么xaml后台代码就可以了people_dataGrid.Items.Add(new person { name = newName, age = newAge, address = newAdd });这种做法不好吗?这里不知道如何使用ObservableCollection,它能以某种方式检测到我的模型类列表中的更改,然后将行删除并添加到数据网格中吗

我已经读了一整天了,但我在这里感到震惊,希望我能得到一些方向

模型存储数据,视图显示数据,视图模型是两者之间的桥梁。

这并不意味着视图可以直接访问模型数据,因为您并不总是需要在模型层中显示所有数据。因此,我们有一个视图模型层,它只允许访问有用的信息。

当您希望多次显示相同的数据但显示方式不同时,视图模型非常有用:您不必复制数据,只需要两次定义您需要从这些数据中获得哪些信息以及如何显示这些信息。

您在第二个问题中所做的是使用视图中的模型:这不是MVVM。您想要做的是将Datagrid的ItemsSource DP绑定到PersonVM的列表,该列表将从Person获取信息。

你的代码可以是这样的结构:

public class Person {
    public String Name {get; set;}
    public int Age {get; set;}
}
public class PersonVM {
    public PersonVM(Person model) {
        _model = model;
    }
    private readonly Person _model;
    internal Person Model {get {return _model;}}
    public String Name {
        get { return _model.Name; }
        set { _model.Name = value; }
    }
    public int Age {
        get {return _model.Age;}
        set { _model.Name = value; }
    }
}
//PersonV.xaml
<StackPanel>
    <TextBlock Text="{Binding Name}"/>
    <TextBlock Text="{Binding Age}"/>
</StackPanel>

public class People : ObservableCollection<Person> {
}
public class PeopleVM : ObservableCollection<PersonVM> {
    public PeopleVM(People model) {
        _model = model;
        foreach(Person p in _model) {
            Add(new PersonVM(p));
        }
        _model.CollectionChanged += CollectionChangedHandler;
    }
    private void CollectionChangedHandler(Object sender, NotifyCollectionChangedEventArgs args) {
        switch (notifyCollectionChangedEventArgs.Action) {
            case NotifyCollectionChangedAction.Add:
                foreach(People p in args.NewItems) {
                    if(!this.Any(pvm => pvm.Model == p)) {
                        this.Add(new PersonVM(p));
                    }
                }
                break;
            case NotifyCollectionChangedAction.Remove:
                foreach(People p in args.OldItems) {
                    PersonVM pvm = this.FirstOrDefault(pe => pe.Model == p);
                    if(pvm != null) this.Remove(pvm);
                }
                break;
             case NotifyCollectionChangedAction.Reset:
                Clear();
                break;
            default:
                break;
            }
    }
    private readonly People _model;
}
//PeopleV.xaml
<ItemsControl ItemsSource={Binding}>
    <ItemsControl.ItemTemplate>
        <DataTemplate DataType="{x:Type PersonVM}">
            <PersonV/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>
public class AppVM {
    public AppVM() {
        People p = ServiceLayer.LoadPeople(); //load people
        People = new PeopleVM(p);
    }
    public PeopleVM People {get; set;};
}
//MainWindow.xaml
<Window ...
    >
    <Window.DataContext>
        <local:AppVM/>
    </Window.DataContext>
    <PeopleV/>
</Window>

你的帖子的答案可以是你想要解释的那么长,也许是一个很长的博客本身。我将试着在这里回答你的两个具体问题。我不会显示每个子答案的代码,你必须把它当作家庭作业。:)

我没有使用INotifyPropertyChanged ObservableCollection,仍在尝试把我的脑袋绕过去。虽然我的程序能做我想做的事,但我不是确定如何更好地构建它。

为什么?如果你不使用这些魔术棒,最好写一个WinForms应用程序,而不是WPF应用程序。忘掉一切,深入研究这两件事。您必须(无法逃脱)理解并在MVVM/WPF中使用它们。你甚至可以推迟阅读我对此事的进一步回答。

我在网上看到一些例子,人们在其中存储/启动项目列表viewmodel,我不应该把列表放在model中吗所有数据都存储在哪里?

他们没有错。模型层中的Person类表示一个真实世界的实体,是必须的,然而,我不会为模型中有People类而烦恼。它只是一个集合,可以很容易地被ViewModel容纳。我个人更喜欢这种方式。

假设我要显示模型类)映射到dataGrid中。现在在我的程序中:mainWindow.xaml.cs会检测到按钮点击,然后询问viewModel要将其存储在模型中,如果没有错误,则xaml后台代码即可people_dataGrid。项目。添加(new person{name=newName,age=newAge,address=newAdd});这种做法不好吗?不知道如何使用ObservableCollection在这里,它能以某种方式检测到列表中的变化吗我的模型类,然后删除行并将其添加到数据网格中?

相信我,这不是MVVM。在视图代码背后编写代码时,最多需要初始化视图模型并将其设置为视图的数据上下文。

要处理视图事件(例如Button.Click),应该使用将绑定到XAML中Button.Command属性的ICommand实现。通过这种方式,您可以将控件的事件处理程序与代码分离。

您需要在视图模型中有一个ObservableCollection<Person>,它将绑定视图中的DataGrid。因此,当单击按钮添加人员时,按钮的命令对象将更新此集合,视图将自动刷新,而无需手动将其添加到数据网格中。

您根本没有使用MVVM听起来你在使用MVP,这是一种完全不同的模式。

在继续之前,您需要了解MVVM的设计目的,因为它是一个高度复杂的(参见过度设计的模式),有大量的抽象,只是为了编写无处不在的待办事项列表。

但您必须完成所有这些操作,否则它就不是MVVM。

MVVM的禅

MVVM是在意识到编写好的、无错误的、安全的UI代码是困难的基础上发展起来的。测试UI代码更难,而且需要雇佣人工测试人员,这些测试人员速度很慢,可能会出错。

所以他们提出的解决方案很简单。

不要在您的UI中写入任何代码

完成。

除了,不是。现在,你的UI什么都不做,只是看起来很漂亮。因此,他们在UI和Program/BusinessLogic/Model之间添加了一个额外的层,称之为ViewModel

ViewModel的工作是告诉用户界面该做什么。但诀窍是让它告诉用户界面要做什么,而ViewModel根本不知道用户界面。

(MVP也有类似的概念,但演示者确实知道UI)

通过让ViewModel完全不了解UI,我们可以编写干净的代码,这些代码可以使用我们常用的技巧轻松地进行调试和测试。如单元测试、重构、静态代码分析、依赖注入等

时代很好!

除了视图模型仍然不知道UI。所以我们知道UI应该是什么样子,但UI不知道,因为没有人告诉它…

所以他们添加了Binding类。绑定类的工作是监视ViewModel,然后在ViewModel中发生变化时更新UI。

在MVVM的世界里,Binding类的工作方式有两种不同的方法。

您有WPF的方法,即实现一个事件,该事件告诉Binding类ViewModel已经更新。这是快速和灵活的,但写起来真的很烦人。

您有AngularJS的做事方式,即轮询ViewModel以获取更新。这真是慢得离谱。

到目前为止,如果您一直在关注我,您会注意到MVVM定义了从模型到视图的数据流。这个链条的任何一部分断裂都会使其"不起作用"。

这一切都很复杂,为什么要麻烦呢

我发现证明MVVM过于复杂的唯一原因是,您可以编写一个GUI,它可以覆盖90%的测试,因为视图只覆盖程序的一小部分。

如果您认为自动化测试被高估了,那么您不应该使用MVVM。

我对WPF、C#和MVVM也很陌生。在这两三个月里,我读了不少书,所以也许我会分享我所理解的。

  1. 你似乎和我一两周前有同样的问题。数据不应存储在模型中。模型只是数据结构。数据库(或类似List的模拟替代品)是存储这些数据的实际存储器。有些人会简单地将它们保留在ViewModel中,而有些人会将它们移动到MVVM之外的某个位置(例如"Repository"类)。

  2. 你做错了。在MVVM中,视图不会以这种方式与ViewModels交互——它们通过Command和绑定进行交互。您的视图直接操纵列表,这意味着它肯定是错误的。

示例:

视图(窗口):

<Window.Resources>
    <vm:MyViewModel x:Key="MyVM" />
</Window.Resources>
<Window.DataContext>
    <StaticResourceExtension ResourceKey="MyVM" />
</Window.DataContext>
<DataGrid ItemsSource="{Binding PeopleList}" ..... >
<Button Command="{Binding StoreCommand}" .... >

ViewModel:

public static readonly DependencyProperty PeopleListProperty =
    DependencyProperty.Register("PeopleList",
    typeof(ObservableCollection<Person>),
    typeof(ViewModel));
public ObservableCollection<Person> PeopleList
{
    get
    {
        return GetValue(PeopleListProperty) as ObservableCollection<EmissionEntry>;
    }
    set
    {
        SetValue(PeopleListProperty, value);
    }
}
private ICommand _storeCommand;
public ICommand StoreCommand
{
    get
    {
        if (_storeCommand == null)
            _storeCommand = new MyCommandImplementation();
        return _storeCommand;
    }
}

Person是带有名称/年龄等的模型类。除非您想在某个地方拥有存储库,否则该列表将保存在ViewModel中。

你可能还没有真正读过关于ICommand的任何内容,所以我建议你先读一读。这里的教程太长了,但你可以在读了一些之后提问。

最新更新