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 Part4: Data formatting and presentation

In compliance with the principle of separating the data layer from the presentation layer, NetGrid has a powerful data binding system  that fully implements all relevant capabilities. Data can be represented with arbitrary classes, lists, Dictionaries, etc. The grid provides multiple methods of connecting to this data via a binding list or in unbound mode.

This tutorial shows presentation of formatted business layer data and back transformation of this data to values, for  example when a user edits a cell via TextBox or other controls.

It is very important to understand how data differs from its presentation. Let’s review an example to make it more clear. Let’s take some abstract product. Quantity and price of this product can be represented with simple types: int and double. Class containing this data has corresponding fields: Price & Quantiry.


class Quote
{
private int _quantity;
private double _price;

public int Quantity
{
get { return _quantity; }
}

public double Price
{
get { return _price; }
}
}

It is better to provide this data in a form convenient for user, i.e. if the price is not set (price value equals zero), it is better not to display anything in cells. If price and quantity values are high, it would be good to display thousand separators or display data in a special way, e.g. adding suffixes for thousands, millions, etc.

For example, value 1234567 can be displayed as 1.23 M

However, if a user decides to edit quantity and types 15 K, this value should be transformed to 15000 in Quote object.

Now let’s see how is data displayed in the grid. For this purpose the grid provides a simple IFormat interface that enables data formatting and reverse operations that the programmer can use for full customization of text presentation of business data.


//Display a number with thousands separator and two decimal places.
class CustomFormat : IFormat
{
public string Format(IDataField dataField)
{
return string.Format(CultureInfo.InvariantCulture, "{0:### ### ##0.##}", dataField.Value).Trim();
}

public bool CanParse(string text, IDataField dataField)
{
return false;
}

public void Parse(string text, IDataField dataField)
{
}
}

Format setting

There are several ways of setting an arbitrary format:

  • Declarative definition of format for data objects. Format is declared via FormatAttribute for data object properties. If data of different types is used in the grid, this approach enables different ways of formatting values in the same column. Besides, when the same data objects are used in different grids, they are displayed in the same way in the entire application.


    class Quote
    {
    ...

    [Format(typeof(CustomFormat))]
    public double Price
    {
    get { return _price; }
    }

    ...
    }


    There are some specialized attributes that simplify declarative format definition: DoubleFormatAttribute, EmptyFormatAttribute etc.  It is also possible to create custom attributes for declaring formats with parameters.

    //Display a number using specific format string
    class CustomFormat : IFormat
    {
    private readonly string _formatString;

    public CustomFormat(string formatString)
    {
    _formatString = formatString;
    }

    public string Format(IDataField dataField)
    {
    return string.Format(CultureInfo.InvariantCulture, "{0:" + _formatString +"}", dataField.Value).Trim();
    }

    public bool CanParse(string text, IDataField dataField)
    {
    return false;
    }

    public void Parse(string text, IDataField dataField)
    {
    }
    }

    //An attribure returning an instance of IFormat object
    public class CustomFormatAttribute : FormatBaseAttribute
    {
    private readonly string _formatString;

    public CustomFormatAttribute(string formatString)
    {
    _formatString = formatString;
    }

    public override IFormat Format
    {
    get { return new CustomFormat(_formatString); }
    }
    }

    //Declare custom format
    class Quote
    {
    ...

    [CustomFormat("### ### ##0.##")]
    public double Price
    {
    get { return _price; }
    }

    ...
    }
  • Format can be set directly in grid column and values in cells will be formatted in the same way disregarding formats set via FormatAttribute


    Column column = grid.Headers[0]["Price"];
    column.Format = new CustomFormat("### ### ##0.##");

Cell value formatting

Values can be transformed to text strings displayed in the grid as follows:

  • The grid checks whether format is set for column corresponding to cells.

  • If no format is found, the grid checks whether declarative format exists for data object field.

  • If neither format is found, the grid searches for default format corresponding to data type. Basically this format calls object.ToString() method or invokes the ConvertTo() of appropriate TypeConverter.

  • The string is written to PaintCellEventArgs where the programmer can easily modify it:


    grid.PaintCell += delegate(object sender, PaintCellEventArgs e)
    {
    if(e.Cell.Column != null && e.Cell.Column.Id == "some id")
    {
    e.Text = "a new text to be displayed in cell";
    }
    };
  • Finally, the resulting string is displayed in grid cell via Graphics object.


Cell value parsing

Formats can be two-sided. It means that data entered by user in cell editors can be transformed from text to unformatted values and transmitted to data objects. For this purpose it is sufficient to implement CanParse and Parse methods in a class implementing IFormat.


class CustomFormat : IFormat
{
public string Format(IDataField dataField)
{
return string.Format(CultureInfo.InvariantCulture, "{0:### ### ##0.##}", dataField.Value).Trim();
}

public bool CanParse(string text, IDataField dataField)
{
text = text.Replace(" ", string.Empty);
double value;
return double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out value);
}

public void Parse(string text, IDataField dataField)
{
text = text.Replace(" ", string.Empty);
dataField.Value = double.Parse(text, NumberStyles.Float, CultureInfo.InvariantCulture);
}
}

TypeConverter – a specific method of declarative format setting

An actual project may consist of multiple assemblies some of which may represent application business logic while others may represent graphical controls displaying this logic. To comply with the principle of business logic being independent from presentation, in such case it would be good to remove dependency on assemblies with graphical components in business logic assemblies, including Dapfor.Net.dll assembly.

On the other hand, sometimes it would be convenient to use declarative data formatting set with FormatAttribute attributes.

Microsoft component model can be used to avoid adding dependency on Dapfor.Net.dll to application business logic. Specifically, it would be good to use TypeConverter that enables transformation of data of one type to another and back. In most cases these converters are set for data types:


TypeConverter converter = TypeDescriptor.GetConverter(typeof (Color));


E.g. Color class has its ColorConverter. However, it is also possible to
declare these converters for data object properties as well.

//Type converter to transform double values to strings and vice versa
class DoubleTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
}

//Converts value to string
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string))
{
return string.Format(CultureInfo.InvariantCulture, "{0:### ### ##0.##}", value).Trim();
}

return base.ConvertTo(context, culture, value, destinationType);
}

//Converts string to double value
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string)
{
string text = ((string)value).Replace(" ", string.Empty);
double v = 0;
if(!string.IsNullOrEmpty(text))
{
double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out v);
}
return v;
}

return base.ConvertFrom(context, culture, value);
}
}

//Declaration of the converter without reference to the Dapfor.Net.dll assembly
class Quote
{
...

[TypeConverter(typeof(DoubleTypeConverter))]
public double Price
{
get { return _price; }
set { _price = value; }
}

...
}
Declarative formatting