A hierarchical event-driven .Net control with ultimate performance.

Namespace: Dapfor.Net.Ui
Assembly: Dapfor.Net (in Dapfor.Net.dll) Version: 2.10.3.24917 (2.10.3.24917)

Syntax

C#
public class Grid : Control, ISupportInitialize
Visual Basic
Public Class Grid
	Inherits Control
	Implements ISupportInitialize
Visual C++
public ref class Grid : public Control, 
	ISupportInitialize
F#
type Grid =  
    class
        inherit Control
        interface ISupportInitialize
    end

Remarks

Originally the grid was designed for electronic financial markets with their stringent requirements to robustness, low consumption of memory and CPU resources, ergonomic and simple user interface and rich API. These requirements have a common objective - to gain the first place on the market, to improve development time and to keep productive work in the run-time. As the result, we create the most productive and robust hierarchical grid with one of the best object models. It can be used in a great variety of applications (not just for financial markets). It makes software user-friendly, reduces CPU and memory consumption and considerably accelerates development time with multiple services included in the grid and RAD (rapid application development) patterns.


Working modes
.Net Grid supports various working modes that that enable its use in various types of applications

  • Non-event model supporting insertion of data of different types (user defined classes, IEnumerable<T>, IList<T>, IDictionary<string, object> etc.) into any level of grid hierarchy. The grid provides Grid.Rows.Add(object) and Row.Add(object) methods to work in this mode.
  • Event-driven mode is used when a programmer uses objects that implement INotifyPropertyChanged interface. We recommend to get acquainted with this mode and use it everywhere. It will enable you to eliminate all dependencies between business logic layer and the System.Windows.Forms, as well as Dapfor assemblies.
  • Data binding mode is used to connect to data sources via DataSource/DataMember properties. Supported data sources: IList, IListSource, IBindingList.


Data types
Such broad functionality of the .Net Grid is possible due to the IDataAccessor interface that is one of the most crucial part of the grid. The main purpose of this interface is to normalize presentation of different data types in the grid. There are lots of implementations of IDataAccessor interface. This way, a programmer can add his own implementation to broaden the list of data types that can be used by the .Net Grid. When you call Add(Object), an implementation of the IDataAccessor interface is created implicitly for 'your object' and the .Net Grid works with it only via the IDataAccessor proxy, making no difference between the data types.

 Copy imageCopy
//Some data object
public class Product : INotifyPropertyChanged
{
    //Some fields
    private double price;
    private DateTime maturity;

    [DoubleFormat(Precision = 3, ShortForm = true, ShowZero = false)]
    public double Price
    {
        get { return price; }
        set
        {
            price = value;
            if(PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs("Price"));
            }
        }
    }
    public DateTime Maturity
    {
        get { return maturity; }
    }
    public event PropertyChangedEventHandler PropertyChanged;
}

//Using sample
public void AddDataObjectToGrid(Grid grid)
{
    //Initialize the grid
    grid.Headers.Add(new Header());
    grid.Headers[0].Add(new Column("Price"));
    grid.Headers[0].Add(new Column("Maturity"));

    //Add data object to the grid. The object will be implicitly wrapped by the DataObjectAccessor class
    Product product = new Product();
    grid.Rows.Add(product);

    //Add a collection of values to the grid.
    //Because of this collection implements IList, it will be implicitly wrapped by the ListDataAccessor
    grid.Rows.Add(new double[] {123, 12, 45});

    //The object will notify the grid through the INotifyPropertyChanged, and the grid will automatically
    //invalidate, sort, filter and highlight the affected cells.
    product.Price = 12.34;
}


Hierarchy

.Net Grid allows to present data both in tabular format and in hierarchical form. A data hierarchy may have one or more headers. Working with a single header is similar to TreeView control with multiple columns (a classic example is file explorer). Multiple headers enable separate column management for each hierarchical level. At a glance, every header consists of two panels: the grouping panel and the columns panel. These panels aren't related to each other. It means that the same column may be present in both panels simultaneously. Each column carries very important information about width, location, visibility, grouping and data sorting

Data can be added into the data grid via Add(Object) method call. This method returns object of Row type, which contains information about a data object, its location, hierarchy in the data grid, color, etc. To build a hierarchy, it is enough to call the Add(Object) method, which in turn returns a new Row object. This way a programmer can build almost any data hierarchy in the .Net Grid. All headers and rows have their own zero-based hierarchical level that is defined by Level and Level properties. To display data in every row, .Net Grid takes header of the same level as that row. However, if that level doesn't have a header, the header for the previous hierarchical level is used. In other words, if only one header is present, the grid will behave like Microsoft Windows Explorer.

When multiple headers are used, those that have level greater than 0 will be displayed in the grid above the first visible row on the same hierarchy level. A header can be hidden/shown via the Visible property.

 Copy imageCopy
public void PopulateGrid(Grid grid)
{
    //Add some data objects
    Row product1 = grid.Rows.Add(new Product());
    Row product2 = grid.Rows.Add(new Product());

    //Add some customers to the first product
    product1.Add(new Customer());
    product1.Add(new Customer());
    //Add some customers to the another product
    product2.Add(new Customer());
}


Grouping

.Net Grid enables multiple data grouping in headers of the .Net Grid by any columns at any hierarchical level. When data is grouped by a specific column, .Net Grid searches all rows within a group that has similar values. When a group is organized, a row that doesn't contain a data object is added to the data grid. The Row.IsGroup property of such row will always return true, and Row["column id"].Value will return a value by which data is grouped. All rows with values that meet grouping conditions are attached to the newly created group. Before a new data object is added, .Net Grid verifies whether there is any group with the required value on the current hierarchical level. If there is no such group, a new group is created. When the Row.Update() method is invoked, the grid checks whether a row conforms to group value. If there are no more rows in the group, the group is removed from the grid.

In programming the grouping feature can be enabled via the Column.Grouped property. Sequential invocation of this property for several columns results in data grouping of these columns. The column with grouping remains visible unless Column.Visible property is set to false. Sorting (and multiple sorting) can be enabled or disabled for grouped columns because sorting and grouping are completely independent processes. The list of grouped columns can be viewed with Header.GroupedColumns collection property. A user can also group columns in the data grid. To use this ability the user just needs to drag a column to a special panel on the grid's header. However, this is not possible if height of this panel is set to 0.

Real-time grouping in non event-driven model is done with Update()()()() method. In the event-driven model Update()()()() method is called every time when a data object sends a notification. Once again we'd like to emphasize the importance of such model as it removes dependency of the business layer on System.Windows.Forms controls and on Dapfor assemblies as well.


Formats

A very important feature in .Net Grid its ability to work directly with application business logic. Business logic is a set of classes that may have certain properties returning specific values, i.g. prices, quantities, dates, etc. Generally these values are represented by primitive types, such as System.Int32, System.Double, System.Decimal etc. To show this data in grid cells, it's sufficient to convert the necessary values into the System.String type by calling ToString()()()() or Format(String, Object). However, this approach is not flexible and doesn't support parsing strings to objects. To fill in for this, the .Net Grid provides a very powerful system of formats to convert values into strings and vice-versa. These formats are fully customizable. For instance, the grid can display empty strings instead of "0" when a value equals 0 or add a separator between thousands or some prefix or suffix like "$". These formats can also parse strings back into values. For application programming it's better to have a set of format classes, where data presentation is centralized.

In programming, formats can be defined in the following places:

  • As an attribute of a class property. For example: FormatAttribute, DoubleFormatAttribute, EmptyFormatAttribute, etc:
     Copy imageCopy
    public class Product
    {
        private double price;
    
        [DoubleFormat(Precision = 3, ShortForm = true, ShowZero = false)]
        public double Price
        {
            get { return price; }
        }
    }
  • In a column: Column.Format = 'your format';
  • Directly in a Cell (this method requires a lot of memory): Cell.Format = 'your format';

The .Net Grid looks for IFofmat object to format values or parse strings in the following order:

  • In a Cell
  • In a Column returned by the Column property
  • In a IDataField object, returned by the DataField property.
  • If the format is still not found, the grid uses default format for the specified object type.

Some formats greatly simplify application development. For example, StringFormat, enables use of standard patterns for formatting values through Format(String, Object):

 Copy imageCopy
column.Format = new StringFormat("### ### ### ###", string.Empty, " $");
//The value 12345 will be displayed in cells as "12 345 $"

.NET Framework has similar system of type conversion based on the TypeConverter class that enables conversion of values to strings and vice versa. This conversion system is more complete, but a little bit cumbersome in the context of formating and data parsing. Althrough converters are bulky, such approach enables development of a business logic independently from data presentation. In a perfect case all these mechanisms can be completely based on Microsoft's component model and therefore have no physical dependencies on libraries of other vendors and on the Dapfor libraries as well.

 Copy imageCopy
class SomeClass
{
    private int intValue;

    [TypeConverter(typeof(HexTypeConverter))]
    int SomeHexdecimalValue
    {
        get { return intValue;  }
        set { intValue = value; }
    }
}


Editors

.Net Grid supports multiple ways of cell editing. When we were developing this mechanism, we based it on standard editors used in the PropertyGrid control. There are several types of these editors including controls that can be displayed in a dropdown box or as a modal dialog. Such editors enable users to edit text, colors and enumerations and to perform painting in small rectangles inside cells. There are plenty of predefined editors. For example, you can get the color editor as follows:

 Copy imageCopy
UITypeEditor editor = (UITypeEditor)TypeDescriptor.GetEditor(typeof (Color), typeof (UITypeEditor));

To edit values, these editors use the EditValue(IServiceProvider, Object) method. Within this method a mandatory control is created and placed in the dropdown box. You should note a very important detail' the return from the function EditValue(IServiceProvider, Object) occurs only when editing within in the control is completed, which is convenient from the programmer's point of view. Look at this example:

 Copy imageCopy
object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
    IWindowsFormsEditorService service = provider.GetService(typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService;
    using (SomeControl control = new SomeControl(value))
    {
        service.DropDownControl(control);
        value = control.NewValue;
    }
    return value;
}

The .Net Grid fully supports this mechanism and editors of other vendors that can be used in your applications.

Despite the convenience of this interface, we have concluded that it lacks some functionality. These editors can't be created above the edited cell with its size (for example, slider control). Besides that, they can't be painted in the whole cell ' just in a small rectangular area. To remediate this, we have created UITypeEditorEx ' a class, derived from UITypeEditor that allows to create controls directly above a cell. The process of editing is quite similar to the aforementioned code example:

 Copy imageCopy
StopEditReason EditCell(IGridEditorService service, Cell cell, StartEditReason reason)
{
    using (SomeControl control = new SomeControl(cell))
    {
        return service.CellEditControl(control, cell.VirtualBounds, reason);
    }
}

Besides, some editors can be used without creating graphic controls, i.e. a rating editor. It simply draws stars, and when the user clicks on a certain star, this control calculates its relative location and sets a new value. The whole editing code will look as follows:

 Copy imageCopy
StopEditReason EditCell(IGridEditorService service, Cell cell, StartEditReason reason)
{    
    //compute a new rating
    cell.Value = rating ;
    return StopEditReason.UserStop ;
}


Real-time sorting

.Net Grid enables multiple row sorting upon data changes in real-time, taking into account sorting direction, grouping rules and data hierarchy.

On programming side it can be done via SortDirection property. This property sets the sorting direction or turns it off. When you sequentially set this property for several columns, you enable multiple sorting in the data grid. Every column that is involved in the sorting has its own zero-based level. Information about all sorted columns can be retrieved via SortedColumns property. It's important to mention that column visibility doesn't affect data sorting. It means that rows will be sorted without regard to column visibility or to whether column is grouped or not.

Without sorting, all grid rows are automatically indexed thereby increasing grid performance. When sorting is used for some columns, all data is sorted in ordered sequence according to sorting rules and data hierarchy. This way, when you add new data into the data grid, it is automatically added to the right position according to the above rules. When you call Update()()()() method, the .Net Grid searches a new position for the row and moves it to the right position. When you use an event-driven model (i.e. when you use the INotifyPropertyChanged interface implementation), Update()()()() method is invoked systematically. In other words, when sorting is enabled, the .Net Grid constantly stores data in ordered sequence. In addition to that, the .Net Grid properly processes data changes in several rows including processing of separate threads. You won't find some kind of Sort() method in the .Net Grid, because the data is always in the ordered sequence.


Real-time data filtration

Simply put, data filtration is managing visibility of rows in the grid. Row invisibility in the grid means that the row is still in the grid, but it is invisible together with its children. It's important to say that this row can be accessed only via Nodes/Children collection properties. In the Rows property invisible rows are absent, as it shows only visible rows. Filtration is particularly important when data is grouped. If there are no visible rows, the whole group becomes invisible (but is not removed!). If a filtered row should be made visible again, it takes certain position according to the sorting rules if any.

.Net Grid presents 3 ways of data filtration:

  • Setting boolean in the Filtered property
  • Implementing the IFilter interface and setting it with the Filter property
  • Filters in columns

The first way is the easiest to use, however, we recommend you to favor the second one that provides definite advantages.

The IFilter interface has only one property ' IsFiltered(Row). This method is invoked when data is inserted into the data grid. It is also invoked every time Update()()()() and FilterRefresh()()()() methods are called. Therefore, grid rows always meed the filtration criteria. However, when IFilter interface is not implemented, the invocation of Update()()()() doesn't result in data filtration and newly added data is always visible in the grid until the Filtered call. Besides, when filtering conditions are changed, invocation of FilterRefresh()()()() doesn't make the row visible ' the programmer should iterate through every row in the data grid via Nodes and Children collections to verify new conditions. There is an important thing to add regarding multi-threaded applications. Invocation of IsFiltered(Row) method occurs regularly in the GUI thread, and it should be considered during development of multi-threaded applications. Please note that Update()()()() method is thread-safe and can be invoked in any thread.

Data filtration implemented via IFilter interface of non-event-driven model consists in the Row.Update() method call:

 Copy imageCopy
public class CustomFilter : IFilter
{
    public bool IsFiltered(Row row)
    {
        //There are three ways to get cell's value: 
        //1. Via Cell.Value property: double value = (double)row["Price"].Value
        //2. Via IDataAccessor and IDataField: double value = (double) row.DataAccessor["Price"].Value
        //3. Directly from the data object: double value = (double)((IList)row.DataObject)[2];

        if ((double)row["Price"].Value < 30000)
        {
            //Filter the row
            return true;
        }

        //The row is not filtered
        return false;
    }

    public event EventHandler<EventArgs> FilterUpdated;
}

public void FilterUsing(Grid grid)
{
    //Initialize the grid
    grid.Headers.Add(new Header());
    grid.Headers[0].Add(new Column("Name"));
    grid.Headers[0].Add(new Column("Color"));
    grid.Headers[0].Add(new Column("Price"));

    //Set filter
    grid.Filter = new CustomFilter();

    //Populate the grid
    Row row1 = grid.Rows.Add(new object[] { "Mercedes", Color.Black, 25000d});
    Assert.IsFalse(row1.Visible);

    Row row2 = grid.Rows.Add(new object[] { "BMW", Color.White, 35000d });
    Assert.IsTrue(row2.Visible);

    //Set a new price for "Mercedes"
    row1["Price"].Value = 32000d;
    Assert.IsTrue(row1.Visible);
}


Cell highlighting

.Net Grid provides an extremely convenient mechanism of data highlighting. Highlighting is the process of changing background color of a cell for a specified time interval and gradual restoration of the initial color afterwards. Background color of a cell can be changed twice (at the beginning and at the end) or with a little periodicity (about 30 ms)enabling fading effect with gradual transition between the highlight color and the original background color of the cell.

In programming cell highlighting can be achieved by calling Highlight(TimeSpan, Color). When a programmer calls this method, the grid adds specific information about cell's state into an internal container and launches timers when necessary. The second parameter is the color that may contain alpha-channel enabling mixing of the highlight color and the background color (transparency effect). It's important to mention that the .Net Grid has a very easy to use API that saves programmer's time. Highlighting management and initial parameters are accessible via Highlighting property.


Customization and painting

.Net Grid provides broad functionality of displaying various data grid elements, such as cells, rows, headers and columns. There two ways for data grid customization:

  • Setting specific appearance of the element via Appearance property. For instance, Appearance, Appearance, Appearance, Appearance, Appearance. All information about colors, fonts etc. is stored in memory. Therefore setting of this property is more suitable for Grid, Header, Column. A programmer should not use this property on Rows and Cells, as it will consume lot of memory.
  • Setting specific parameters in the very moment of painting. For most efficient memory usage, this information is stored in memory for only a short period of time. To implement this feature, a programmer should subscribe to a specific event such as PaintCell, PaintRow, PaintHeader etc.

The PaintXXXEventArgs contains the following information:

  • Full and clipped sizes of the painted element
  • Graphics object
  • Colors and parameters used for elemebt painting (they can be altered)
  • Methods for painting every single part of an element (for a Cell there are methods for painting background, text, selection, focus etc.)
  • Painting filter ' PaintPart, which allows to skip a certain part of an element (partial painting)
  • PaintAll() method that paints all elements defined in the PaintPart filter
  • Handled property that forbids a data grid to perform default painting

Note that the whole painting process is accumulated in PaintXXXEventArgs and consists of the following: a data grid creates PaintXXXEventArgs object, sets default parameters, fires Paint event, and if Handled property is set to false, invokes PaintAll() method. This mechanism is used to paint all elements of the .Net Grid ' cells, rows, columns, headers, hierarchy tree etc.

We have intentionally described the process of painting in details to show broad and rich abilities of data grid customization. In other words, you may customize any parameters, some painted elements (cell, column) and even use Graphics object to manipulate the data grid. Even better, this approach enables you to define a painting sequence. For example, you may perform some default actions and then finish drawing with the Graphics object, or do it vice versa!


Invalidation

It is well known that System.Windows.Forms controls use standard Windows API based on window messages. To repaint a certain part of a control, it should invoke the Invalidate(Rectangle) method, where Rectangle specifies location and size of the client surface that should be repainted. In general, the calculation of screen coordinates of an element is a quite complicated process, especially in a hierarchical data grid. The .Net Grid provides very convenient API to repaint different elements such as cells, rows, columns etc.

Below you may see a list of elements that can be repainted in the grid:


Tooltips

Displaying auxiliary information above a grid cell is a typical task. In the .Net Grid this process is considerably simplified. To display a tooltip is is sufficient to set text calling Cell.TooltipMessage = "some text". Color management, display time and tooltip location can be set with Grid.Tooltips property. The programmer can control tooltips via .Net Grid notifications.

Inheritance Hierarchy

See Also