Click here to Skip to main content
Licence CPOL
First Posted 29 Feb 2008
Views 9,644
Downloads 111
Bookmarked 14 times

Bind Entities without Properties to Windows Forms Controls

By | 29 Feb 2008 | Article
How to avoid property coding in entity objects

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

da5id

Software Developer

Germany Germany

Member



Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
Generalother options Pinmembersefstrat21:01 29 Feb '08  
QuestionBreaking guidelines? PinmemberPop Catalin6:02 29 Feb '08  
AnswerRe: Breaking guidelines? Pinmemberda5id8:02 29 Feb '08  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web01 | 2.5.120517.1 | Last Updated 29 Feb 2008
Article Copyright 2008 by da5id
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid