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
Dapfor .Net Grid
Dapfor .Net Grid is a hierarchical WinForms control with single or multiple headers. The grid can display data of various types from various sources. Data can be displayed with or without hierarchy. Supported .Net frameworks: 2.0, 3.0, 3.5, 4.0
Data presentation without hierarchy.
Single header used for all hierarchy levels.
Multiple independent headers.
Grid Headers
The method of data presentation depends on the number of headers in the grid. Upon display of each row the grid searches for a header for it, and if it doesn't find it, a header from the previous hierarchy level is used. This enables to implement the grid and tree listview in a single component.
Header header1 = new Header();
header1.Add(new Column("Id1", "Column 1"));
header1.Add(new Column("Id2", "Column 2"));
Header header2 = Header.FromDataType(typeof(SomeClass));
grid.Headers.Add(header1);
grid.Headers.Add(header2);
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.
User may modify column width, move columns, modify their visibility and group data by dragging visible columns to group panel. By clicking a column user may turn on data sorting. Holding Ctrl key enables multiple sorting and Shift + left click cancels sorting. All these operations are also available in the context menu that
opens upon right-clicking in header area.
Freezed columns Grid 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.
Grid rows
Grid content is displayed as rows containing cells. Cells in a row are determined by header that belongs to the same hierarchy level as the row, and if there is no header, a header from the previous level is used.
Row selection and focusingThe 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 end user may navigate the grid using Up/Down/Left/Right/PageUp/PageDown/Home/End keys. Focused and selected rows are changed during navigation. If user holds the Shift key during navigation, all rows between the previous focused row and the new focused row become selected. If user holds only Ctrl key, it changes only focused row and selection doesn't changed. Rows can also be selected by left-clicking with Ctrl and Shift keys having the same effect.
Row indexing
The grid provides a lot of ways to define place for storing data and business objects in hierarchical grid structure. Grid rows may be visible or filtered and may connect child rows that may in turn be visible, filtered or collapsed by their parent row. Every row has multiple indices that define its precise position. The grid also provides 2 collections that contain the same rows organized in different ways. Grid.Rows contains only visible rows disregarding their hierarchy level. Grid.Nodes contains all rows of the top hierarchy level disregarding their visibility. The figure below illustrates row indexing in the grid.
If there are multiple filtered rows at the same hierarchy level, Row.ChildIndex and
Row.VisibleChildIndex may differ. Rows can be moved programmatically 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 ofmethods for adding data (to any hierarchy level). Main methods of adding data:
- Binding via a data source Grid.DataSource = ‘data source'. IList, IBindingList, IListSource may be used as data sources. When IListSource is used, data can be added from multiple IBindingList sources.
- Binding in any grid row. Row.DataSource = ‘data source'. IList, IbindingList may also be used as data sources.
- Unbound mode. i.e. adding rows on program level without collections: Grid.Rows.Add(...), Row.Add().
- Conditional binding that raises Grid.RowAdding event when data is added (or via Grid.DataSource/Row.DataSource ???? Grid.Rows.Add(…)/Row.Add()). In conditional binding programmer may replace a business object with any other object and define method of adding it to the grid and relevanthierarchy level.
It is worth mentioning that it is possible to combine these methods of adding data ensuring maximum flexibility.
Data types Data in the grid may be of various types. It may be 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 Grid.Rows.Add()/Row.Add() methods. Objects of arbitrary classes have a special role. Properties of such objects usually correspond to column identifiers and the 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 in 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 when needed 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 Grid.Rows.Add()/Row.Add() 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.
Data highlighting
One of the most convenient grid features is highlighting cells with specified color for a certain period of time. This feature is often used by traders and requires significant time and good understanding of grid operation to implement. Cell highlighting is actually modification of its background color for a certain period of time followed by restoration of this color. Regular implementation involves a timer and storage of timestamps for each modified cell. This requires attention to memory and CPU resource consumption. It is almost impossible to create optimal
highlighting implementation without good understanding of GDI/GDI+ and grid implementation specifics. This causes excessive waste of computer resources and programmer's time.
NetGrid provides a simple API with optimal implementation of this feature.
Programmatically cell highlighting looks as follows:
Row row = grid.Rows[10];
Cell cell = row["Price"];
cell.Highlight(TimeSpan.FromMilliseconds(500), Color.Red);
Cell.Highlight() is called automatically when implemented by INotifyPropertyChanged objects.
If the same data object exists in several points of the grid or is displayed in different grids, each copy of the object will independently highlight the required cells.
Dapfor developers paid special attention to performance and optimal memory consumption when implementing cell highlighting feature. For example, a grid expanded on full screen can process over 50000 data updates per second with simultaneous cell highlighting.
Data sorting in the gridSorting 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 of Column.SortDirection property for relevant header columns. The grid constantly supports sorted content. If grid objects implement INotifyPropertyChanged interface, the grid calls Row.Update() upon receiving a notification and searches for a new row position. If content doesn't implement this interface, the programmer should add code that calls Row.Update() upon 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 of 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 with content 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();
Freezed rows.
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
There are two main methods of data filtering:
- Programming using IFilter interface.
- Using filters in grid columns.
Both methods enable data filtering in real-time mode. A check for the need of filtering of every row is done in the following cases:
- When data is added to the grid.
- When Row.Update() method is called.
- Upon receiving notifications from INotifyPropertyChanged and IBindingList. Row.Update() is called as the result of notifications.
- Upon calling Grid.FilterRefresh() method.
Once again we'd like to stress the convenience of implementing INotifyPropertyChanged interface. Data that changes in real time always conforms to filtering rules set by the programmer. The grid performs all complex
routine work on hierarchical filtering automatically saving programmer's time and letting the programmer to concentrate on implementation of application business logic.
Filters in grid columns are a simple and convenient tool for implementing visual filters. These filters are intuitive for end users and very simple to implement. Visual component is based on UITypeEditor that enables using in filters any user control that returns filter implementing IFilter interface.
Grouping
One of the most important features of the grid is data grouping support. The grid supports single and multiple grouping at any hierarchy level. On program level grouping is performed by setting
Column.Grouped property to true. Sequential setting of this property to true is used to create multiple grouping.
Header header = grid.Headers[0];
header["Status"].Grouped = true;
header["Status"].Visible = false;
User may also group columns by dragging them to the grouping panel. Users may sort and filter data in grouped grid at the same time.
Upon changes of business object value it is sufficient to call Row.Update() to verify that row belongs to a group. If needed, the row can be moved to a new group. The grid automatically checks for empty groups and if such groups are found, they are deleted after removal of the last row.
If data objects implement INotifyPropertyChanged interface, Row.Update() method is called upon receiving a notification from this interface. This means that the grid automatically processes notifications from this interface and frees the programmer from searching for changed rows and routine and complex code used for dynamical regrouping. The same applies to IBindingList interface that notifies the grid of changes in a collection and the grid indirectly invokes Row.Update().
Data editing The grid has convenient features for editing data in cells. The editing process is based on UITypeEditor that is broadly used in PropertyGrid control. This ensures full compliance with standard editors and third-party editors. Standard UITypeEditor provides 3 main methods of data editing:
- Text control over the edited cell.
- Any arbitrary control situated in drop combo box (color picker, DockEditor...).
- Any form displayed modally (e.g. font editor).
In addition to standard editors the grid enables creation of fully customized editors such as TrackBar or RatingControl.
Editors know how to obtain values from data objects and set new values. Therefore, data object in a row doesn't know how data can be edited and which editor can be used for editing.
Drag & Drop The grid significantly simplifies working with drag & drop feature for the programmer. The programmer may implement data relocation, change data hierarchy, move data from one grid to another and export or import data to such applications as Word or Excel. During drag & drop operations the grid highlights the place of data insertion enabling the programmer to specify new data location precisely, including hierachical information.