Binding to multiple data sources

Let's review a situation when we have several data sources with different types of information (e.g. orders and strategies), and we need to display this information in the grid. The grid enables simple connection of information from different data sources simultaneously. The component model has IListSource interface that enables objects to return a list that can be bound to a data source. It can be a datatable or a dataset object. In our example we shall use this interface to bind multiple data sources to the grid.

public class MultipleDataSources : IListSource
{
    private readonly IList _listSource = new ArrayList();

    public IList GetList()
    {
        return _listSource;
    }

    public bool ContainsListCollection
    {
        get { return true; }
    }
}

public void PopulateGrid(Grid grid)
{
    BindingList<Strategy> strategies = new BindingList<Strategy>();
    //Populate strategies...

    BindingList<Order> orders = new BindingList<Order>();
    //Populate orders...

    //Combine multiple data sources in the MultipleDataSources object
    MultipleDataSources multipleSources = new MultipleDataSources();
    multipleSources.GetList().Add(orders);
    multipleSources.GetList().Add(strategies);

    //Bind the grid to datasources
    grid.DataSource = multipleSources;
}

Support of data binding in the grid is a powerful and convenient feature for working with various data sources. IBindingList is a very convenient tool for storing data collections, however it is completely unsuitable for storing hierarchical data. In addition to Grid.DataSource you can also add data objects one by one with Grid.Rows.Add() method. This method of adding data works simultaneously with datasource and these two features may complement each other. Let’s note that objects added via Grid.Rows.Add() can also be used to build a hierarchy.

public void PopulateGrid(Grid grid)
{
    Strategy strategy = new Strategy("Strategy 1");
    strategy.Orders.Add(new Order("SubOrder1", 10.15, 34));
    strategy.Orders.Add(new Order("SubOrder2", 30.51, 43));
    grid.Rows.Add(strategy);

    Strategy strategy = new Strategy("Strategy 2");
    strategy.Orders.Add(new Order("SubOrder1", 10.15, 34));
    strategy.Orders.Add(new Order("SubOrder2", 30.51, 43));
    grid.Rows.Add(strategy);
}

As it has been shown, the grid provides powerful data binding support for various data types and sources. A binding list is a convenient feature that notifies the grid of collection change or of changes inside a data object, however it provides no way to discover exact object changes. The component model has a wonderful INotifyPropertyChanged interface that can notify subscribers of changes in specific business object fields. Therefore, controls get notifications of data changes and redraw information in UI.

We have extrapolated this scheme to objects inserted to the grid with Grid.Rows.Add() or Grid.DataSource methods. If an object implements INotifyPropertyChanged interface, the grid subscribes to events of this object, automatically redraws data, sorts and filters it, without regard to how this data object is added to the grid. Let's review a case when order parameters are changed as an example. Just add INotifyPropertyChanged implementation to order objects.

public class Order : INotifyPropertyChanged
{
    ...

    public double Price
    {
        get { return _price; }
        set
        {
            _price = value;
            if(PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs("Price"));
            }
        }
    }

    public long Quantity
    {
        get { return _quantity; }
        set 
        { 
            _quantity = value;
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs("Quantity"));
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

Data binding and multithreading.

The last issue we have to review is thread protection and its use with data binding. As it is well known, graphical components may work only in one thread. Calling controls and methods from non-GUI thread causes exceptions and application crashes. With data binding, when data implements INotifyPropertyChanged or IBindingList interface, the grid subscribes to notifications of these objects. Therefore, these notifications may come from any thread. Grid architecture enables it to get messages from any thread and then perform synchronization with GUI thread, thus making the data model fully independent of the presentation model. This is true for multithreading model as well. We have to note that during synchronization of IBindingList notifications the calling thread is locked (Control.Invoke call), and for synchronization of INotifyPropertyChanged notifications the programmer has a choice whether to lock the thread (Control.BeginInvoke calls).