Data binding is used by almost all modern applications. It provides simple means to separate the data layer from the presentation layer. Generally, binding means connecting a graphical control property with a data object property. For example, if we have Product class, Label control can be bound to Name property of this control.

C# Copy imageCopy
class Product
{
    private readonly string _name;

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

    public string Name
    {
        get { return _name; }
    }
}

//Create an instance of the product
Product product = new Product("Some product");

//Bind the property 'Text' of the control to the Product.Name property.
label1.DataBindings.Add(new Binding("Text", product, "Name"));

In grids, Product class objects can be stored in collections such as BindingList<T>. When the grid is bound to a collection, values returned by Product object properties are displayed in cells.

Let’s see what happens, when values in Product object start changing. Microsoft component model provides INotifyPropertyChanged interface that can notify controls of changes in data objects. Let’s say, Product class has Price property, whose setter fires a notification on value change over the specified interface.

C# Copy imageCopy
class Product : INotifyPropertyChanged
{
    private readonly string _name;
    private double _price;

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

    public string Name
    {
        get { return _name; }
    }

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

    public event PropertyChangedEventHandler PropertyChanged;
}

When a regular control is bound to Price property, it gets a notification, requests new price value from Product object, and displays this value in the control.

When BindingList<T> collection is created, it checks data types that it will use and if they implement INotifyPropertyChanged interface, the collection subscribes to every object inside it. When the collection receives a notification, it transforms the object to IBindingList.ListChanged with ItemChanged value notifying the grid of changes in the collection. However, due to incorrect implementation of notification, BindingList handler has serious performance issues (see this for more information). Dapfor.Net.dll library provides an improved implementation of this container that significantly accelerates BindingList<T> performance with objects implementing INotifyPropertyChanged interface and ensures thread safety of the collection.

After the purpose of INotifyPropertyChanged and IBindingList interfaces has become clear, let’s look at grid's reaction on notification. When the grid works with data implementing INotifyPropertyChanged interface, it subscribes to changes of every object no matter how it has been added to the grid (either via grid binding to collections via Grid.DataSource/Row.DataSource or by adding objects via Grid.Rows.Add()/Row.Add() methods). When the grid gets a notification from such object, it first checks whether thread synchronization is required. If needed, the grid performs such synchronization using Control.Invoke() /Control.BeginInvoke() method depending on selected thread model. On the next stage, it checks whether the row has to be moved to a new required position if sorting is used, whether it should be hidden or displayed based on filtering and whether it complies with grouping conditions. If a value used for grouping has changed, the grid moves the row to the relevant group, creating new groups and removing old groups as necessary.

To demonstrate power and convenience of the event-driven model, let’s look at the example of data updating in real-time.

C# Copy imageCopy
class Order : INotifyPropertyChanged
{
    private readonly Product _product;
    private double _price;
    private long _quantity;

    public Order(Product product, double price, long quantity)
    {
        _product = product;
        _price = price;
        _quantity = quantity;
    }

    [CompositeField]
    public Product Product
    {
        get { return _product; }
    }

    public double Price
    {
        get { return _price; }
        set
        {
            if (_price != value)
            {
                _price = value;
                FirePropertyChanged("Price");
            }
        }
    }

    public long Quantity
    {
        get { return _quantity; }
        set
        {
            if(_quantity != value)
            {
                _quantity = value;
                FirePropertyChanged("Quantity");
            }
        }
    }

    private void FirePropertyChanged(string field)
    {
        if(PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(field));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

Objects of these classes can be added to the grid one by one:

C# Copy imageCopy
Product product = new Product("Some product");
Order order = new Order(product, 123.32, 15);

grid.Rows.Add(order);

However, the grid usually connects to binding lists.

C# Copy imageCopy
Product product1 = new Product("Product 1");
Product product2 = new Product("Product 2");
Product product3 = new Product("Product 3");

BindingList<Order> orders = new BindingList<Order>();
orders.Add(new Order(product1, 123.32, 15));
orders.Add(new Order(product2, 110.11, 54));
orders.Add(new Order(product1, 151.93, 46));
orders.Add(new Order(product3, 98.16, 44));
orders.Add(new Order(product2, 165.91, 64));
orders.Add(new Order(product1, 74.96, 77));

grid.DataSource = orders;
Binding to BindingList collection

Now let's add a timer that will simulate data update in real time.

C# Copy imageCopy
Random random = new Random();
timer = new Timer();
timer.Tick += delegate
{
    //Get random order and up to date the price
    Order order = orders[random.Next(orders.Count)];
    order.Price = random.Next(50, 150) + random.NextDouble();
};

//Set interval and start timer
timer.Interval = 500;
timer.Start();
Realtime data updating

Now let’s add a filter that displays only the most significant changes (e.g. when value change exceeds 10%) of the initial price. As we can see, nothing has changed from the business logic point of view.

C# Copy imageCopy
//Implementation of the format to display changes of the initial price
class DeltaFormat : IFormat
{
    public string Format(IDataField dataField)
    {
        Order order = (Order) dataField.DataAccessor.DataObject;
        double delta = Math.Abs(order.Price - order.InitialPrice) / order.InitialPrice;
        return string.Format("{0} {1:#} %", order.Price > order.InitialPrice ? "+" : "-", 100 * delta);
    }

    public bool CanParse(string text, IDataField dataField) { return false; }
    public void Parse(string text, IDataField dataField)    {}
}

//Create unbound column to display % change
Column unboundColumn = new Column("unboundColumn", "% Change");
unboundColumn.Format = new DeltaFormat();
grid.Headers[0].Add(unboundColumn);

//Modified data object:
class Order : INotifyPropertyChanged
{
    ...
    private readonly double _initialPrice;

    public Order(Product product, double price, long quantity)
    {
        ...
        _initialPrice = price;
    }

    ...

    public double InitialPrice
    {
        get { return _initialPrice; }
    }

    public double Price
    {
        get { return _price; }
        set
        {
            if (_price != value)
            {
                _price = value;
                //Notify without field to refresh the whole row including the '% Change' column
                FirePropertyChanged(string.Empty);

                //This notification will refresh and highlight only the 'Price' column
                FirePropertyChanged("Price");
            }
        }
    }

    ...

    public event PropertyChangedEventHandler PropertyChanged;
}

//Filter implementation:
grid.Filter = new Filter(delegate(Row row)
{
    Order order = (Order) row.DataObject;
    double delta = Math.Abs(order.Price - order.InitialPrice);
    bool bigChange = delta/order.InitialPrice > 0.10;
    return !bigChange;
});
Realtime data filtering in event-driven model

Let’s add grouping and sorting

C# Copy imageCopy
//Group by the 'Product' column
grid.Headers[0].GroupingEnabled = true;
grid.Headers[0]["ProductName"].Grouped = true;
grid.Headers[0]["ProductName"].Visible = false;

//Enable multiple sorting by 'Product' and 'Price' columns
grid.Headers[0]["ProductName"].SortDirection = SortDirection.Ascending;
grid.Headers[0]["Price"].SortDirection = SortDirection.Ascending;
Realtime data filtering grouping and sorting in event-driven model

The event-driven data model works equally well with or without any types of hierarchies described in .Net Grid tutorial (Part2: Data binding). It also supports declarative hierarchy building when any hierarchy levels can be updated.

C# Copy imageCopy
Row rowCategory = grid.Rows.Add(new string[] { "Some category" });
rowCategory.Expanded = true;

Product product1 = new Product("Product 1");
Product product2 = new Product("Product 2");
...

BindingList<Order> orders = new BindingList<Order>();
orders.Add(new Order(product1, 123.32, 15));
orders.Add(new Order(product2, 110.11, 54));
...

rowCategory.DataSource = orders;
Realtime data filtering grouping sorting and hierarchy in event-driven model

Now a few words on thread protection. Since threads firing notifications to the grid are systematically synchronized with the main thread, business object values can be modified from any thread. Receiving data from TCP/IP is a typical example. For demonstration purposes let's replace System.Windows.Forms timer with a timer operating in non-GUI thread:

C# Copy imageCopy
System.Threading.Timer timer = new System.Threading.Timer(delegate
{
    Order order = orders[random.Next(orders.Count)];
    order.Price = random.Next(50, 200) + random.NextDouble();    
}, null, 0, 500);

The grid will still receive and handle data although notifications arrive from a secondary thread. Grids and other GUI controls without such protection generate InvalidOperationException in such cases.

Finally, we come to the issue of performance. When notifications from IBindingList or INotifyPropertyChanged interfaces are received, it doesn’t perform any operations with rows outside the visible area. In other words, the grid has been developed to minimize consumption of CPU resources and is capable of processing many thousands of notifications per second.