.Net Grid tutorial (Part2: Data binding)

Data binding links a data layer with graphical controls and enables data independency of its presentation. Data binding is broadly used in WinForms applications, while in WPF applications it is practically the only data presentation method. Correct organization of data binding is the foundation of well-built applications. Any mistakes at the design stage may turn out costly later on. This tutorial is dedicated to .Net Grid methods of work with data and data binding organization.

All grids use binding to data collections of (IList, IBindingList, IListSource) type as the main and often the only method of working with data. Besides, connection to IBindingList, Dapfor NetGrid significantly expands data binding features enabling operations in unbound mode. Using grid in each of these modes will be reviewed in detail below.

Unbound data in the grid

In this mode, data is not located in a binding list but is added to the grid one by one. Data can be added on any hierarchy level via Row type objects.


//Build a header with columns
grid.Headers.Add(new Header());
grid.Headers[0].Add(new Column("IntValue"));
grid.Headers[0].Add(new Column("DoubleValue"));
grid.Headers[0].Add(new Column("StringValue"));

//Add data object at the top-level and attach 2 children of the same type
Row row1 = grid.Rows.Add(new MyCustomClass(10, 11.11, "item 1"));
row1.Add(new MyCustomClass(11, 11.22, "subitem 1"));
row1.Add(new MyCustomClass(12, 11.22, "subitem 2"));

//Add data other data object with 2 children
Row row2 = grid.Rows.Add(new MyCustomClass(20, 22.11, "item 2"));
row2.Add(new MyCustomClass(22, 22.22, "subitem 1"));
row2.Add(new MyCustomClass(22, 22.22, "subitem 2"));
Single-header treelist view

.Net Grid makes it easy adding any number of headers, thus changing the presentation from the treelist with a single to multi-header grid.

grid.Headers.Add(new Header());
grid.Headers[1].Add(new Column("DoubleValue"));
grid.Headers[1].Add(new Column("StringValue"));
Multi-header grid

In the unbound mode .Net Grid supports all data types, described in .Net Grid tutorial (Part1: Data types)

//Add an array of objects on the top level
Row row = grid.Rows.Add(new object[] { 10, 11.12, "some string 1" });

//Add a dictionary as a child object
IDictionary<string, object> dataObject2 = new Dictionary<string, object>();
dataObject2.Add("IntValue", 20);
dataObject2.Add("DoubleValue", 21.33);
row.Add(dataObject2);

//Add an object with variable number of fields as a child of the row
Row childRow = row.Add(new UnboundValueAccessor());
childRow["StringValue"].Value = "some value";

Unbound mode is convenient for creating a grid hierarchy and in situations when data quantity remains unchanged or when data is only added but not removed. At the same time, data is not fully separated from presentation layer as it is necessary to have a reference to grid to add new data or remove it. If data objects implement INotifyPropertyChanged interface, the grid is fully functional event-driven threadsafe control that is controlled by data layer notifications (i.e. automatically updates, highlights, sorts, and regroups rows).

Bound mode

Data contained in IList, IBindingList or IListSource collections. The grid provides Grid.DataSource property for data binding. Bound collections are convenient for adding and removing data and for complete separation of the data layer from the presentation layer. These collections may also contain objects that implement INotifyPropertyChanged interface. Just like in the unbound mode, the grid subscribes to notifications of these objects and therefore becomes an event-driven grid with automated data sorting, filtering and grouping.

The grid provides a lot of options of work with data objects in the bound mode. Let’s first review common work methods that are implemented in any grid (and then let’s consider some specific cases that highlight NetGrid advantages as compared to other grids)

Declarative hierarchical binding

As it has been shown above, Net Grid can work with various data sources including hierarchical ones. At the same time, IBindingList doesn’t contain hierarchy information. After deep analysis of numerous applications we have made a conclusion that data objects may contain hierarchical information by themselves. There are a lot of such examples, i.e. author writing various numbers of articles or books, a department with employees, a financial index (e.g. CAC40 containing 40 largest French enterprises), etc. Therefore, this information may already be contained in an application business layer. For example, author-books relation can be expressed as follows:

Author book relation
class Author
{
    private readonly IList<Book> _books = new List<Book>();
    private readonly string _name;

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

    public IList<Book> Books 
    { 
        get { return _books; } 
    }
    public string Name  
    { 
        get { return _name; } 
    }
}

class Book
{
    private readonly Author _author;
    private readonly string _name;

    public Book(Author author, string name)
    {
        _author = author;
        _name = name;
    }

    public Author Author 
    { 
        get { return _author; }
    }
    public string Title  
    { 
        get { return _name; }
    }
}

A list of authors stored in IBindingList can be bound to the grid.

//Populate binding list with some authors
BindingList<Author> authors = new BindingList<Author>();
Author author = new Author("Agata Kristi");
author.Books.Add(new Book(author, "Second front"));
author.Books.Add(new Book(author, "Shameful Star"));
authors.Add(author);

author = new Author("Conan Doyle");
author.Books.Add(new Book(author, "The Blanched Soldier"));
author.Books.Add(new Book(author, "The Mazarin Stone"));
authors.Add(author);

//Bind grid to author collection
grid.DataSource = authors;

However, hierarchy won’t be created as the grid has no information of author-books hierarchy structure. Dapfor’s framework provides an attribute to inform the grid about it by marking Author.Books as having a special significance for hierarchy building.

class Author
{
    ...

    [HierarchicalField]
    public IList<Book> Books 
    { 
        get { return _books; } 
    }

    ...
}
Declarative binding with HierarchicalField attribute

Now let’s consider a case when an author writes a new book that should be added to the collection.

//Add a book to a collection
author.Books.Add(new Book(author, "His Last Bow"));

Nothing happens, as the book collection is represented by List<Book> data type. If this collection is replaced with BindingList<Book>, the grid will get notifications of any changes in collection and automatically display them. The grid also checks whether sorting, filtering or grouping is required, i.e. the application has sufficiently complex behaviour (grouping, sorting and filtering), while the developer needs to implement only a few simple classes.

class Author
{
    private readonly IList<Book> _books = new BindingList<Book>();

    ...
}
Declarative binding to BindingList<T> collections

Two important notes:

Composite objects

The above example of declarative data binding is not the only one. Continuing the idea of declarative data binding, Dapfor has implemented the concept of composite object. As it has already been mentioned, it is recommended to use objects of arbitrary classes. Properties of these objects return values that are then displayed in corresponding grid cells. However, there are cases when a class object doesn’t have the required property but only refers to another object that contains the required information. There are a lot of such examples – a book written by the author or stock quoted at a certain market. In both cases book or stock objects refer to other objects. However, when these objects are displayed in the grid, it is better to display not just object characteristics but some information taken from referenced objects.

class Author
{
    ...

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

class Book
{
    private readonly Author _author;
    private readonly string _name;

    public Book(Author author, string name)
    {
        _author = author;
        _name = name;
    }

    public Author Author 
    { 
        get { return _author; }
    }
    public string Title  
    { 
        get { return _name; }
    }
}

As shown in the example, the book object provides information of publication date and name and has a reference to the author. If we have a binding list with books of different authors, it would be more convenient to see the author’s name in the grid in addition to book name and publication date.

BindingList<Book> books = new BindingList<Book>();

Author author = new Author("Agata Kristi");
books.Add(new Book(author, "Second front"));
books.Add(new Book(author, "Detective story"));

author = new Author("Conan Doyle");
books.Add(new Book(author, "The Blanched Soldier"));
books.Add(new Book(author, "The Mazarin Stone"));

grid.DataSource = books;

Without declarative binding this can be achieved by adding new fields to book class to return values received from Author object or by creating a new class combining properties of both objects and intended only for displaying in the grid. However, it is not a good solution. On the one hand, the code becomes bulky and contains duplicate information. On the other hand, the book object has properties that it shouldn‘t have. Besides, changing author class signature may cause problems as changes might also impact the book object. Declarative binding offered by Dapfor’s framework makes things different. Marking Book.Author field as a composite field makes the grid process it in a special way by combining Book and Author object fields. When a book is displayed, the grid calls Book.Author property but doesn’t display the Author object in a cell. When a header contains a column with FirstName identifier, the grid first searches Book object for this field and if it is not found, the grid searches the Author object. The business logic remains the same. There are different Author and Book objects, their fields are not duplicated and author data can be accessed by calling book.Author.FirstName. However, the grid displays data of both objects. It is possible to use any data type instead of Author (including data types with variable number of fields).

class Book
{
    ...
    [CompositeField]
    public Author Author 
    { 
        get { return _author; }
    }
    ...
}

If the book and the author have properties with the same names (e.g. Name), Author.Name property can be market with FieldAttribute attribute for displaying purposes.

public class Author
{
    ...

    [Field("AuthorName")]
    public string Name
    {
        get { return _name; }
    }
}

In this case, if the grid has a column with the same identifier, values received from Author.Name will be displayed in the corresponding cell.

grid.Headers.Add(new Header());
grid.Headers[0].Add(new Column("Title"));
grid.Headers[0].Add(new Column("AuthorName", "Author"));
Declarative binding and composite fields