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.

Wpf Grid: flat view

Single header used for all hierarchy levels.

Wpf Grid: hierarchical view, single header

Multiple independent headers.

Wpf Grid: hierarchical view, multiple 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.

Wpf Grid: Header

Header columns can be added in xaml or in C# or VB code.

XAML Copy imageCopy
<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>
C# Copy imageCopy
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).

C# Copy imageCopy
Header header = ...; 
//Enumerate all columns in header 
foreach (Column column in header.Columns) 
{ 
    //Some code here... 
} 

//Enumerate all visible columns in header 
foreach (Column column in header.VisibleColumns) 
{ 
    //Some code here... 
} 


//Enumerate all grouped columns 
foreach (Column column in header.GroupedColumns) 
{ 
    //Some code here... 
} 

//Enumerate all sorted columns 
foreach (Column column in header.SortedColumns) 
{ 
    //Some code here... 
} 

//Find a column by its identifier or position: 
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:

C# Copy imageCopy
Column column = header["MyColumn"]; 
column.VisibleIndex = 5;

Frozen columns

GridControl columns can be easily frozen on any hierarchical levels. On program level it is done via Header.FixedColumns.Count = ‘count of frozen columns’. All grid content to the left will not be scrollable.

Wpf Grid: freezed columns

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.

Wpf Grid: rows

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 shortcutDescription
Left clickFocuses and selects a row
Ctrl + left clickFocuses a row, inverts selection
Shift + left clickFocuses a row, selects of all rows between the previously focused row and the new focused row
EscapeRemoves selection and focus
Ctrl + ASelects of all content
Moving the cursor with left mouse button pressedLasso 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 pressedLasso 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 arrowFocus move. Selects the newly focused row.
PageUp /PageDownSelects and focuses a row that is one page above or below the previously focused row.
Home/EndSelects the first or the last row of the grid.
Shift + Up / Down arrow / PageUp / PageDown / Home / EndFocuses a new row and selects all rows between the last focused row and the new focused row.
Ctrl + Up / Down arrow / PageUp / PageDown / Home / EndMoves the focus without changing selection.
Right arrowMoves to the last child or (if no last child is available) to the next row selecting it.
Left arrowMoves 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.

Wpf Grid: selection and focusing

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.

Property or methodDescription
GridControl..::..RowsCollection of all visible rows in the grid.
GridControl..::..NodesCollection of both visible and invisible rows on the top hierarchy level.
Row..::..ChildrenCollection of both visible and invisible children.
Row..::..VisibleChildrenCollection of visible children on the same hierarchy level.
Row..::..ParentParent row or null if the current row is on the top hierarchy level.
Row..::..VisibleIndexRow position in GridControl..::..Rows
Row..::..ChildIndexRow position in GridControl..::..Nodes or parent Row..::..Children
Row..::..VisibleChildIndexRow position in parent Row..::..VisibleChildren
Row..::..NextVisibleNext visible row in parent Row..::..VisibleChildren container.
Row..::..PrevVisiblePrevious visible row in parent Row..::..VisibleChildren container.
Row..::..FirstVisibleChildFirst visible child.
Row..::..LastVisibleChildLast visible child.
GridControl.Rows.TopVisibleRowThe first row in visible area of the grid.
GridControl.Rows.BottomVisibleRowThe last row that is at least partially visible in the visible area of the grid.
GridControl.Rows.BottomFullyVisibleRowThe last fully visible row in the visible area of the grid.

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 use GridControl.DataObjects.FindFirstRow(Object) method.

Wpf Grid: row location

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.

C# Copy imageCopy
Row row = grid.Rows[10]; 

//Move to the first position
row.ChildIndex = 0; 

//Move to the first visible position (note: some previous rows can be filtered) 
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.

C# Copy imageCopy
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.

C# Copy imageCopy
Cell.Blink(TimeSpan.FromMilliseconds(1000), Brushes.Red);
Wpf GridControl performance

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.

Wpf Grid: sorting

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.

C# Copy imageCopy
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.

C# Copy imageCopy
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.

  • Row..::..Filtered - the easiest method of displaying or hiding data. However, it is doesn't support even-driven data model and therefore it is not useful for more complex applications. With this method the programmer has to control row visibility manually and to consider that data can be sorted or grouped and may have complex hierarchical structure.
  • IFilter interface - the most efficient and convenient filtering method. The programmer has to inherit IFilter interface and to implement IsFiltered method. This method uses row as argument for which the grid determines whether filtering is required.

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.

Editing data template

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 of GridControl 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.