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; }
}
}
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"));
grid.Rows.Add(new MyCustomClass(10, 11.12, "some string 1"));
grid.Rows.Add(new MyCustomClass(20, 21.33, "some string 2"));
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;
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.
grid.Rows.Add(new object[] { 10, 11.12, "some string 1" });
grid.Rows.Add(new object[] { 20, 21.33, "some string 2" });
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.
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);
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;
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.
grid.Rows.Add(new UnboundValueAccessor());
grid.Rows[0]["IntValue"].Value = 10;
grid.Rows[0]["DoubleValue"].Value = 11.12;
UnboundValueAccessor dataObject2 = new UnboundValueAccessor();
grid.Rows.Add(dataObject2);
dataObject2["IntValue"].Value = 20;
dataObject2["DoubleValue"].Value = 21.33;
dataObject2["StringValue"].Value = "some string 2";
BindingList<UnboundValueAccessor> collection = new BindingList<UnboundValueAccessor>();
collection.Add(new UnboundValueAccessor());
collection.Add(new UnboundValueAccessor());
collection[0]["IntValue"].Value = 10;
collection[0]["StringValue"].Value = "some string 1";
collection[1]["StringValue"].Value = "some string 2";
grid.DataSource = collection;
collection[0]["IntValue"].Value = 55;
collection[1]["DoubleValue"].Value = 21.33;
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;
}
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;
grid1.Headers[0]["IntValue"].SortDirection = SortDirection.Ascending;
grid1.Headers[0]["DoubleValue"].SortDirection = SortDirection.Descending;
grid2.Filter = new Filter(delegate(Row row)
{
MyCustomClass dataObject = row.DataObject as MyCustomClass;
return dataObject != null && dataObject.IntValue > 30;
});
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.