Question

I am experimenting with WPF and MVVM. My ViewModel contains a custom collection (IEnumerable) which has to be rendered in UI. I have added some code to add a new entity to the Model which I suppose to be rendered in UI. But that does not happen.

[1] Am I wrong with the pattern? [2] Why isn't the new entity is not reflected in the View, though I raise INPC?

Please find my code below

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>
Was it helpful?

Solution

There is a problem with the way WPF handles property changes along with your way of notifying the change. WPF checks to see if your value is still the same; the EmployeeCollection is actually the same as the old one (it is still the same instance and it won't look inside the collection).

One solution is to change the implementation of EmployeeCollection to this:

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; }
    }
}

This introduces the INotifyCollectionChanged interface and removes the need to call vm.NotifyOnEmployeeCollectionChanged() in the main window, since the ObservableCollection<T> underlying the EmployeeCollection will tell WPF that it has changed.

If you do not want to learn about these concepts for now, a quick work around would be to :

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"));
}

OTHER TIPS

Your custom collection should implement INotifyCollectionChanged. Every time you add an item, fire the CollectionChanged event.

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));
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top