MVVM:如何获取反映模型更改的视图(自定义集合)

时间:2014-03-04 15:12:58

标签: wpf design-patterns mvvm

我正在尝试使用WPF和MVVM。 我的ViewModel包含一个自定义集合(IEnumerable),它必须在UI中呈现。我添加了一些代码来向 Model 添加一个新实体,我想这将在UI中呈现。但这不会发生。

[1] 我的模式错了吗? [2] 为什么新的实体没有反映在View中,虽然我提出了INPC?

请在下面找到我的代码

Classes.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace _06_MVVMTest___Add_to_Model_Reflects_in_View
{
    public class Employee
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
        public int Salary { get; set; }
        public Employee(int id, string name,int age, int salary)
        {
            this.Id = id;
            this.Name = name;
            this.Age = age;
            this.Salary = salary;
        }
    }


    public class EmployeeCollection : IEnumerable
    {
        List<Employee> employees = new List<Employee>();
        public EmployeeCollection()
        {
            employees.Add(new Employee(100, "Alice", 23, 300));
            employees.Add(new Employee(100, "Bob", 22, 400));
            employees.Add(new Employee(100, "Trude", 21, 200));

        }
        public IEnumerator GetEnumerator()
        {
            foreach (Employee emp in employees)
            {
                if (emp==null)
                {
                    break;
                }

                yield return emp;

            }
        }
        public void AddNewEmployee()//For Test
        {
            employees.Add(new Employee(200, "Dave", 21, 2000));
        }
    }

    public class EmployeeViewModel:INotifyPropertyChanged
    {
        EmployeeCollection employees=new EmployeeCollection();

        public EmployeeCollection Employees
        {
            get { return employees; } 

        }



        public event PropertyChangedEventHandler PropertyChanged;

        public void NotifyOnEmployeeCollectionChanged()
        {
            if (PropertyChanged!=null)
                PropertyChanged(this,new PropertyChangedEventArgs("Employees"));
        }
    }
}

MainWindow.xaml.cs

sing System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace _06_MVVMTest___Add_to_Model_Reflects_in_View
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            EmployeeViewModel vm =(EmployeeViewModel) this.DataContext;
            vm.Employees.AddNewEmployee();
            vm.NotifyOnEmployeeCollectionChanged();
        }
    }
}

MainWindow.xaml

<Window x:Class="_06_MVVMTest___Add_to_Model_Reflects_in_View.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:_06_MVVMTest___Add_to_Model_Reflects_in_View"
        Title="Employee Window" Height="350" Width="525">
    <Window.DataContext>
        <local:EmployeeViewModel />
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="80"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <ListView Height="300" Grid.Row="0" Grid.Column="0" BorderBrush="Blue" ItemsSource="{Binding Employees}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <Grid  Background="LightCoral" Margin="10,10,10,10">
                        <StackPanel>
                            <TextBlock Text="{Binding Name}"/>
                            <TextBlock Text="{Binding Age}"/>
                        </StackPanel>
                    </Grid>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
        <ListView Height="300"  Grid.Row="0" Grid.Column="1" BorderBrush="Blue" ItemsSource="{Binding Employees}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <Grid  Background="LightCoral" Margin="10,10,10,10">                        
                    <StackPanel>                        
                        <TextBlock Text="{Binding Name}"/>
                        <TextBlock Text="{Binding Age}"/>
                    </StackPanel>
                    </Grid>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
        <Button Grid.Row="1" Height="40" Content="add new employee" Click="Button_Click" />
    </Grid>
</Window>

2 个答案:

答案 0 :(得分:1)

WPF处理属性更改的方式以及通知更改的方式存在问题。 WPF会检查您的值是否仍然相同; EmployeeCollection实际上与旧的相同(它仍然是同一个实例,它不会在集合中查看)。

一种解决方案是将EmployeeCollection的实现更改为:

public class EmployeeCollection : IEnumerable, INotifyCollectionChanged
{
    private ObservableCollection<Employee> employees = new ObservableCollection<Employee>();

    public EmployeeCollection()
    {
        employees.Add(new Employee(100, "Alice", 23, 300));
        employees.Add(new Employee(100, "Bob", 22, 400));
        employees.Add(new Employee(100, "Trude", 21, 200));
    }

    public IEnumerator GetEnumerator()
    {
        return employees.GetEnumerator();
    }

    public void AddNewEmployee() //For Test
    {
        employees.Add(new Employee(200, "Dave", 21, 2000));
    }

    public event NotifyCollectionChangedEventHandler CollectionChanged
    {
        add { employees.CollectionChanged += value; }
        remove { employees.CollectionChanged -= value; }
    }
}

这引入了INotifyCollectionChanged接口,并且无需在主窗口中调用vm.NotifyOnEmployeeCollectionChanged(),因为ObservableCollection<T>底层的EmployeeCollection将告诉WPF它已更改。

如果您现在不想了解这些概念,请快速解决:

public void NotifyOnEmployeeCollectionChanged()
{
    var current = this.employees;
    this.employees = null;
    if (PropertyChanged != null)
        PropertyChanged(this, new PropertyChangedEventArgs("Employees"));
    this.employees = current;
    if (PropertyChanged != null)
        PropertyChanged(this, new PropertyChangedEventArgs("Employees"));
}

答案 1 :(得分:1)

您的自定义收藏集应该实现INotifyCollectionChanged。每次添加项目时,都会触发CollectionChanged事件。

public class EmployeeCollection : IEnumerable, INotifyCollectionChanged
{
    public event NotifyCollectionChangedEventHandler CollectionChanged;

    public void AddNewEmployee() //For Test
    {
        employees.Add(new Employee(200, "Dave", 21, 2000));

        if (CollectionChanged != null)
            CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add));
}