Introduction

SourceGrid is a Windows Forms control written entirely in C#, my goal is to create a simple but flexible grid to use in all of the cases in which it is necessary to visualize or to change a series of data in a table format.
There are a lot of controls of this type available, but often are expensive, difficult to be customize or not compatible with. NET.
The Microsoft DataGrid for me is too DataSet orientated and therefore results often complicated to use in the cases in which the source data isn't a DataSet and often is not enough customizable.

I want to thank Chirs Beckett, Anthony Berglas, Wayne Tanner, Ernesto Perales, Vadim Katsman, Jeffery Bell, Gmonkey, cmwalolo, Kenedy, zeromus, Darko Damjanovic, John Pierre and a lot of other persons who helped me with code, bugs report and with new ideas and suggestions.

This control is compiled with the Microsoft Framework. NET 1.1 and reference the assembly SourceLibrary.dll 1.2.0.0, this is a small library with common functionality. I introduced this dll in the ZIP file, but is possible to download the entire code and the last binary from the site http://sourcegrid.codeplex.com/. For use SourceGrid is necessary have Visual Studio.NET 2003 or a compatible development environment.

In this article I will want to supply a panoramic on the utilization and on the functionality of the control SourceGrid, for the details on the classes, properties or methods you can consult the documentation in CHM format or the example project in the ZIP file.

Use SourceGrid

In the assembly SourceGrid2.dll are present 2 controls that can be inserted in the Toolbox of Visual Studio and used in any Form:

GridVirtual - A grid of virtual cells (ICellVirtual).

Grid - A grid of real cells (ICell).

There are therefore two fundamental distinctions to do: virtual cells and real cells. Virtual cells are the cells that determine the appearance and the behavior of the cell but don't contain the value, the real cells have the same properties of the virtual cells but contain also the value of the cell, they are therefore associated to a specific position of the grid.

Every cells are composed by three fundamental parts:

DataModel : The DataModel is the class that manages the value of the cells. Converts the value of the cell in a string for visual representation, create the editor of the cell and validate the inserted values.

VisualModel : The VisualModel is the class that draws the cell and contains the visual properties.

BehaviorModel : The BehaviorModel is the class that supplies the behavior of the cell.

This subdivision grants a great flexibility and reusability of code, save time and supplies a solid base for every type of customizations.
For the more common cases there are some classes already arranged and configured, but with little lines of code is possible to create personalized cells (see the next paragraphs for the details).

Grid

The Grid control is the ideal if you want the greatest flexibility and simplicity but with not many cells. In fact in this control every cells are represented by a .NET class and therefore occupies a specific quantity of resources. Moreover this is the only grid that supports features of RowSpan and ColumnSpan.

After to have inserted the control in the form we can begin to write our code to use the grid in the Load event of the form like this:

The previous code creates a table with 2 lines and 2 columns (Redim method) and populates every positions with a cell. I have used the SourceGrid2.Cells.Real namespace where are present all the real cells.

Every cells contains all the necessary display properties, for example to change the background color of the cell we can write:

In the previous code I have set the grid border, the number of columns, the number of fixed rows and created the first header row. For the header I have used a ColumnHeader cell. With a simple cycle for I have then created the other cells using for each column a specific type. The Cell class creates automatically an appropriate editor for the type specified (in this case a TextBox and a DateTimePicker). For the last column I have used a CheckBox cell that allows the display of a checkbox directly on the cell.
The form should look equal to the one in the following figure, this example is present also in the project SampleProject with the ZIP file.

GridVirtual

The GridVirtual control is the ideal when is necessary to visualize a lot of cells and you have already available a structure data like a DataSet, an Array, a document XML or other data structure.
This type of grid have the same features of the Grid control except for the automatic sort (this because the grid cannot order automatically any external data structure without copying its content) and the feature of RowSpan and ColumnSpan that allow to span a cell across other adjacent cells.
Another disadvantage is that to create a virtual grid is a little difficult.

The main concept in a virtual grid is that the cells do not contain the values, but read and write the value in an external data structure. This idea was implemented with an abstract class CellVirtual in which is necessary to redefine the methods GetValue and SetValue.
To use the GridVirtual is therefore necessary to create a class that derives from CellVirtual and to personalize the reading using the data source chosen.
Usual is better to create also a control that derive from GridVirtual, to have a greater flexibility and a more solid code, overriding the method GetCell.
If you prefer you can directly use the GridVirtual control and the event GettingCell. The purpose of the method GetCell and of the event GettingCell is to return, for a given position (row and column), the chosen cell. This allows a large flexibility because you can return for a specific type any ICellVirtual, for example you could return a cell of type header when the row is 0.

In the following example I create a virtual grid that reads and writes the values in an array. First I have inserted the control GridVirtual in a form, then I write this code that defines our virtual class:

With the previous code I have created a virtual cell with an editor of type string that reads and writes the values in an array specified in the constructor.
After the call to the SetValue method we should call the OnValueChanged method to notify the grid to update this cell.

I have added an event handler to GettingCell event, created the grid and the array with 1000 rows and 1000 columns, then I have created a new instance of the previous cell and with the method BindToGrid I have linked the cell to the grid.
I have created a single cell that will be used for every position of the matrix. Is always necessary to call the method BindToGrid on the cells that we want to use in a virtual grid.

In order to finish we should write the method GettingCell and declare the variable for the cell:

The result should look equal to the one in the following picture, this example is present also in the project SampleProject included in the ZIP file.

VisualModel

Namespace: SourceGrid2.VisualModels

Every cell have a property VisualModel that returns an interface of type IVisualModel. The cell uses this interface to draw and to customize the visual properties of the cell.

The purpose of the VisualModel is to separate the drawing code from the rest of the code and allows to sharing the same visual model between more cells. In fact the same instance of VisualModel can be used on more cells simultaneously optimizing the use of the resources of the system.
The default VisualModel classes are read-only, however each VisualModel is provided with a Clone method that allows you to create identical instances of the same model.

These are the default VisualModel classes in the namespace SourceGrid2.VisualModels:

SourceGrid2.VisualModels.Common - Used for classic cells. In this model you can customize the colors, the font, the borders and a lot other properties.

SourceGrid2.VisualModels.CheckBox* - Used for checkbox style cells. The checkbox can be selected, disabled and can contains a caption.

SourceGrid2.VisualModels.Header* - Used for header style cells with 3D borders. You can configure the borders to gradually vanish from the color of the border to the color of the cell for a better three-dimensional effect.

SourceGrid2.VisualModels.MultiImages - Allows to drawing more then one image in the cell.

*The VisualModel marked with an asterisk require a special interface to work correctly, for example the CheckBox model needs a cell that supports the ICellCheckBox interface.

Each of these classes contains one or more static properties with some default read-only instances easily useable:

SourceGrid2.VisualModels.Common.Default

SourceGrid2.VisualModels.Common.LinkStyle

SourceGrid2.VisualModels.CheckBox.Default

SourceGrid2.VisualModels.CheckBox.MiddleLeftAlign

SourceGrid2.VisualModels.Header.Default

SourceGrid2.VisualModels.Header.ColumnHeader

SourceGrid2.VisualModels.Header.RowHeader

This code shows how to assign the same VisualModel to more cells previously created and then change some properties:

Consider also that when you write Cell.BackColor the property calls automatically the BackColor property of the VisualModel associated. To facilitate the utilization of the more common properties if you write Cell.BackColor = Color.Black; the cell automatically clone the current VisualModel , changes the backcolor to the cloned instance and assigns the cloned instance again to the cell.

DataModel

Namespace: SourceGrid2.DataModels

To represent the value of a cell in a string format and to supply a cell data editor, is necessary to populate the property DataModel of the cell. If this property is null is not possible to change the value of the cell and the string conversion will be a simple ToString of the value.

Usual a DataModel use a TypeConverter of the type asked to manage the necessary conversion, particularly the string conversion (used to represent the cell value).

These are the default DataModel classes in the namespace SourceGrid2.DataModels:

DataModelBase - Supplies the methods of conversion and allows the alteration of the cell value only by code, does not supply graphic interface. This class is the base of all the other editors and is used also to manage read-only cells but with customized formattings or special editors (for example the CheckBox cell use a read-only editor because the value is changed clicking directly on the checkbox).

EditorControlBase - Abstract Class that help to use a control as editor for the cell.

EditorTextBox - A TextBox editor. This is one of the more used editor by all types that support string conversion (string, int, double, enum,....)

EditorComboBox - A ComboBox editor.

EditorDateTime - A DateTimePicker editor.

EditorNumericUpDown - A NumericUpDown editor.

EditorTextBoxButton - A TextBox editor with a button to open a details mask.

EditorUITypeEditor - Supplies the editing of the cell of all types that have an UITypeEditor. A lot of types support this interface: DateTime, Font, a lot of enum, ...

A DataModel can be shared between more cells, for example you can use the same DataModel for every cell of a column.

To create an editable cell there are 2 possibilities:

Create the cell specifying the value type. In this manner the cell calls automatically an utility function, Utility.CreateDataModel, that returns a DataModel in base to the type specified.

This method is recommended when you want to use the same editor for more of cells.

If you need a greater control on the type of editor or there are special requirements is possible to create manually the editor class.
In this case for example I create manually the class EditorTextBox and then I call the property MaxLength and CharacterCasing.

Some properties are defined to a DataModel level, while other to an editor control level, in this case the property CharacterCasing is defined to a TextBox control level. To use these properties is necessary therefore to force a linking of the editor to the grid with the method AttachEditorControl and then call the method GetEditorTextBox to returns the instance of the TextBox.
This mechanism is also useful for create special editor like the ComboBox editor. To insert a ComboBox you must write this code:

Of course is possible to create custom DataModel editor with custom control or special behaviors.
In the following picture it is possible to observe most of the editors available and some options like image properties:

BehaviorModel

Namespace: SourceGrid2.BehaviorModels

Every cell have a collection of BehaviorModel that you can read with the Behaviors property. A BehaviorModel is a class that characterizes the behavior of the cell. A model can be shared between more cells and allows a great flexibility and simplicity of any new feature.

These are the default classes of type BehaviorModel:

SourceGrid2.BehaviorModels.Common - Common behavior of a cell.

SourceGrid2.BehaviorModels.Header - Behavior of a header.

SourceGrid2.BehaviorModels.RowHeader - Behavior of a row header, with resize feature.

Cells

Namespace: SourceGrid2.Cells

These are the default cells available:

SourceGrid2.Cells.Virtual - This namespace contains all the virtual cells that can be used with a GridVirtual control, these are all abstract cells and you must derive from these cells to use your custom data source.

CellVirtual - Base cell for each other implementation, use for the most common type of virtual cells.

Header - A header cell.

ColumnHeader - A column header cell.

RowHeader - A row header cell.

Button - A button cell.

CheckBox - A checkbox cell.

ComboBox - A combobox cell.

Link - A link style cell.

SourceGrid2.Cells.Real - This namespace contains all the real cells that can be used with a Grid control.

Cell - Base cell for each other implementation, use for the most common type of real cells.

Header - A header cell.

ColumnHeader - A column header cell.

RowHeader - A row header cell.

Button - A button cell.

CheckBox - A checkbox cell.

ComboBox - A combobox cell.

Link - A link style cell.

The goal of these classes is to simplify the use of VisualModel, DataModel and BehaviorModel. If we look at the code of any of these classes we can see that these classes use the previous models according to the role of the cell. There are however models that require special interfaces and in this case the cells implement all the required interfaces.
This is for example the code of the cell SourceGrid2.Cells.Real.CheckBox:

As you can see the CheckBox class simply use the models SourceGrid2.DataModels.DataModelBase(typeof(bool)), SourceGrid2.VisualModels.CheckBox.MiddleLeftAlign e BehaviorModels.CheckBox.Default implements the ICellCheckBox interface with its method GetCheckBoxStatus.
The methods Checked, Caption, GetCheckedValue and SetCheckedValue are methods to simplify the editing of the value of the cell.

Structure of the Grid

Rows and Columns

The main components of a grid are the rows and the columns. To manipulate these informations SourceGrid supplies 2 properties:

Rows - Returns a collection of type RowInfoCollection that is a strip of classes RowInfo.

Columns - Returns a collection of type ColumnInfoCollection that is a list of classes ColumnInfo.

These are some of the RowInfo class properties: Height, Top, Bottom, Index, Tag. These are instead the properties of the ColumnInfo class:Width, Left, Right, Index, Tag.

These three examples perform all the same task of creating a table with 2 rows and 2 columns.

To change the width or the height of a row or a column you can use this code:

grid1.Rows[0].Height = 100;
grid1.Columns[0].Width = 100;

The properties Top, Bottom, Left and Right are automatically calculated using the width and the height of the rows and columns.

Panels

To manage correctly scrollbars, columns and rows fixed and a lot other details, the grid inside has a panels structure like this:

1) TopLeftPanel - Keeps fixed row and fixed column cells.

2) TopPanel - Keeps fixed rows.

3) LeftPanel - Keeps fixed columns.

4) ScrollablePanel - Keeps all not fixed cells.

5) HScrollBar - Horizontal ScrollBar

6) VScrollBar - Vertical ScrollBar.

7) BottomRightPanel - Panel to manage the small space between the two scrollbars.

Events

The mouse and keyboard events can be used with a BehaviorModel or can be connected directly to the grid.
All the events are first fired to the panels and then automatically moved to GridVirtual and Grid control. To use these events you can write this code:

This can be done also with the Visual Studio designer.
Look at the example 8 in the project SampleProject for details.

ContextMenu

The grid has a default ContextMenu that can be customized with the ContextMenuStyle property. It is possible to connect a ContextMenu to the Selection object with the Grid.Selection.ContextMenuItems, that will be used for all selected cells or otherwise you can connect a ContextMenu directly to a specific cell.
Look at the example 10 in the project SampleProject for further details.

Other Informations

Focus and Selection

A cell can be selected of can have the focus. Only one cell can have the focus, identified by the FocusCellPosition property of the grid, instead many cells can be selected. A cell is selected when is present in the Selection object of the grid.
The cell with the focus receives all of the mouse and keyboard events, while the selected cells can receive actions like the copy/paste.

Position and Range

Two of the most used objects in the project SourceGrid are the struct Position and Range. The struct Position identifies a position with a Row and a Column, while the struct Range identifies a group of cells delimited from a start Position and an end Position.

Performance

To optimize performance of this control use the GridVirtual control when is necessary to visualize a lot of cells and try always to share the models (DataModel, VisualModel, BehaviorModel) between more possible cells.
The performance of the grid is quite good even if the drawing code can be still optimized, especially when scrolling.
It is possible to consult the project SampleProject for further information on the performance of the grid.

Extensions

In the project SampleProject are present a lot of examples and parts of code that can give ideas or suggestions of how implements custom grid, particularly in the folder Extensions are present some grids that supply functionality like the binding to a DataSet(DataTable), to an Array and to an ArrayList.

Future developments

Known problems

The Shift key does not work with the arrows keys and there are still some problems with header cells.

Previous versions

Version 2 of SourceGrid introduce many changes, and is not possible to list the all. The manner of utilization is very similar, but is not simple to convert a code written with previous versions.
These are some suggestions:

The major features of the grid work with the ICellVirtual interface, no more with Cell class. This interface contains only necessary methods and therefore is poorer. For this the code that before was:

In the prior version the base cell was identified from the Cell class while now is the interface ICellVirtual and the major change of this class is that does not contain informations about the position (row and column).

A lot of the methods of the grid that before used the Cell type now use the Position type, is however possible to extract the interface ICellVirtual (and then cast to more specific interfaces) given a struct Position with the method Grid.GetCell

Now the grid support natively the property BorderStyle that is able therefore to eliminate the eventual Panel that before was necessary to introduce a border.

All of the code that first was bound to the events of a cell now must be moved in a BehaviorModel, you can use for example the SourceGrid2.BehaviorModels.CustomEvents.

The Selection object is no more a collection of cells but a collection of Range.

With the insertion of the Rows and Columns objects the code that first should work on lines and columns now results simpler, besides a lot of methods that before were in the grid now are in the RowInfoCollection, RowInfo, ColumnInfoCollection or ColumnInfo classes.

The object CellsContainer is no more present, and even if logically was replaced from the panels, the more commons are linked directly to the grid and therefore the code that before used CellsContainer now could use directly the Grid control.

The old object ICellModel now is the object IDataModel, while the object VisualProperties now became IVisualModel.

The class CellControl for now is no more supported, I will think in future if introduce it again.

History

2.0.3.0 (25 March 2004)

Moved and reviewed to the SourceLibrary project controls ComboBoxEx and TextBoxButton.

2.0.2.1 (24 March 2004)

Fixed a bug on Range class. When adding or removing rows ColumnSpan and RowsSpan informations were not preserved.

2.0.2.0 (23 March 2004)

Fixed a bug on ColumnHeader sort when used without FixedRows

2.0.1.0 (23 March 2004)

Divided interface IDataModel and partially moved to SourceLibrary.ComponentModel.Validator.

Renamed methods StringToObject to StringToValue and ObjectToString to ValueToString.

Now to prevent editing the textbox of a ComboBox editor now you must use property AllowStringConversion = false. StandardValuesExclusive property allows to insert only the values present in the StandardValueList.

2.0.0.0 (15 March 2004)

License (BSD-style)

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.

Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.

Neither the name of the ORGANIZATION nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

License

This article, along with any associated source code and files, is licensed under The MIT License