Introduction
This article shows how I have managed to use databinding to display/edit objects with Hybrid Properties. By Hybrid Property, I mean properties with multiple modes (e.g. data or formulae
or code, etc). A Hybrid Property at one time can have one or multiple modes (meaning if the user types a formulae
, then at the same time have formulae
like say "=G1+ 2.5" as well its converted value which evaluates to something). The user can choose to input the date as codes say "3m" when the date property switches mode to "code" and displays a valid date which is 3 months from now. Real life use cases are even complex than this but the general idea is how to effectively provide an object abstraction which can handle these complexities through a neat manageable non repetitive objective pattern.
Background
Databinding is an effective way to display objects on a grid. This is easy with objects having integral property types or if property types implement IConvertible
or if they have type converters.
So far so good, but things become messy when the databound objects are user editable and we want to achieve the above requirements.
If we take the approach of properties having just string
types, so that we can parse the user input, then we end up mangling our business code with UI specific code.
A better approach would be to use BO specific custom Types for the properties and have their custom type converters. But TypeConverters
do not maintain state and what if we have loads of properties and the user might have a valid reason to persist both formulae
as well as data for a specific property.
To tackle this situation , I have made a Generic Property Type called Attribute<T>
with hybrid states.
Attribute Class
The Attribute
class is a generic class which can have various modes . The modes are context specific and can be easily set to retrieve meaningful string
conversion hence facilitating hybrid feature, whereas Value
part will always return the implied integral value. Also depending on mode and context, specific layer (BO or UI or Presenter) can take specific action like either modify this object or trigger some external action.
This is to some extent like AOP without incorporating its messaging framework. To give a specific usecase, most of the grids that support formulae
engine, require the string
conversion to return a formulae string
. But after the engine evaluates a value for the formulae
, the grid uses reflection to invoke the typeconverter
on the propertytype
to set the value.
On the other hand while the attribute is in useful business mode like say Code = "atms"
for strike, the BO layer can compute "ATM" from Analytics and set the value.
Secondly, this object encapsulation makes it a lot easier to handle UI, BO, Data Layers, without much code fragmentation and cyclomatics.
Thirdly, Attribute
being a generic type makes it possible to dynamically associate internal integral type for the value. Meaning DateTime
for a datetime column, double
for some other column, etc.
namespace Attributes
{
public enum AttributeMode
{
Text,
Data,
Error,
Formulae,
Code
};
[Serializable]
[TypeConverter(typeof(AttributeConverter))]
public class Attribute<Type>
{
}
}
But the problem now becomes how to provide a TypeConverter
for this class, because the Generic Type for the Attribute
class is not known at design time when we try to convert "From
" string
to this specific Type .
AttributeConverter
public override object ConvertFrom(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture, object value)
{
Mediator mediator = new Mediator();
mediator.Value = value.ToString();
return mediator;
}
The trick is to return a Mediator
Type in the ConvertFrom
, so that subsequently the CLR will try to convert the Mediator
type "To
" to this specific type.
Note that the ConvertTo
method of the TypeConverter
has the destination type. Thus we can now use the destinationType
to probe the embedded generic type.
The code below is self explanatory. The most important part here is how we use Reflection to probe the generic type associated with the Value
property of the specific Attribute
generic class, which has been dynamically set at some point of execution. (Note Types in generics C# are set in runtime unlike as in C++ preprocessing time.)
I can now get the associated type converter of the Target Value
Type and use it to convert "From
" string
to this specific type and set it in the destination Type instance.
MediatorConverter
public override object ConvertTo(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture, object value, Type destinationType)
{
object target = null;
if (value is Mediator)
{
target = Activator.CreateInstance(destinationType);
if (((Mediator)value).IsCode)
{
PropertyInfo piTarget = destinationType.GetProperty("Code");
piTarget.SetValue(target, ((Mediator)value).Value, null);
piTarget = destinationType.GetProperty("Mode");
piTarget.SetValue(target, AttributeMode.Code, null);
}
else if (((Mediator)value).IsFormulae)
{
PropertyInfo piTarget = destinationType.GetProperty("Formulae");
piTarget.SetValue(target, ((Mediator)value).Value, null);
piTarget = destinationType.GetProperty("Mode");
piTarget.SetValue(target, AttributeMode.Formulae, null);
}
else
{
PropertyInfo piMediatorValue = value.GetType().GetProperty("Value");
Type tMediatorValue = piMediatorValue.PropertyType;
PropertyInfo piTarget = destinationType.GetProperty("Value");
Type tTarget = piTarget.PropertyType;
TypeConverter tcTarget = TypeDescriptor.GetConverter(tTarget);
if (tcTarget != null && tcTarget.CanConvertFrom(tMediatorValue))
{
object mediatorValue = piMediatorValue.GetValue(value, null);
object targetValue =
tcTarget.ConvertFrom(context, culture, mediatorValue);
piTarget.SetValue(target, targetValue, null);
piTarget = destinationType.GetProperty("Mode");
piTarget.SetValue(target, AttributeMode.Data, null);
}
}
}
return target;
}
}
Conclusion
This is a useful way to provide a Hybrid Property Type with Generic Type behaviour. Credit also goes to Marc Clifton who pointed type conversion on similar lines. If you find errors or further enhancements, I would be happy to hear from you.
History
- 23rd February, 2008: Initial post
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.