Introduction
Bind Entities without Properties to Windows Forms Controls.
Background
Most developers are not amused by coding properties for each field of an entity (although in all Frameworks that I know, properties are mandatory). The properties are required by the databinding mechanism of all Window Forms controls. The standard databinding also does not support binding of embedded object properties (e.g. Person.address.City
). Because I get tired of writing redundant code (properties and dummy objects to be able to bind embedded object properties) the idea of this little Framework was born.
The base entity BaseEntiy
provides all methods that are needed for the databinding mechanism of the Framework. The entities only define private or public fields. The GenericEntityList<T>
and the GenericEntityPropertyDescriptor
classes are the only extensions to the standard databinding classes that translate all property calls to the GetValue<T>
and SetValue<T>
methods of the BaseEntity
class.
At this time only the databinding for the DataGridView
is supported by the Framework.
Using the Code
Let's start with a simple example of using the Framework. Create a new Project (Class Library), set a reference to the GenericEntity.dll and create a class e.g. Person. The Person
class have five private members No, Name, Surname, Birthdate and Address. To support the databinding mechanism of the Framework the class Person
have to inherit from the BaseEntity
. When using private members set the PropertyVisibility
Attribute to control the visibility.
public class Person:BaseEntity
{
[PropertyVisibility(true)]
private Int32 no;
[PropertyVisibility(true)]
private String name;
[PropertyVisibility(true)]
private String surname;
[PropertyVisibility(true)]
private DateTime dateOfBirth;
[PropertyVisibility(true)]
private Address address;
}
Now create a second Project (Windows Application), set references to the GenericEntity.dll and to the SampleLib.dll, create a Form
and add a DataGridView
control to the Form
.
To bind a Collection
of Person
to the DataGridView
set a GenericEntityList<Person>
as datasource of the DataGridView
.
Person person = new Person();
person.SetValue<Int32>("no", 1);
person.SetValue<String>("name", "Donald");
person.SetValue<String>("surname", "Duck");
person.SetValue<DateTime>("dateOfBirth", new DateTime(1934, 6, 9));
Address address = new Address();
address.SetValue<String>("city", "Ducktown");
address.SetValue<String>("street", "Duck Str. 1");
person.SetValue<Address>("address", address);
GenericEntityList<Person> persons = new GenericEntityList<Person>();
persons.Add(person);
PersonsDataGridView.DataSource = persons;
All public or visible tagged members of the Person
are displayed in the DataGridView
. Also all visible tagged members of embedded BaseEntity
classes are displayed. The GenericEntityList<T>
supports adding a new row and sorting.
Points of Interest
Reflection and generics are the most valuable features of the .NET 2.0 Framework. My little databinding framework combines both to handle the databinding mechanism without coding redundant properties. Let's have a look at the GenericEntityList<T>
class. The GetItemProperties
method publishes all public properties of the datasource entities to the binding classes. To support binding of entities without properties the GenericEntityList<T>
collects all public or visible tagged private members of an entity and publish this PropertyDescriptorCollection
to the binding classes.
public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
{
T entity = items[0];
IList<GenericEntityPropertyDescriptor> genericProperties = GetVisibleProperties(
entity.GetType(), null);
PropertyDescriptor[] propertyDescriptors =
new PropertyDescriptor[genericProperties.Count];
int index = 0;
foreach (GenericEntityPropertyDescriptor genericProperty in genericProperties)
{
propertyDescriptors[index] = genericProperty;
index++;
}
return new PropertyDescriptorCollection(propertyDescriptors);
}
internal IList<GenericEntityPropertyDescriptor> GetVisibleProperties(Type entityType,
String rootPropertyName)
{
FieldInfo[] fields = GetFieldInfos(entityType);
IList<GenericEntityPropertyDescriptor> propertyList =
new List<GenericEntityPropertyDescriptor>();
foreach (FieldInfo fieldInfo in fields)
{
if (FieldIsVisible(fieldInfo))
{
String propertyName = fieldInfo.Name;
if (rootPropertyName != null)
propertyName = rootPropertyName + "." + fieldInfo.Name;
if (fieldInfo.FieldType.BaseType.Equals(typeof(BaseEntity)))
{
Type subEntityType = fieldInfo.FieldType;
foreach (
GenericEntityPropertyDescriptor propertyDescriptor in
GetVisibleProperties(subEntityType, propertyName))
{
propertyList.Add(propertyDescriptor);
}
}
else
{
propertyList.Add(
new GenericEntityPropertyDescriptor(propertyName, items[0]));
}
}
}
return propertyList;
}
internal Boolean FieldIsVisible(FieldInfo info)
{
if (!info.IsPublic)
{
PropertyVisibilityAttribute[] attributes =
(PropertyVisibilityAttribute[])info.GetCustomAttributes(typeof(
PropertyVisibilityAttribute), true);
if (attributes == null)
return false;
if (!attributes[0].IsVisible)
return false;
}
return true;
}
To support sorting a custom Comparer
is needed. In this framework the Comparer
is a generic class GenericEntityComparer<T>
where T have to inherit from BaseEntity
. The Constructor
expects two arguments, String fieldName
and Type fieldType
. While the GenericEntityList<T>
has to create an instance of the GenericEntityComparer<T>
the creation has to be done via reflection. To create an instance of a generic class it is required to pass the list of Types
to the generic type parameter of the generic type via the method MakeGenericType
. The GetConstructor
method of the GenericEntityComparer<T>
Type also expects the Types
of the Constructor
arguments. To create an instance call the Invoke
methods of the ConstructorInfo
with the values of the Constructor
arguments.
internal IComparer<T> GetComparer(PropertyDescriptor property)
{
Type genericType = typeof(GenericEntityComparer<>);
genericType = genericType.MakeGenericType(new Type[1]{typeof(T)});
ConstructorInfo constructorInfo = genericType.GetConstructor(
new Type[2]{typeof(String), typeof(Type)});
object[] parameters = new object[2];
parameters[0] = property.Name;
parameters[1] = items[0].GetFieldType(property.Name);
return (IComparer<T>)constructorInfo.Invoke(parameters);
}
The Compare
implementation of the IComparer<T>
uses the generic method GetValue<T>
of the BaseEntity
. Due to the fact that the method is generic reflection is used to invoke it.
public int Compare(T x, T y)
{
MethodInfo methodInfo = x.GetType().GetMethod("GetValue");
MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod(
x.GetFieldType(fieldName));
object[] parameters = new object[1];
parameters[0] = fieldName;
IComparable xComparable = ((IComparable)genericMethodInfo.Invoke(x, parameters));
IComparable yComparable = ((IComparable)genericMethodInfo.Invoke(y, parameters));
return xComparable.CompareTo(yComparable);
}
The implementation supposes an IComparable
Type
to do the comparison in an uncomely manner, but I haven't found a way to avoid the error-prone cast.