Dapfor GridControl tutorial
Wpf GridControl is a control that provides data in tabular form and has one or several headers. Supported frameworks: 4.0, 4.5. Development tools: VS2010, VS2012, VS2013.
All grids use binding to data collections of (IList, IBindingList, IListSource) type as the main and often as 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.
This topic contains the following sections.
Work modes
The grid can display both hierarchical and non-hierarchical data. When the grid is used to display hierarchical content, data can be presented with a single header controlling all hierarchy levels or with multiple headers. Transition from one presentation to another requires only addition of the new header to header collection. Meanwhile, the content remains unchanged.
Data presentation without hierarchy.
Single header used for all hierarchy levels.
Multiple independent headers.
Headers and columns
Every header consists of two panels: the column panel and the grouping panel. The column panel displays all visible columns, each of which defines width of data displayed in cells.
Header columns can be added in xaml or in C# or VB code.
<df:Header>
<df:Header.Columns>
<df:Column Id="Id0" Title="Column 0"/>
<df:Column Id="Id1" Title="Column 1"/>
<df:Column Id="Id2" Title="Column 2"/>
</df:Header.Columns>
</df:Header>
Header header = new Header();
header.Add(new Column("Id0", "Column 0"));
header.Add(new Column("Id1", "Column 1"));
header.Add(new Column("Id2", "Column 2"));
The header provides several convenient accessories to control and manage sequence, column visibility and data grouping. The following code demonstrates a way of receiving all header columns (both visible and invisible).
Header header = ...;
foreach (Column column in header.Columns)
{
}
foreach (Column column in header.VisibleColumns)
{
}
foreach (Column column in header.GroupedColumns)
{
}
foreach (Column column in header.SortedColumns)
{
}
Column column1 = header["MyColumn"];
Column column2 = header[5];
The column provides the following properties to establish column position in the header: Column.Index, Column.VisibleIndex. The column may also be moved to any position in header using programming means:
Column column = header["MyColumn"];
column.VisibleIndex = 5;
Freezed columns
GridControl columns can be easily freezed on any hierarchical levels. On program level it is done via Header.FixedColumns.Count = ‘count of freezed columns’. All grid content to the left will not be scrollable.
GridControl rows
GridControl content is displayed as rows containing cells. Row cells are defined by header that belongs to the same hierarchy level as the row. If there is no such header, a header from the previous level is used.
Row selection and focusing
The grid may contain multiple selected rows and one focused row. It is necessary to distinguish these two concepts. A row can be selected and focused at the same time, but selection is row property and focus is grid property. The grid provides standard user-friendly tools to control selection and focus. Rows are selected by left-clicking grid cells, area above row selector or location above the area to the right of the last visible column. Clicking a row selects it and puts it in focus. Clicking a row with different keys pressed may have various effects. The most frequent key shortcuts for the grid are listed below.
Key shortcut | Description |
Left click | Focuses and selects a row |
Ctrl + left click | Focuses a row, inverts selection |
Shift + left click | Focuses a row, selects of all rows between the previously focused row and the new focused row |
Escape | Removes selection and focus |
Ctrl + A | Selects of all content |
Moving the cursor with left mouse button pressed | Lasso selection – selection of content in a rectangular area determined by the starting point of lasso operation and the current cursor position. |
Shift + Moving the cursor with left mouse button pressed | Lasso selection – selection of content in a rectangular area determined by the starting point of lasso operation and the current cursor position. Previous selection is preserved. |
Up/Down arrow | Focus move. Selects the newly focused row. |
PageUp /PageDown | Selects and focuses a row that is one page above or below the previously focused row. |
Home/End | Selects the first or the last row of the grid. |
Shift + Up / Down arrow / PageUp / PageDown / Home / End | Focuses a new row and selects all rows between the last focused row and the new focused row. |
Ctrl + Up / Down arrow / PageUp / PageDown / Home / End | Moves the focus without changing selection. |
Right arrow | Moves to the last child or (if no last child is available) to the next row selecting it. |
Left arrow | Moves to the first child or (if no first child is available) to the previous row selecting it. |
An additional lasso selection feature has been implemented to make the selection method even more user-friendly. To initiate this selection mode you have to move the cursor over rows in row selector area or over an empty area beyond the last visible row of the grid and click the left mouse button. When the cursor is moved with left mouse button pressed, a rectangular area appears. All rows in this area become selected. Lasso selection works only when selection of multiple rows is enabled.
Row and cell location
The grid has two row indexing system. GridControl.Rows performs linear indexing of visible rows in a grid without regard to hierarchy. It means that this collection contains all visible (not filtered and non-collapsed by parent) rows. Row.VisibleIndex property specifies position of a visible row in GridControl.Rows. The second time of indexing is related to hierarchical structure of the grid. Rows at the first hierarchy level can be accessed with GridControl.Nodes property. This collection contains both visible and invisible rows that are located on the same hierarchy level. Every row may contain child rows that can be accessed via Row.Children property. Neither filtering, nor visibility affect row presence in this collection. Row.ChildIndex property defines hierarchical position of a row. If the row is on the top level, it is in GridControl.Nodes, otherwise it is in parent Row.Children container. The row also has Row.VisibleChildren container that contains only visible children on the same hierarchy level. You can use Row.VisibleChildIndex property to determine Row position inRow.VisibleChildren of the parent.
One row can be present in different containers, including GridControl.Rows, GridControl.Nodes, Row.Children, etc. Containers organize grid structure and enable precise definition of data location. For hierarchies the grid provides a convenient API that can be used to determine parent, children and adjacent visible rows. The list of most frequent methods and properties used for data indexing is provided below.
If you work with reference-type data objects, you can use convenient grid features to search for row or rows containing the required data object.
You can get a data object that is associated with a row by calling Row.DataObject property. The same data object may be contained in multiple grid rows. You can get them using GridControl.DataObjects.Find(Object) method. If you need only the first row, you can useGridControl.DataObjects.FindFirstRow(Object) method.
If there are multiple filtered rows at the same hierarchy level, Row.ChildIndex and Row.VisibleChildIndex may differ. Rows can be moved with programming means by assigning new row index. Row.VisibleIndex doesn’t specify row location unambiguously because it doesn’t consider hierarchy. Therefore, it is read-only.
Row row = grid.Rows[10];
row.ChildIndex = 0;
row.VisibleChildIndex = 0;
Data in the grid
The grid works with different types of data and provides a lot of methods for adding data (to any hierarchy level). Main methods of adding data:
Data types
Data in the grid may be of various types, including objects of arbitrary classes, object[]/string[] or objects implementing IList, IDictionary and other interfaces. These objects may be collected in IBindingList or added to the grid with GridControl.Rows.Add(Object) /Row.Add(Object)) methods. Objects of arbitrary classes have a special role. Properties of such objects usually correspond to column identifiers and objects by themselves are contained in grid rows. If desired, it is possible to modify property identifiers using attributes. It is worth mentioning that the same objects may be located in multiple grid rows at the same time.
public class Instrument
{
[Field("InstrumentId")]
public string Isin { get { return _isin; } }
}
Header header = new Header();
header.Add(new Column("InstrumentId", "Instrument"));
grid.Headers.Add(header);
grid.Rows.Add(new Instrument());
INotifyPropertyChanged interface
The grid supports objects that implement INotifyPropertyChanged interface. Such objects may notify the grid of changes in object property values. The grid subscribes to events, performs multi-threaded synchronization on demand and automatically sorts, filters and regroups rows that contain business objects that have notified the grid. These objects may be stored in IBindingList or added via GridControl.Rows.Add(Object) / Row.Add(Object) methods. Disregarding the method of adding data to the grid, it subscribes to objects that implement this interface and performs automated sorting, filtering etc. It is recommended to implement INotifyPropertyChanged for implementing application business logic. This approach enables significant reduction of code volume, making the code simpler and implementing modern programming patterns (such as MVVM) to separate business logic from its presentation.
Cell Blinking
Blinking of rapidly changing data in cells is an important element of modern applications. Implementation usually requires creation of timers, tracking blinking end time to restore the original background and creation of a DataTemplate with necessary triggers. All these tasks require a lot of work and high programming skills. Besides, implementation based on usage of DataTemplate cannot have high performance will consume a lot of CPU and memory resources.
GridControl provides a simple and convenient interface for blinking a cell for the defined period of time with selected color. To use it, one only has to transmit the required parameters to Cell.Blink(TimeSpan, Brush) . The grid automatically creates timers, tracks their expiration and manages the blinking process. An example of blinking a grid cell in red for 1 second is shown below.
Cell.Blink(TimeSpan.FromMilliseconds(1000), Brushes.Red);
For more information about cell blinking see.
Data sorting in the grid
Sorting information is stored in grid headers. The grid supports multiple sorting. It is possible to use up to 5 sorting levels for each header. On program level sorting is enabled by consequential setting ofColumn.SortDirection property for relevant header columns. The grid always supports content sorting. If grid objects implement INotifyPropertyChanged interface, the grid calls Row.Update() on receiving a notification and searches for a new row position. If content doesn’t implement this interface, the programmer should add code that callsRow.Update() on data modification.
Storage of sorted data reduces code volume (the programmer doesn’t have to worry about content as the grid systematically sorts it) and greatly improves application performance. For example, sorting N elements using classical approach requires N*ln(N)operations, while the grid uses only ln(N) operations since it moves only one unsorted row in a sorted container. This feature of the grid enables it to perform over 5000 sorting operations per second while containing over 1000 business objects.
The grid also enables customized sorting with ICustomSort interface.
class MyCystomSort : ICustomSort
{
public int Compare(Row row1, Row row2, string fieldId, object value1, object value2, int defaultResult)
{
string v1 = value1.ToString();
string v2 = value2.ToString();
if (v1 == "some value") return -1;
if (v2 == "some value") return 1;
return defaultResult;
}
}
grid.CustomSort = new MyCystomSort();
Read more about data sorting.
Row freezing
In addition to regular sorting the grid supports docking rows at any hierarchical levels enabling these rows to ignore sorting. This feature is most frequently used to display summaries or average values of data in columns.
Row row = grid.Rows.Add(new SomeObject());
row.Dock = RowDockStyle.Top;
Data filtering
Data filtering actually controls visibility of individual grid rows basing on rules set by the programmer. These rules should be convenient and easy to use, should not depend on data sorting or grouping and should be equally applied to static and real-time data. Data filtering should not depend on hierarchy level or on data being expanded or collapsed by parent rows.
The grid provides two complementary methods of data filtering.
Read more about data filtering.
Data grouping
Grouping means combining rows in groups by values. During grouping one or multiple special rows with Row.IsGroup flag are created. They combine regular rows with equal values of the grouping field, which is set by the column.
The programming interface is very simple and clear. Grouping information is set in the header. For this purpose it is sufficient to set Column.Grouped flag to true for the required column. Column.Visible = false property can be used to hide a column on the panel of visible columns.
The grid supports grouping by multiple columns. This requires sequential setting of Column.Grouped flag for required columns. To determine columns used for grouping, Header class provides Header.GroupedColumns property that can be used to get all information of columns used for grouping. Header.GroupedColumnCollection.Clear() method can be used to cancel grouping, while Column.GroupIndex = required_index can be called to change sequence of columns used for grouping.
Read more about data grouping.
Data templates
Data templates provide a convenient way of using arbitrary controls in grid cells. GridControl fully supports this architecture enabling data template setting for columns and individual cells. Templates are set in xaml code and can be easily replaced with other templates without impact on application business logic functionality.
Read more about data templates.
Performance
Any developer faces the performance issue at one time or another. This often happens on final development stage, when it is either impossible to make significant changes of application architecture or such changes involve high risk and labor costs. Developers ofGridControl tried to focus on performance issues as much as possible. All grid architecture is perfectly optimized to ensure minimum consumption of CPU and memory resources.
GridControl characteristics were measured in different working modes and conditions during development. To make the picture more clear, all these characteristics were summed up in charts showing how performance depends on data volume..
Thread safety
While developing the grid we paid attention to application thread safety. Hardware developers often face limitation of possible performance improvement by increasing processor frequency and solve this problem by threading workloads and increasing the number of processor cores. Today many processors have 4-6 cores, and tomorrow they will have 16, 32 or even more cores. Single-threaded software that uses only one core can’t provide high performance by definition. However, threading makes application development harder and may easily cause application crashes or deadlocks.
Writing an application that correctly supports multi-threading is a very complicated task. Errors in some parts of code may dramatically impact the application as whole. It is also very important to understand thread safety implementation, especially in such complex components as Wpf GridControl.
Most Wpf GridControl methods that work with data are thread safe, i.e. such methods asGridControl.Rows.Add(Object) / Row.VisibleIndex etc can be called from any thread. However, it is not the most important grid feature in multi-threaded applications. The most important new feature is thread-safe handling of notifications coming from IBindingList and INotifyPropertyChanged interfaces. These interfaces are very important for data binding and enable separation of application logic from its presentation. In other words, logic shouldn’t know how it is presented GUI. There are various popular patterns of application implementation such as MVC or MVVM, but neither of these patterns considers that logic may operate in non-GUI thread. All data binding methods send business logic notifications to data presentation layer in business logic thread.
Read more about thread safety.
Wpf GridControl features