【问题标题】:wpf application mvvm how to structure properlywpf应用mvvm如何正确构造
【发布时间】:2016-05-13 05:46:14
【问题描述】:

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

到目前为止我所拥有的:

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

  • 一个模型类people,拥有一个私有链表(人的列表),其中还包含get、remove、add等方法并做一些计算

    李>
  • 一个视图模型类,在 xaml 后面的代码和模型之间进行转换/解析/转换。

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

我没有使用INotifyPropertyChangedObservableCollection,仍然试图将我的头包裹起来。虽然我的程序做我想做的事,但我不确定如何更好地构建它。

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

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

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

【问题讨论】:

  • 你需要使用 INPC 和 ObservableCollection,否则它不是 MVVM。 MVVM 应该在 Model 中完成所有动作,而 INPC/INCC 是 View 如何知道数据已更改,以及屏幕需要更新的方式。听起来您实际上是在编写 MVP 应用程序。

标签: c# wpf mvvm


【解决方案1】:

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

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

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

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

你的代码可以这样构造:

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>

【讨论】:

  • 您好 nkoniishvt,很好的答案。但是有很多样板,我会用一些库来代替,比如 ReactiveUI。
  • @Aron 谢谢!我需要看看这个库,我尝试使用标准方式太多了。
  • ReactiveUI 有一个 .CreateDerivedCollection() 扩展方法,可以为您执行 PeopleVM。然后Fody.PropertyChanged 会自动为你实现 PropertyChanged。大大节省时间。
  • 收藏同步用我用的方式确实很烦人,我会研究一下
【解决方案2】:

对您的帖子的回答可以根据自己的意愿进行解释,也可以是一篇很长的博客。我将尝试在这里回答您的 2 个具体问题。我不会显示每个子答案的代码,你必须把它当作家庭作业。 :)

我没有使用 INotifyPropertyChanged ObservableCollection,还在尝试 把我的头缠在它周围。虽然我的程序做我想要的,但我不是 确定如何更好地构建它。

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

我在网上看到人们存储/启动项目列表的示例 viewmodel,我不应该将列表保留在模型中吗,应该是 所有数据都存储在哪里?

他们没有错。模型层中的Person 类代表一个真实世界的实体,并且是必须的,但是,我不会担心模型中有People 类。它只是一个 ViewModel 可以轻松容纳的集合。我个人更喜欢这种方式。

假设我想显示所有项目(在 模型类)到数据网格上。现在在我的程序中: mainWindow.xaml.cs 将检测到按钮单击,然后询问 viewModel 将其存储在模型中,如果没有错误,则代码后面的 xaml 将执行 people_dataGrid.Items.Add(新人{名称=新名称,年龄=新年龄, 地址 = newAdd });这是不好的做法吗?不知道怎么用 ObservableCollection 在这里,它能否以某种方式检测到列表中的变化 我的模型类,然后删除行并将其添加到数据网格?

这不是 MVVM,相信我。最多您应该在视图代码中编写的内容是初始化视图模型并将其设置为视图的数据上下文。

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

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

【讨论】:

  • 感谢您的指导!我会从头开始一切工作
【解决方案3】:

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

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

但你必须全部完成,否则它不是 MVVM。

MVVM 之禅

MVVM 是在意识到编写好的、无错误、安全的 UI 代码很难的情况下发展起来的。测试 UI 代码更难,并且需要雇佣人工测试人员,这很慢并且可能会出错。

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

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

完成。

除了,不是。现在,您的 UI 没有做任何事情,它只是看起来很漂亮。所以他们在 UI 和 Program/Business Logic/Model 之间添加了一个额外的层,他们称之为ViewModel。

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

(MVP 有类似的概念,但 Presenter 确实了解 UI)

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

时间很好!

除了 View Model 仍然不知道 UI。所以我们知道 UI 应该是什么样子,但 UI 不知道,因为没有人告诉它......

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

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

你有 WPF 的做事方式,即实现一个事件,告诉 Binding 类 ViewModel 已更新。这既快速又灵活,但写起来真的很烦人。

你有 AngularJS 的做事方式,即轮询 ViewModel 以获取更新。这是非常缓慢和错误的。

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

一切都那么复杂,何必呢?

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

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

【讨论】:

    【解决方案4】:

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

    1. 您的问题似乎与我一两周前的问题相同。数据不应存储在模型中。模型只是数据结构。数据库(或类似Lists 的模拟替代品)是存储这些数据的实际存储。有些人会简单地将它们保留在 ViewModel 中,而有些人会将它们移动到 MVVM 之外的东西(例如“Repository”类)。

    2. 你做错了。在 MVVM 中,视图不以这种方式与视图模型交互——它们通过Commands 和绑定进行交互。您的 View 直接操纵列表这一事实意味着它绝对是错误的。

    例子:

    视图(窗口):

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

    视图模型:

    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 的任何内容,因此我建议您先阅读一下。教程太长了,看完后大家可以提问。

    【讨论】:

    • 这里的大部分信息都是正确的。然而,你不应该在你的模型/视图模型中使用依赖属性。依赖属性专门用于 View 类。它们的部分用途是帮助 XAML 引擎绑定到属性。
    • 为什么会这样?我一直在使用依赖属性而不是 INotifyPropertyChanged。然而,说视图必须使用依赖属性是正确的,因为绑定目标必须是依赖属性。据我所知,绑定可以在依赖属性源上完成。除非有什么不好的地方使用这种方法。 @Aron
    • 几个原因。 a) DependencyObject 是一个 WPF 类,在模型中使用它会破坏 POCO,并消除 WPF/MVVM 的关键优势,即与您的视图技术分离。这意味着测试更难 b) WPF 团队做了很多工作以确保 WPF/Binding 将 INPC 编组回 UI 线程(这是另一件有助于与 UI 技术分离的事情,对 CollectionViews 感到羞耻)。 DependencyProperties 只能从 UI 线程访问。
    • 谢谢大家,你们现在给了一些方向
    猜你喜欢
    • 1970-01-01
    • 2012-10-26
    • 2020-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多