Click here to Skip to main content
15,881,173 members
Articles / Programming Languages / C#
Article

Bind Entities without Properties to Windows Forms Controls

Rate me:
Please Sign up or sign in to vote.
1.00/5 (4 votes)
29 Feb 2008CPOL3 min read 25.6K   198   14   3
How to avoid property coding in entity objects
Image 1

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.

C#
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.

C#
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.

C#
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.

C#
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.

C#
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)


Written By
Software Developer
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Generalother options Pin
sefstrat29-Feb-08 21:01
sefstrat29-Feb-08 21:01 
QuestionBreaking guidelines? Pin
Pop Catalin29-Feb-08 6:02
Pop Catalin29-Feb-08 6:02 
So you're breaking the guidelines that say :

"Do not use instance fields that are public or protected "

for what reason? Programmers don't like to write property code? well, that is a very weak reason.
Propeties are a central point in .Net and everyting orbits around them, databinding, PropertyGrid, design time suport, not to mention the OOP rules they enforce, encapsulation, hiding of implementation.

I don't want to say here why generaly it's a very bad ideea to use public fields, tons of writings about this exist.

There are some very specific cases vhere it's legit to break the rule and use public fields, but those are again, very specific.
AnswerRe: Breaking guidelines? Pin
da5id29-Feb-08 8:02
da5id29-Feb-08 8:02 

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

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