This topic contains the following sections.
- Unbound data in the grid
- Binding to collections without creating a hierarchy
- Binding to multiple data sources
- Hierarchy binding by mixing unbound data with binding to collections
- Binding Row object to collection
- Declarative hierarchy building
- Declarative field joining
Data binding links a data layer with graphical controls and enables data independence of its presentation. Data binding is broadly used in WinForms applications, while in WPF applications it is virtually the only data presentation method. Correct organization of data binding is the base foundation of well-built applications. Any mistakes during design stage may turn out costly later on. This tutorial describes methods of working with data and data binding organization in Wpf GridControl.
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 Wpf GridControl 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 as single elements. Data can be added on any hierarchy level via GridControl.Rows.Add(Object) and Row..::..Add(Object) methods.
C# | Copy |
---|---|
//An example of a simple class public class MyCustomClass { public MyCustomClass(int intValue, double doubleValue, string stringValue) { IntValue = intValue; DoubleValue = doubleValue; StringValue = stringValue; } public int IntValue { get; set; } public double DoubleValue { get; set; } public string StringValue { get; set; } } |
The following code snippet demonstrates how to declare Wpf GridControl with a single header and some columns in xaml file:
XAML | Copy |
---|---|
<!--Declaration of the GridControl in xaml file--> <df:GridControl Name="grid"> <df:GridControl.Headers> <df:Header ScrollType="Stretch"> <df:Header.Columns> <df:Column Id="IntValue" Title="Int Value" CellHorizontalAlignment="Right" /> <df:Column Id="DoubleValue" Title="Double Value" /> <df:Column Id="StringValue" Title="String Value" CellHorizontalAlignment="Left" /> </df:Header.Columns> </df:Header> </df:GridControl.Headers> </df:GridControl> |
Let's add some hierarchical data to the Wpf Grid:
C# | Copy |
---|---|
private void OnGridControlLoaded(object sender, EventArgs e) { //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")); } |
Wpf GridControl makes it easy to add any number of headers, thus changing the presentation from a treelist with a single header to a multi-header grid.
XAML | Copy |
---|---|
<!--Declaration of the GridControl with multiple headers--> <df:GridControl Name="grid"> <df:GridControl.Headers> <df:Header ScrollType="Stretch"> <df:Header.Columns> <df:Column Id="IntValue" Title="Int Value" CellHorizontalAlignment="Right" /> <df:Column Id="DoubleValue" Title="Double Value" /> <df:Column Id="StringValue" Title="String Value" CellHorizontalAlignment="Left" /> </df:Header.Columns> </df:Header> <df:Header ScrollType="Stretch"> <df:Header.Columns> <df:Column Id="DoubleValue" Title="Double Value" /> <df:Column Id="StringValue" Title="String Value" /> </df:Header.Columns> </df:Header> </df:GridControl.Headers> </df:GridControl> |
In the unbound mode Wpf GridControl supports all data types, described in Wpf Grid tutorial (Part1: Data types). Example:
C# | Copy |
---|---|
private void OnGridControlLoaded(object sender, EventArgs e) { //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> var 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 volume remains unchanged or when data is only added but not removed. At the same time, data is not fully separated from presentation layer as adding or removing data requires a reference to grid. If data objects implement INotifyPropertyChanged interface, the grid is a fully functional event-driven thread-safe control that is controlled by data layer notifications (i.e. it automatically updates, highlights, sorts, and regroups rows).
Binding to collections without creating a hierarchy
The grid provides GridControl..::..ItemsSource property for binding to data contained in IList, IBindingList or IListSource collections. 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 for working with data objects in the bound mode. First of all, we shall review common work methods that are implemented in any grid (and afterwards we shall move to some specific cases that highlight Wpf GridControl advantages as compared to other grids)
It is the easiest method of binding. Data is added to container that implements IList or IBindingList interface. Container choice depends on whether data will be added or removed during application execution. If data volume is constant, it is recommended to use List<your type> container. If data volume may change, it is better to choose BindingList<your type> container. (Note that BindingList<T> has serious performance issues when working with objects that implement INotifyPropertyChanged. Click here for more details). All data types, described in Wpf Grid tutorial (Part1: Data types) can be used as the data type. They may be either arbitrary classes or UnboundValueAccessor supporting variable number of fields.
XAML | Copy |
---|---|
<!--Declaration of the grid--> <df:GridControl Name="grid"> <df:GridControl.Headers> <df:Header ScrollType="Stretch"> <df:Header.Columns> <df:Column Id="IntValue" Title="Int Value" CellHorizontalAlignment="Right" /> <df:Column Id="DoubleValue" Title="Double Value" /> <df:Column Id="StringValue" Title="String Value" CellHorizontalAlignment="Left" /> </df:Header.Columns> </df:Header> </df:GridControl.Headers> </df:GridControl> |
C# | Copy |
---|---|
private void OnGridControlLoaded(object sender, EventArgs e) { //Build and attach data BindingList<MyCustomClass> bindingList = new BindingList<MyCustomClass>(); bindingList.Add(new MyCustomClass(10, 11.12, "some string 1")); bindingList.Add(new MyCustomClass(20, 21.33, "some string 2")); grid.ItemsSource = bindingList; } |
Binding to multiple data sources
In real world applications it is often necessary to connect multiple data sources of different data types to the grid simultaneously. It is not easy to do it with standard Microsoft interfaces. Many grid vendors ignore this problem forcing developers to create inheritance relations between a single base type and multiple classes of different types in a single IBindingList. Dapfor Wpf GridControl easily overcomes this limitation using IListSource interface, so it is sufficient to create a simple implementation of this interface to provide the grid with a collection of data sources, while the grid subscribes to each of the sources and works with them together.
C# | Copy |
---|---|
//Implementation of IListSource interface providing access to two collections class ListSource : IListSource { private readonly IList _sources; public ListSource(IList source1, IList source2) { IList<IList> sources = new List<IList>(); sources.Add(source1); sources.Add(source2); _sources = new ReadOnlyCollection<IList>(sources); } public IList GetList() { return _sources; } public bool ContainsListCollection { get { return true; } } } |
Now let's demonstrate how to use already created ListSource. The xaml code remains unchanged.
C# | Copy |
---|---|
private void OnGridControlLoaded(object sender, EventArgs e) { //Create a collection of custom objects BindingList<MyCustomClass> collection1 = new BindingList<MyCustomClass>(); collection1.Add(new MyCustomClass(11, 11.11, "subitem 1")); collection1.Add(new MyCustomClass(22, 22.22, "subitem 2")); //Create other collection with objects supporting variable number of fields BindingList<UnboundValueAccessor> collection2 = new BindingList<UnboundValueAccessor>(); collection2.Add(new UnboundValueAccessor()); collection2[0]["StringValue"].Value = "dynamic field"; //Bind grid to both collections together grid.ItemsSource = new ListSource(collection1, collection2); //Add an object to the first collection collection1.Add(new MyCustomClass(33, 33.33, "newly added object")); //Add other object to the second collection collection2.Add(new UnboundValueAccessor()); //Update some fields in the second collection collection2[0]["DoubleValue"].Value = 1.234; collection2[1]["IntValue"].Value = 100; collection2[1]["StringValue"].Value = "newly added field"; } |
Hierarchy binding by mixing unbound data with binding to collections
Modern applications require grids that can present data in hierarchical form. Although data binding is a simple concept, it is hard to use it for hierarchy building since IBindingList is not intended for storing hierarchy data. There are multiple approaches to using data binding together with hierarchy. Most of these approaches involve expanding BindingList functionality by mixing data of different levels inside the container. It can involve usage of primary-foreign keys constraints or inheritance of BindingList<T> base class expanding its functionality. However, these solutions work poorly, consume a lot of CPU and memory resources and are hard to debug. One of the reasons for this is that grids of different vendors don’t work with multiple data collections simultaneously. So, what does Dapfor Wpf GridControl offer?
As it has already been mentioned, Dapfor Wpf GridControl supports both bound and unbound data. It provides access to rows via GridControl..::..Rows or GridControl..::..Nodes accessors for any method that has been used to fill the grid. The grid returns objects of Row type. Developers can use Wpf GridControl to easily create a hierarchy by calling Row..::..Add(Object) method that is equivalent to working in the unbound mode.
C# | Copy |
---|---|
//Build and attach data IList<MyCustomClass> bindingList = new List<MyCustomClass>(); bindingList.Add(new MyCustomClass(10, 11.12, "some string 1")); bindingList.Add(new MyCustomClass(20, 21.33, "some string 2")); grid.ItemsSource = bindingList; Row row = grid.Rows[1]; row.Add(new object[] { 200, null,"subitem1"}); row.Add(new object[] { 33, 33.33,"subitem2" }); |
Binding Row object to collection
Every Row object has Row..::..ItemsSource property that can be used to connect any data source on any hierarchical level. The grid will be subscribed to events of this source and process them by performing required synchronization with GUI thread.
C# | Copy |
---|---|
private void OnGridControlLoaded(object sender, EventArgs e) { Row row1 = grid.Rows.Add(new object[] { 200, null, "item 1" }); Row row2 = grid.Rows.Add(new MyCustomClass(33, 33.33, "item 2")); //Create a collection of custom objects BindingList<MyCustomClass> collection = new BindingList<MyCustomClass>(); collection.Add(new MyCustomClass(11, 11.11, "subitem 1")); collection.Add(new MyCustomClass(22, 22.22, "subitem 2")); //Bind row1 to the collection row1.ItemsSource = collection; } |
Declarative hierarchy building
As it has been demonstrated above, Wpf GridControl can work with various data sources including hierarchical. At the same time, IBindingList doesn’t contain hierarchy information. After deep analysis of numerous applications we concluded that data objects may contain hierarchical information by themselves. There are a lot of such examples, i.e. various numbers of articles or books by an author, lists of department employees, financial indexes (e.g. CAC40 containing 40 largest French enterprises), etc. Therefore, this information may already be contained in the application business layer. For example, author-books relation can be expressed as follows:
C# | Copy |
---|---|
//The author class Author { public Author(string name) { Books = new List<Book>(); Name = name; } public IList<Book> Books { get; private set; } public string Name { get; private set; } } //The book class Book { public Book(Author author, string name) { Author = author; Title = name; } public Author Author { get; private set; } public string Title { get; private set; } } |
Let's declare a grid with two headers. The second one will be invisible:
XAML | Copy |
---|---|
<!--Declaration of the grid--> <df:GridControl Name="grid"> <df:GridControl.Headers> <df:Header ScrollType="Stretch"> <df:Header.Columns> <df:Column Id="Name" Title="Name" CellHorizontalAlignment="Left" /> </df:Header.Columns> </df:Header> <df:Header ScrollType="Stretch" Visible="False"> <df:Header.Columns> <df:Column Id="Title" Title="Name" CellHorizontalAlignment="Left" /> </df:Header.Columns> </df:Header> </df:GridControl.Headers> </df:GridControl> |
A list of authors stored in IBindingList can be bound to the grid.
C# | Copy |
---|---|
private void OnGridControlLoaded(object sender, EventArgs e) { //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.ItemsSource = 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 attribute HierarchicalFieldAttribute for hierarchy building.
C# | Copy |
---|---|
class Author { ... [HierarchicalField] public IList<Book> Books { get; private set; } ... } |
Now let’s review a case when an author writes a new book that should be added to the collection.
C# | Copy |
---|---|
//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. whether the application has sufficiently complex behavior (grouping, sorting and filtering), while the developer needs to implement only a few simple classes.
C# | Copy |
---|---|
class Author { public Author(string name) { Books = new BindingList<Book>(); Name = name; } ... } |
The grid supports combinations of any data types. For example, instead of arbitrary class of Book type, it is possible to use Dictionary<string, object> or UnboundValueAccessor with variable number of fields:
C# | Copy |
---|---|
//The author class Author { public Author(string name) { Books = new BindingList<UnboundValueAccessor>(); Name = name; } [HierarchicalField] public IList<UnboundValueAccessor> Books { get; private set; } public string Name { get; private set; } } |
Let's populate the grid with authors:
C# | Copy |
---|---|
private void OnGridControlLoaded(object sender, EventArgs e) { //Populate binding list with some authors BindingList<Author> authors = new BindingList<Author>(); Author author = new Author("Agata Kristi"); authors.Add(author); //Add some books with variable number of fields UnboundValueAccessor book = new UnboundValueAccessor(); book["Title"].Value = "Second front"; book["Genre"].Value = "Detective story"; author.Books.Add(book); book = new UnboundValueAccessor(); book["Title"].Value = "Shameful Star"; book["Genre"].Value = "Detective story"; author.Books.Add(book); author = new Author("Conan Doyle"); authors.Add(author); //Bind grid to author collection grid.ItemsSource = authors; } |
Finally, the data can be added to the grid in multiple ways:
C# | Copy |
---|---|
grid.Rows.Add(...); grid.ItemsSource = ...; |
or at any available hierarchy level, e.g., like this:
C# | Copy |
---|---|
Row row = grid.Rows.Add(new object[] { "Detective stories" }); row.ItemsSource = authors; |
Declarative field joining
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 objects. 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, sometimes 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 some author or a 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.
C# | Copy |
---|---|
class Author { public Author(string name) { Books = new List<Book>(); Name = name; } public IList<Book> Books { get; private set; } public string Name { get; private set; } } class Book { public Book(Author author, string name) { Author = author; Title = name; } public Author Author { get; private set; } public string Title { get; private set; } } |
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.
C# | Copy |
---|---|
private void OnGridControlLoaded(object sender, EventArgs e) { 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.ItemsSource = 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).
C# | Copy |
---|---|
class Book { ... [CompositeField] public Author Author { get; private set; } ... } |
If the book and the author have properties with the same names (e.g. Name), Author.Name property can be marked with FieldAttribute attribute for the purpose of displaying.
C# | Copy |
---|---|
class Author { ... [Field("AuthorName")] public string Name { get; private set; } } |
Let's prepare the grid to display authors with their books:
XAML | Copy |
---|---|
<!--Declaration of the grid--> <df:GridControl Name="grid"> <df:GridControl.Headers> <df:Header ScrollType="Stretch"> <df:Header.Columns> <df:Column Id="Title" Title="Title" CellHorizontalAlignment="Left" /> <df:Column Id="AuthorName" Title="Author" CellHorizontalAlignment="Left" /> </df:Header.Columns> </df:Header> </df:GridControl.Headers> </df:GridControl> |
Back to Wpf Grid tutorials