Binding to Hierarchical Fields

.Net Grid supports various methods of data binding giving programmers a broad choice in application implementation. Generally speaking, data binding is a way to connect graphical components to data sources that greatly simplifies application logic, reduces code volume and greatly improves application quality.

Binding with IBindingList is most frequently used in grids as it is targeting data collections. However, this method has a serious limitation as it doesn't support hierarchical data. There are multiple workaround solutions for this issue, e.g. combining objects that should be located on different hierarchy levels in one list and entering their hierarchy information through Id and ParentId keys. This method is hard to implement and negatively impacts performance as it often has to rebuild the entire data collection to update data.

Although binding lists have simple and intuitive interface, this interface is not fit for hierarchical data display, and therefore there is not much reason to use it. There is an easier method to link binding list data to the grid in hierarchy mode as shown below.

Hierarchical data binding

Let's review the following example involving a binding list.

public class Order
{
private readonly string _instrument;
private readonly double _price;
private readonly long _quantity;

public Order(string instrument, double price, long quantity)
{
_instrument = instrument;
_price = price;
_quantity = quantity;
}

public string Instrument
{
get { return _instrument; }
}

public double Price
{
get { return _price; }
}

public long Quantity
{
get { return _quantity; }
}
}

public void PopulateGrid(Grid grid)
{
BindingList<Order> orders = new BindingList<Order>();

orders.Add(new Order("Instrument1", 10.55, 34));
orders.Add(new Order("Instrument2", 12.26, 154));
orders.Add(new Order("Instrument1", 13.16, 14));
orders.Add(new Order("Instrument5", 9.85, 52));
orders.Add(new Order("Instrument1", 16.47, 11));

grid.DataSource = orders;
}

As it is demonstrated above, data can be easily linked to the grid, it can be added, removed, sorted, etc. However, hierarchy creation is not available.

Now let's say that we have a strategy that consists of multiple orders. These orders can be dynamically added to the strategy, and data values of these orders may vary.


public class Strategy
{
private readonly string _name;
private readonly double _price;
private readonly long _quantity;

private readonly BindingList<Order> _orders = new BindingList<Order>();

public Strategy(string name)
{
_name = name;
}

public IList<Order> Orders
{
get { return _orders; }
}

public string Name
{
get { return _name; }
}

public int OrderCount
{
get { return _orders.Count; }
}
}


A strategy has a list of orders. We are using a binding list that can notify the subscribers of collection changes. Now let's create a collection of strategies and connect it to the grid.


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

Strategy strategy = new Strategy("Strategy 1");
strategy.Orders.Add(new Order("SubOrder1", 10.15, 34));
strategy.Orders.Add(new Order("SubOrder2", 30.51, 43));
strategies.Add(strategy);

//Add other strategies to the binding list
//...
//Bind the grid
grid.DataSource = strategies;
}

As you can see, the grid displays only the list of strategies without any hierarchy. To enable the grid to display a hierarchy, let's specify that the field returning collection of orders should be interpreted as a hierarchical field that requires hierarchical display of data.
public class Strategy
{
...

[HierarchicalField]
public IList<Order> Orders
{
get { return _orders; }
}

...
}

The grid subscribes to binding list events and automatically adds, removes and modifies data. Architecture of an application with multiple  binding lists is more flexible and allows to avoid mixing different data in a single source. Let's also note that if the orders are also hierarchical and also return binding lists, the next hierarchy level is created automatically.

Binding to multiple data sources

Now 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.

This interface is used in the binding method involving Binding object. 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 may decide whether to lock the thread or not (Control.BeginInvoke calls).

Professionals working with complex systems will surely appreciate our efforts that make the code simpler and improve its reliability.

Back to .Net Grid Features