Sam Martin
Sam Martin

Reputation: 13

Update Observable Collection When Item Added to Model C# wpf

Ok so I'm fairly new to this. I followed along with this MVVM tutorial from YouTube. It was pretty good and straightforward. Basically it sets up a very basic program with a Model class, DataAcess class, 3 viewmodels (Main window, Employee and ViewModelBase) and finally a view which has a stackpanel and a couple of text boxes that are bound to the FirstName and LastName in the Model.

It all works how it's meant to and I have been through it a number of times and I'm pretty sure I understand how it all works but the trouble that I am having is adding new Employees.

In the DataAccess class (Employee Repository) Employees are added as shown below.

    class EmployeeRepository
{
    readonly List<Employee> _employee;

    public EmployeeRepository()
    {
        if (_employee == null)
        {
            _employee = new List<Employee>();
        }

        _employee.Add(Employee.CreateEmployee("Bob", "Jones"));
        _employee.Add(Employee.CreateEmployee("Sarah", "Marshall"));
        _employee.Add(Employee.CreateEmployee("Peter", "Piper"));

    }

    public List<Employee> GetEmployees()
    {
        return new List<Employee>(_employee);
    }


}

And in the Model there is a method call CreateEmployee as such

    public class Employee
{
    public static Employee CreateEmployee(string firstName, string lastName)
    {

        return new Employee { FirstName = firstName, LastName = lastName };

    }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

So I thought I would add a button to the MainWindow and then add another name to the list. Hopping the view would update as an item is updated. Just to see if it would work I just used the code behind.

I thought I could just add a new employee the same way I did in the EmployeeRepository so I tried this

    readonly List<Employee> _employee = new List<Employee>();

    private void btnAdd_Click(object sender, RoutedEventArgs e)
    {
        _employee.Add(Employee.CreateEmployee("John", "Smith"));
    }

I have tried many many ways of doing this, to no avail. I have watched and read many tutorials and questions, but nothing that I have tried as worked.

What am I missing? I initially thought that it was not working because I am adding the item to the List in the repository, but not to the ObservableCollection that is in the viewmodel. And the AllEmployees ObservableCollection is the ItemSource for view.

    readonly EmployeeRepository _employeeRepository;

    public ObservableCollection<Model.Employee> AllEmployees
    {
        get;
        private set;
    }

    public EmployeeListViewModel(EmployeeRepository currentWindowRepository)
    {
        if (currentWindowRepository == null)
        {
            throw new ArgumentException("currentWindowRepository");
        }
        _employeeRepository = currentWindowRepository;
        this.AllEmployees = new ObservableCollection<Model.Employee>(_employeeRepository.GetEmployees());
    }

But in the button code I tried to implement something similar, but no.

I can also add the view xaml code and MainViewModel codes so that you can see how it's all bound if you like.

Thanks in advance for any help!

Upvotes: 1

Views: 3808

Answers (3)

Mark Feldman
Mark Feldman

Reputation: 16148

One solution to this is to add INPC to your models and to then have your view models watch their models and update themselves accordingly i.e. something like this:

public class MyListType
{
    // some data
}

public class MyModel
{
    public IList<MyListType> MyListItems { get; set; }

    public MyModel()
    {
        this.MyListItems = new ObservableCollection<MyListType>();
    }
}

public class MyListTypeViewModel : ViewModelBase
{
    public MyListType Model {get; set;}

    // INPC properties go here
}

public class MyViewModel
{
    public IList<MyListTypeViewModel> MyListItemViewModels { get; set; }

    public MyViewModel(MyModel model)
    {
        (model.MyListItems as INotifyCollectionChanged).CollectionChanged += OnListChanged;
        // todo: create initial view models for any items already in MyListItems
    }

    private void OnListChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        // create any new elements
        if (e.NewItems != null)
            foreach (MyListType item in e.NewItems)
                this.MyListItemViewModels.Add(new MyListTypeViewModel{Model = item});

        // remove any new elements
        if (e.OldItems != null)
            foreach (MyListType item in e.OldItems)
                this.MyListItemViewModels.Remove(
                    this.MyListItemViewModels.First(x => x.Model == item)
                );
    }

Now your list of view models will automatically stay synched with your list of models. The main problem with this approach is that your models will typically originate from your ORM (database) code, so you will need to work with whatever framework you're using to inject INPC at creation time e.g. if you're using NHibernate then you'll need to use a binding interceptor for INPC and a collection convention to make the lists ObservableCollections.

Upvotes: 0

Tseng
Tseng

Reputation: 64298

You can't do it in "one operation".

When you add a new Employee in the UI, you first need to instantiate your Employee class and add it to the observable collection.

If in valid state, then persist it to in the repository.

private ICommand addEmployeeCommand;
public ICommand AddEmployeeCommand { get { return addEmployeeCommand; } }

public ObservableCollection<Employee> Employees { get; protected set; }

private void AddEmployee() 
{
    // Get the user input that's bound to the viewmodels properties
    var employee = Employee.Create(FirstName, LastName);

    // add it to the observable collection
    // Note: directly using model in your ViewModel for binding is a pretty bad idea, you should use ViewModels for your Employees too, like: 
    // Employee.Add(new EmployeeViewModel(employee));
    Employees.Add(employee);

    // add it to the repository
    this.employeeRepository.AddOrUpdate(employee);
}

// in constructor
this.addEmployeeCommand = new DelegateCommand(AddEmployee, CanExecuteAddEmployee);

As noted, avoid directly using your model inside the ViewModel bindings, it has several disadvantages, like you view now depend on your viewmodel. each and every change in the model needs to be reflected in the view, this beats the purpose of a viewmodel which is meant to decouple view, viewmodel and model.

Another disadvantage is, that typically your models are do not implement INotifyPropertyChanged and this will cause memory leaks in the view.

Upvotes: 1

AnjumSKhan
AnjumSKhan

Reputation: 9827

In your EmployeelistViewModel you are creating ObservableCollection , and you think that it will get repopulated automatically upon addition/deletion of employees. secondly in your GetEmployees method you are creating a new list. you should use obser.coll directly in place of List (_employee). And return this ocoll from your method.

Upvotes: 0

Related Questions