Prev topic: .Net Grid tutorial (Part15: Real-time blotter)
Next topic: .Net Grid tutorial (Part17: Instrument browser)
Once again let’s start from requirements to application and business model. First let’s define the list of currency for the application. It may include US dollar, euro, etc and the list of currencies can be expanded in future. In our simplify example we shall use string to represent currencies and add a list of available currencies to previously created Provider class.
Every currency may have different rates to other currencies, but its rate for itself shall always equal 1. We shall create CurrencyRate class that represents currency price as compared to all other currencies. For convenience, it would be good to represent rate values as indexing operator where the index would be a currency with rate to be determined. Let's note that price rates may change in real time. To change currency rates we can use the same indexing operator for setting new values. Let’s provide an example demonstrating the above-described API.
C# | Copy |
---|---|
public class CurrencyRate : INotifyPropertyChanged { ... public IList<string> AvailableCurrencies { get { return _availableCurrencies; } } public string MainCurrency { get { return _mainCurrency; } } } CurrencyRate currencyRate = CurrencyRate("USD", Provider.Instance.AvailableCurrencies); //Get current USD/EUR rate double rate = currencyRate["EUR"]; //Set a new USD/EUR rate currencyRate["EUR"] = 1.42; |
It would be good to notify subscribers of price changes. For this purpose we shall implement INotifyPropertyChanged interface in CurrencyRate object and put the object collection to Provider class.
The data model is ready. Now we have to display it in the grid. The perfect way to achieve that is to bind .Net Grid directly to business objects without creating intermediate objects and duplicating information. CurrencyRate objects are the objects that should be displayed in the grid.
We can connect data source to the grid in regular way using Grid.DataSource. Grid columns can be created automatically by using CurrencyRate class:
C# | Copy |
---|---|
Header header = Header.FromDataType(typeof (CurrencyRate)); grid.Headers.Add(header); //Bind grid to the collection of CurrencyRate grid.DataSource = new List<CurrencyRate>(Provider.Instance.CurrencyRates.Values); |
After performing the actions above the application shall look as follows:
Now let’s move to columns. The application has only two columns as the CurrencyRate class has only two public properties. There are multiple ways to display the required columns in the grid. We can use Dapfor engine and use implement own IDataAccessor. The other approach is more general and is based on Microsoft component model. This model has System.ComponentModel.TypeDescriptor class that provides meta information for any class of the application. This class creates description of properties, attributes and other useful information, but unlike Type class it enables setting custom lists of properties. To do it we need to create and declare a new class in CurrencyRateTypeDescriptorProvider.
C# | Copy |
---|---|
// TypeDesctiptor provider. internal class CurrencyRateTypeDescriptorProvider : System.ComponentModel.TypeDescriptionProvider { private ICustomTypeDescriptor _typeDescriptor; public CurrencyRateTypeDescriptorProvider() : base(TypeDescriptor.GetProvider(typeof (CurrencyRate))) { } public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) { return _typeDescriptor ?? (_typeDescriptor = new CurrencyRateTypeDescriptor(base.GetTypeDescriptor(objectType, instance))); } } |
The objective of this class is to provide TypeDescriptor for CurrencyRate type objects. The descriptor itself describes class properties, events and attributes. In our example we are interested only in descriptive part of properties. For this purpose we shall create CurrencyRateTypeDescriptor class and inherit it from System.ComponentModel.CustomTypeDescriptor. We only have to redefine GetProperties() virtual method for this class. Let’s make names of properties described by CurrencyRateTypeDescriptor to match currency names.
C# | Copy |
---|---|
// Provides an overridden collection of CurrencyRate properties internal class CurrencyRateTypeDescriptor : System.ComponentModel.CustomTypeDescriptor { private readonly ICustomTypeDescriptor _baseDescriptor; public CurrencyRateTypeDescriptor(ICustomTypeDescriptor baseDescriptor) { _baseDescriptor = baseDescriptor; } public override PropertyDescriptorCollection GetProperties() { List<PropertyDescriptor> propertyDescriptor = new List<PropertyDescriptor>(); //Add existing properties foreach (PropertyDescriptor descriptor in _baseDescriptor.GetProperties()) { propertyDescriptor.Add(descriptor); } //Add new properties foreach (string currency in Provider.Instance.AvailableCurrencies) { propertyDescriptor.Add(new CurrencyRatePropertyDescriptor(currency, null)); } return new PropertyDescriptorCollection(propertyDescriptor.ToArray()); } public override PropertyDescriptorCollection GetProperties(Attribute[] attributes) { return GetProperties(); } } |
To complete implementation of component model we only have to implement CurrencyRatePropertyDescriptor, which implements properties of the CurrencyRate class, which is fairly simple. Its implementation is demonstrated below.
C# | Copy |
---|---|
// Read-write property of the CurrencyRate class internal class CurrencyRatePropertyDescriptor : System.ComponentModel.PropertyDescriptor { public CurrencyRatePropertyDescriptor(string name, Attribute[] attrs) : base(name, attrs) { } public override Type ComponentType { get { return typeof (CurrencyRate); } } public override bool IsReadOnly { get { return false; } } public override Type PropertyType { get { return typeof (double); } } public override bool CanResetValue(object component) { return false; } public override object GetValue(object component) { CurrencyRate rate = (CurrencyRate) component; return rate[base.Name].Rate; } public override void ResetValue(object component) { } public override void SetValue(object component, object value) { CurrencyRate rate = (CurrencyRate) component; rate[base.Name].Rate = (double) value; } public override bool ShouldSerializeValue(object component) { return false; } } |
Let’s note an interesting detail: in this implementation GetValue() and SetValue() don’t use reflection unlike standard System.ComponentModel.TypeDescriptor that is associated with all classes by default. Accordingly, in real-time GetValue() method may be called frequently enough to save CPU resources.
Now, let's declare CurrencyRateTypeDescriptorProvider with System.ComponentModel.TypeDescriptionProviderAttribute and hide AvailableCurrencies and MainCurrency properties with System.ComponentModel.BrowsableAttribute:
C# | Copy |
---|---|
[TypeDescriptionProvider(typeof(CurrencyRateTypeDescriptorProvider))] public class CurrencyRate : INotifyPropertyChanged { ... [Browsable(false)] public IList<string> AvailableCurrencies { get { return _availableCurrencies; } } [Browsable(false)] public string MainCurrency { get { return _mainCurrency; } } } |
See screenshot of results of working with Microsoft component model below.
Another important note: if we add CurrencyRate object to standard PropertyGrid or DataGridView controls, they will also interpret business object properties as currency names.
Now we shall add currency names to RowSelector and add real-time animation to demonstrate data update. For this purpose we shall use existing Provider class, where we shall add real-time currency updating.
Let’s note that the grid receives notifications via INotifyPropertyChanged interface. Microsoft component model described above is used to get cell values. For better understanding of data used by the grid we can also use .Net Inspector:
Now to complete our sample we only need to change grid appearance. To make the currency converter more convenient we shall distinguish direct and reverse currency rates with different colors. The graphical control should highlight any price change for currency pair for 500 ms. If the price is rising the control should use green color, and if it is dropping – red color. Now we shall add vertical and horizontal lines to divide adjacent cells. When mouse cursor is placed over a cell with price, it should also display reverse rate for the selected currency pair.
Note that this application uses almost no CPU resources and optimizes memory consumption as it doesn’t use duplicate data. The application doesn’t have an intermediate layer between data and data presentation. This greatly reduces memory consumption and ensures thread safety.