views:

224

answers:

3

I have two classes: Employee and EmployeeGridViewAdapter. Employee is composed of several complex types. EmployeeGridViewAdapter wraps a single Employee and exposes its members as a flattened set of system types so a DataGridView can handle displaying, editing, etc.

I'm using VS's builtin support for turning a POCO into a data source, which I then attach to a BindingSource object. When I attach the DataGridView to the BindingSource it creates the expected columns and at runtime I can perform the expected CRUD operations. All is good so far.

The problem is the collection of adapters and the collection of employees aren't being synchronized. So all the employees I create an runtime never get persisted. Here's a snippet of the code that generates the collection of EmployeeGridViewAdapter's:

        var employeeCollection = new List<EmployeeGridViewAdapter>();
        foreach (var employee in this.employees)
        {
            employeeCollection.Add(new EmployeeGridViewAdapter(employee));
        }
        this.view.Employees = employeeCollection;

Pretty straight forward but I can't figure out how to synchronize changes back to the original collection. I imagine edits are already handled because both collections reference the same objects but creating new employees and deleting employees aren't happening so I can't be sure.

A: 

Seems like you need to replace List<EmployeeGridViewAdapter>() with a custom collection which implements adding and deleting behaviour.

Al Bundy
Could you elaborate? Are you suggesting wrapping the `List<Employee>` and having the wrapper return a new `EmployeeGridViewAdapter` for each enumeration?
codeelegance
Implement ICollection<EmployeeGridViewAdapter>. Your class should have an instance of List<Employee> so your Add and Remove implementation will add or remove Employees from the List<Employee>. Bind this collection to your View.
Al Bundy
+1  A: 

The first problem seems to be that you are creating a new list and data binding to that. When you're adding elements these will be added to the collection but your original employee list remains unmodified.

To avoid this you should either provide a custom collection class that will migrate changes back to the underlying employee list, or wire up the appropriate events (to do the migration on insert/delete) before data binding to it.

To avoid a number of other problems with binding editable collections to grids, you should implement the data binding interfaces, as outlined below. The presence of these interfaces allows the visual controls to notify the underlying collection about actions such as "insert cancelled" (when users aborts entry of a new record), and similarly allows information to flow in the opposite direction (update UI when collection or individual entries change).

First, you'll want to implement at least IEditableObject, INotifyPropertyChanged and IDataErrorInfo on the individual items in a data-bound collection, which in your case would be the EmployeeGridViewAdaper class.

Additionally, you'd want your collection to implement ITypedList and INotifyCollectionChanged. The BCL contains a BindingList implementation which provides a good starting point for this. Recommend using this instead of the plain List.

I can recommend Data Binding with Windows Forms 2.0 for an exhaustive coverage of this topic.

Morten Mertner
+1  A: 

You could also considering using System.Collections.ObjectModel.ObservableCollection and wiring up it's CollectionChanged event. It could look something like this.

        ObservableCollection<EmployeeAdapter> observableEmployees = 
                    new ObservableCollection<EmployeeAdapter>();

        foreach (Employee emp in employees)
        {
            observableEmployees.Add(new EmployeeAdapter(emp));
        }

        observableEmployees.CollectionChanged += 
            (object sender, NotifyCollectionChangedEventArgs e) =>
            {
                ObservableCollection<EmployeeAdapter> views = 
                        sender as ObservableCollection<EmployeeAdapter>;
                if (views == null)
                    return;
                switch (e.Action)
                {
                     case NotifyCollectionChangedAction.Add:
                        foreach (EmployeeAdapter view in e.NewItems)
                        {
                            if (!employees.Contains(view.Employee))
                                employees.Add(view.Employee);
                        }
                        break;
                     case NotifyCollectionChangedAction.Remove:
                        foreach (EmployeeAdapter view in e.OldItems)
                        {
                            if (employees.Contains(view.Employee))
                                employees.Remove(view.Employee);
                        }
                        break;
                    default:
                        break;
                }
            };

Code assumes the following using statements.

using System.Collections.ObjectModel;
using System.Collections.Specialized;

If you need the IList interface you could also use System.ComponentModel.BindingList and wire up it's ListChanged event. It could look like this.

BindingList<EmployeeAdapter> empViews = new BindingList<EmployeeAdapter>();

foreach (Employee emp in employees)
{
    empViews.Add(new EmployeeAdapter(emp));
}

empViews.ListChanged +=
        (object sender, ListChangedEventArgs e) =>
            {
                BindingList<EmployeeAdapter> employeeAdapters = 
                        sender as BindingList<EmployeeAdapter>;
                if (employeeAdapters == null)
                    return;

                switch (e.ListChangedType)
                {
                    case ListChangedType.ItemAdded:
                        EmployeeAdapter added = employeeAdapters[e.NewIndex];
                        if (!employees.Contains(added.Employee))
                            employees.Remove(added.Employee);
                        break;
                    case ListChangedType.ItemDeleted:
                        EmployeeAdapter deleted = employeeAdapters[e.OldIndex];
                        if (employees.Contains(deleted.Employee))
                            employees.Remove(deleted.Employee);
                        break;
                    default:
                        break;
                }
            };

Code assumes the following using statement.

using System.ComponentModel;
VoidDweller