Dapfor .Net Grid Introduction

Part1: Data Types
Part2: Data Binding
Part3: Event-driven model
Part4: Data formatting and presentation
Part5: Threadsafety
Part6: Threadsafe BindingList
Part7: Appearance and painting
Part8: Cell highlighting
Part9: Data filtering
Part10: Data sorting
Part11: Data grouping
Part12: Headers and columns
Part13: Editors
Part14: Performance. Practical recommendations
Part15: Real-time blotter
Part16: Currency converter
Part17: Instrument browser
Part18: Order book
Part19: Basket Viewer

.Net Grid Tutorial Part12: Headers and columns

Dapfor’s developers tried to combine intuitive and easy to use grid interface with ability to create complex and feature-rich  applications. Most efforts were applied to the hierarchy building area. Grid developers decided to move away from traditional hierarchy  model based on bands as it is bulk and inconvenient and has poor performance. Instead, the grid supports work with one or multiple  headers that can instantly transform the grid from treelist control (such as windows explorer) to fully functional grid with multiple independent headers without changing data structure inside the grid.


Every header has two panels – the grouping panel and the column panel The grouping panel contains columns with Column.Grouped property set to true, while the column panel contains only visible columns,
i.e. only columns that have Column.Visible property set to true.

grid-intro 4

Every column has its own identifier that is unique within the header. It stores information of width, sorting direction, text and  image alignment and appearance of column and its cells. Columns are added to the header as follows.

Header header = new Header();
header.Add(new Column("price", "Price label", 100, ContentAlignment.MiddleLeft));
header.Add(new Column("quantity", "Qty", 100));


The following code demonstrates removal of one or all columns:

Header header = grid.Headers[0];
//Remove a column
Column column = header["price"];

//Remove all columns

It is also possible to move columns within header using programming means.

column.VisibleIndex = header.VisibleColumns.Count - 1;

The header can display columns at their actual width. If overall width of visible columns in a header exceeds size of visible grid area, the grid displays a horizontal scrollbar that supports two modes of work. In the first mode (ColumnScrollType.Optimized),when there are multiple headers in the grid, the grid stops scrolling columns in a header irrespective of thumb position, if the last column becomes fully visible. This way, it can display the highest volume of information in columns and cells. In the second mode (ColumnScrollType.Normal), the grid scrolls columns according to thumb position and displays empty space fter the last column.

Header header = grid.Headers[1];
header.ScrollType = ColumnScrollType.Optimized;
Horizontal scroll in optimized mode

The grid supports stretching of visible columns to maximum width of visible area. In this case, there is no need to display a horizontal scrollbar.

grid.Headers[0].StretchMode = ColumnStretchMode.All;
grid.Headers[1].StretchMode = ColumnStretchMode.All;
Stretched columns

The header has multiple collections of columns: Header.VisibleColumns, Header.GroupedColumns, Header.MergedColums.
All these collections provide most common mechanisms for column access and enumeration.

Merged columns

A new feature in version 2.8.0 enables column merging in groups. Columns can be moved and sorted and their size can be changed within a group. However, the grid doesn’t support moving columns outside the group or inserting a column not belonging to the group into the middle of this group. Merged columns cannot be grouped, but otherwise they behave as regular columns.

Header header = grid.Headers[0];
MergedColumn mergedColumn = header.MergedColums.CreateNew("Merged column");

header.ColumnPanelHeight = 36;
mergedColumn.Height = 18;
Merged columns

Disabling column changing via UI

A programmer designing an application may wish to prevent users from changing column positions, size, sorting, grouping, etc. For this purpose, every column has properties that prevent users from manipulating headers.

column.Configurable = false;
column.Movable = false;
column.Resizable = false;
column.Groupable = false;
column.Editable = false;
column.Sortable = false;

Header context menu

Header supports displaying context menus with different sets of ToolStrip items depending on clicking position and on header and column settings. This process is very easy to control. The following code shows how to add new items with event handlers.

grid.HeaderContextMenu.Opening += delegate(object sender, CancelEventArgs e)
ContextMenuStrip contextMenu = (ContextMenuStrip) sender;

contextMenu.Items.Add(new ToolStripSeparator());
contextMenu.Items.Add(new ToolStripMenuItem("My item", null, delegate
//The user has clicked on my item,
//do something here!
Header context menu


Every header and every row in the grid have their hierarchy level starting from 0. When displaying cells, the grid searches for header that corresponds to the same hierarchy level as the row. If there is no such header, a header of the previous level is used.

Header header1 = new Header();
Header header2 = new Header();

Assert.AreEqual(0, header1.Level);
Assert.AreEqual(1, header2.Level);

grid.DataSource = someSource;
Row row = grid.Rows[0];
Assert.AreEqual(0, row.Level);

Row childRow = row.Add(new string[] {"subitem1", "subitem2"});
Assert.AreEqual(1, childRow.Level);

This way of data presentation with a single header often reminds Windows Explorer.

To transform this component to a grid with multiple headers you just need to add one or more headers without changing the data structure. When a new header starts corresponding to a row, the grid displays row cells for a new header in a new way. However, data structure and hierarchy won’t change. In this architecture headers can be changed dynamically without modification of data structures. It is possible to create multiple headers with different number of columns, IDs, sorting and grouping to display different data object fields and to change these headers dynamically in runtime.

//First header
Header header1 = new Header();
header1.Add(new Column("Id1"));
header1["Id1"].Format = new StringFormat("dd-MM-yyyy");
header1.Add(new Column("Id2"));

//Second header
Header header2 = new Header();
header2.Add(new Column("Id1"));
header2.Add(new Column("Id3"));
header2["Id1"].Format = new StringFormat("yyyy-MM-dd");

//Change headers without changing data


Serialization features are also available in runtime. Header class has Header.SerializationState property that returns an object implementing two interfaces: ISerializable and IXmlSerializable. These two interfaces can easily save header state to binary or XML files or streams and restore it from these files.

Header header = grid.Headers[0];
Header.HeaderSerializationState state = header.SerializationState;

using (FileStream fs = new FileStream(@"Test.xml", FileMode.OpenOrCreate, FileAccess.Write))
XmlSerializer serializer = new XmlSerializer(typeof(Header.HeaderSerializationState));
serializer.Serialize(fs, state);

Test.xml file:

<?xml version="1.0"?>
<header ver="2" level="0">
<column ver="2" id="Id1" name="Id1" index="0" visIndex="0" width="60" format="Dapfor.Net.TypeConverters.StringFormatConverter;FormatString=dd-MM-yyyy" flag="0" />
<column ver="2" id="Id2" name="Id2" index="1" visIndex="1" width="60" flag="0" />

Deserialization from Test.xml:

using (FileStream fs = new FileStream(@"Test.xml", FileMode.Open, FileAccess.Read))
XmlSerializer serializer = new XmlSerializer(typeof(Header.HeaderSerializationState));
Header.HeaderSerializationState state = (Header.HeaderSerializationState) serializer.Deserialize(fs);

Header header = new Header();
header.SerializationState = state;
Column column = header["Id1"];
Assert.AreEqual(60, column.Width);

An object returned by Header.SerializationState can be transferred to any other header enabling easy copying of all columns with information on their visibility, position, size, sorting, grouping and appearance.

grid1.Headers[0].SerializationState = grid2.Headers[0].SerializationState;