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 Part 1: Data Types

 
The grid provides broad capabilities of working with data such as arbitrary class objects, row arrays, objects implementing IList or IDictionary interfaces and objects with variable number of fields.

Objects of arbitrary classes

Such objects are the main and recommended type of data processed by the grid. A programmer may create any arbitrary classes in application. Objects of these classes are associated with grid rows, and their properties are associated with grid columns. Values returned by getters of these properties are displayed in grid cells.

public class MyCustomClass
{
private int _intValue;
private double _doubleValue;
private string _stringValue;

public MyCustomClass(int intValue, double doubleValue, string stringValue)
{
_intValue = intValue;
_doubleValue = doubleValue;
_stringValue = stringValue;
}

public int IntValue
{
get { return _intValue; }
set { _intValue = value; }
}

public double DoubleValue
{
get { return _doubleValue; }
set { _doubleValue = value; }
}

public string StringValue
{
get { return _stringValue; }
set { _stringValue = value; }
}
}

//Build a header with columns
grid.Headers.Add(new Header());
grid.Headers[0].Add(new Column("IntValue"));
grid.Headers[0].Add(new Column("DoubleValue"));
grid.Headers[0].Add(new Column("StringValue"));

//Add an object of MyCustomClass to a grid:
grid.Rows.Add(new MyCustomClass(10, 11.12, "some string 1"));
grid.Rows.Add(new MyCustomClass(20, 21.33, "some string 2"));

//Other way - bind grid to a BindingList:
BindingList<MyCustomClass> bindingList = new BindingList<MyCustomClass>();
bindingList.Add(new MyCustomClass(10, 11.12, "some string 1"));
bindingList.Add(new MyCustomClass(20, 21.33, "some string 2"));
grid.DataSource = bindingList;


binding 2

Object or string arrays

The grid enables work with arrays of strings or any other objects. This method is applicable only to very simple applications. The grid doesn’t set any limitations of data types, however working with untyped data in a large application often causes a lot of errors, makes the code much more complicated and indicates improper application design.

//Add an array of objects to a grid
grid.Rows.Add(new object[] { 10, 11.12, "some string 1" });
grid.Rows.Add(new object[] { 20, 21.33, "some string 2" });

//Bind a collection of arrays to a grid
List<object> collection = new List<object>();
collection.Add(new object[] { 10, 11.12, "some string 1" });
collection.Add(new object[] { 20, 21.33, "some string 2" });
grid.DataSource = collection;

Dictionaries

IDictionary<string, object> can be used for objects with variable number of fields. The key in this container is a string that identifies the data field. If it matches a column identifier, the value of this object is displayed in cell. This method is usually applicable to data not modified by the application. Dictionary<string, object> type objects take a lot of memory. Therefore, it’s not worth bothering to create a lot of such objects. The recommended number of objects of this type shouldn’t exceed 10000 per grid.

//Add a dictionary to a grid
IDictionary<string, object> dataObject1 = new Dictionary<string, object>();
dataObject1.Add("IntValue", 10);
dataObject1.Add("StringValue", "some string 1");
grid.Rows.Add(dataObject1);

IDictionary<string, object> dataObject2 = new Dictionary<string, object>();
dataObject2.Add("IntValue", 20);
dataObject2.Add("DoubleValue", 21.33);
grid.Rows.Add(dataObject2);

//Bind a collection of dictionaries to a grid
BindingList<IDictionary<string, object>> collection = new BindingList<IDictionary<string, object>>();
collection.Add(new Dictionary<string, object>());
collection[0].Add("IntValue", 10);
collection[0].Add("StringValue", "some string 1");

collection.Add(new Dictionary<string, object>());
collection[1].Add("IntValue", 20);
collection[1].Add("DoubleValue", 21.33);
grid.DataSource = collection;

binding 1

Variable number of fields with UnboundValueAccessor

Use of UnboundValueAccessor for data with variable number of fields. Its difference from IDictionary is that this container enables grid notification of data modification, and data updating, sorting, grouping and filtering is done automatically.

    
//Add an object to the grid
grid.Rows.Add(new UnboundValueAccessor());
//Set dynamically some fields
grid.Rows[0]["IntValue"].Value = 10;
grid.Rows[0]["DoubleValue"].Value = 11.12;

//Create and add some other object
UnboundValueAccessor dataObject2 = new UnboundValueAccessor();
grid.Rows.Add(dataObject2);

//Each modification in dataObject2 will notify the grid
dataObject2["IntValue"].Value = 20;
dataObject2["DoubleValue"].Value = 21.33;
dataObject2["StringValue"].Value = "some string 2";


//The same with binding to a BindingList<UnboundValueAccessor> collection:
BindingList<UnboundValueAccessor> collection = new BindingList<UnboundValueAccessor>();

//Populate collection with 2 objects
collection.Add(new UnboundValueAccessor());
collection.Add(new UnboundValueAccessor());

//Set some fields in the collection:
collection[0]["IntValue"].Value = 10;
collection[0]["StringValue"].Value = "some string 1";
collection[1]["StringValue"].Value = "some string 2";

//Bind grid to a colelction
grid.DataSource = collection;

//Each data modification will notify the grid
collection[0]["IntValue"].Value = 55;
collection[1]["DoubleValue"].Value = 21.33;

binding 3

Event-driven model: A special case of objects of arbitrary classes

As it has been mentioned before, objects of arbitrary classes are recommended for use in the grid. There are many reasons for this. First of all, it is easier and more convenient to work with typed data in applications of high and medium complexity. Besides, objects of arbitrary classes use less memory than string[] objects.

Event-driven model is another important reason to use objects of arbitrary classes. If data objects implement INotifyPropertyChanged interface, the grid subscribes to events of this object and after receiving notification it automatically sorts, groups and filters them. Updating data in a traditional model requires finding a Row that is associated with data and performing all these operations manually
(which is fairly hard). Just compare: MyCustomClass type object is associated with two rows in grid1 and grid2. Grid1 uses sorting and grid2 uses data filtering. Upon change of e.g. IntValue in data object grid1 and grid2 will receive notifications and then grid1 will sort the row to the required position and grid2 will hide it.

This requires only implementation of INotifyPropertyChanged interface in the data object.


public class MyCustomClass : INotifyPropertyChanged
{
private int _intValue;
...

public MyCustomClass(int intValue, double doubleValue, string stringValue)
{
_intValue = intValue;
...
}

public int IntValue
{
get { return _intValue; }
set
{
if(_intValue != value)
{
_intValue = value;
FirePropertyChanged("IntValue");
}
}
}

public double DoubleValue
{
...
}

public string StringValue
{
...
}

private void FirePropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

public event PropertyChangedEventHandler PropertyChanged;
}

//populate both grids with random data:
BindingList<MyCustomClass> collection = new BindingList<MyCustomClass>();
Random r = new Random();
for(int i = 0; i < 5; ++i)
{
collection.Add(new MyCustomClass(i, r.NextDouble(), "some string " + i));
}
grid1.DataSource = collection;
grid2.DataSource = collection;

//Set multiple sorting in the grid1:
grid1.Headers[0]["IntValue"].SortDirection = SortDirection.Ascending;
grid1.Headers[0]["DoubleValue"].SortDirection = SortDirection.Descending;

//Set a filter for the second grid. Grid2 will hide all rows with IntValue > 30
grid2.Filter = new Filter(delegate(Row row)
{
MyCustomClass dataObject = row.DataObject as MyCustomClass;
return dataObject != null && dataObject.IntValue > 30;
});

binding 4

Let’s note that in a non-event-driven model implementation of this simple example will require much more code related to searching for rows in the grid and controlling sorting, visibility and filtering.