Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Custom Data Binding Through Reflection

0.00/5 (No votes)
12 Apr 2005 2  
Using reflection and a custom attribute to bind data to UI elements.

Sample Image - data_binding_reflection.png

Introduction

Do you ever find yourself writing duplicate code mapping data objects to UI elements? There are several articles available on MSDN that cover different approaches to data binding, this article covers yet another possible solution to the problem. I am going to attack this problem through the use of reflection and a custom attribute.

Approach

The approach that I am making requires the usage of a custom attribute. This is simply a class that derives from Attribute and contains our specific usage characteristics. Specifically we will be applying this attribute to properties within data object classes alone. We actually apply an attribute to our derived attribute identifying that our custom attribute will only target properties, this is done through the AttributeUsage attribute. I have defined this custom attribute to be called UIMapAttribute. UIMapAttribute consists of two properties, Index which is an int and UIMap which is a bool. You can use the Boolean property to signify whether or not the specific property should be rendered within the UI element, and the Index property allows you to specify the order in which the elements should be rendered.

Implementation

In order to making sorting of the PropertyInfo array quick I wrote a class that implements IComparer so I can evaluate the Index property defined in each UIMapAttribute applied to our properties. In this example I am using a simple ArrayList to hold a collection of data objects. The Translate method does all the hard work. It begins by iterating over each object within the collection. Once I have an individual object, I ask for its properties, which return an array of PropertyInfo objects. I now need to order this array of PropertyInfo objects according to what was specified within its attribute, a quick call to Array.Sort passing my collection of properties from the specific object along with an instance of a compare class that implements IComparer performs the requested operation. Now I have an array of PropertyInfo objects that are sorted based on the criteria specified in the UIMapAttribute, I simply need to iterate over each PropertyInfo object, confirm that it�s contents need to be rendered to the UI element and continue to the next object. This specific example deals with the ListView control, however it wouldn�t take much to implement this on another type of UI control.  The following is what defines the UIMapAttribute class we apply to our properties.

[AttributeUsage(AttributeTargets.Property)]
public class UIMapAttribute : Attribute
{
    public UIMapAttribute(bool bMap)
    {    
        UIMap = bMap;
    }

    public UIMapAttribute(bool bMap, int index)
    {
        UIMap = bMap;
        Index = index;
    }

    private bool maps;
    private int index;
    public bool UIMap
    {
        get{return maps;}
        set{maps = value;}
    }

    public int Index
    {
        get{return index;}
        set{index = value;}
    }
}

Next we have our class that implements IComparer, this is what we will us to compare the Index property defined with our UIMapAttribute.

class UIMapComparer : IComparer
{
    public int Compare(object x, object y)
    {
        PropertyInfo pix = x as PropertyInfo;
        PropertyInfo piy = y as PropertyInfo;

        if(pix == null)
            return -1;

        if(piy == null)
            return 1;
        
        object[] ciaa1 = pix.GetCustomAttributes(typeof(UIMapAttribute), true);
        object[] ciaa2 = piy.GetCustomAttributes(typeof(UIMapAttribute), true);

        if(ciaa1 == null || ciaa1.Length == 0)
            return -1;
        
        if(ciaa2 == null || ciaa2.Length == 0)
            return 1;

        UIMapAttribute uim1 = ciaa1[0] as UIMapAttribute;
        if(uim1 == null)
            return -1;

        UIMapAttribute uim2 = ciaa2[0] as UIMapAttribute;
        if(uim2 == null)
            return 1;

        return uim1.Index.CompareTo(uim2.Index);
    }
}

And last is the Translate method in which we iterate over the objects and generate the ListViewItem array.

public static ListViewItem[] Translate(ArrayList collection)
{
	UIMapComparer comparer = new UIMapComparer();
	int count = collection.Count;
	ListViewItem[] lvia = new ListViewItem[count];
	for(int i = 0; i < count; i++)
	{
		object item = collection[i];
		if(item != null)
		{
			lvia[i] = new ListViewItem();
			PropertyInfo[] pia = item.GetType().GetProperties();
			
			// Sort properties

			Array.Sort(pia, comparer);
		
			if(pia != null)
			{
				object[] caa = pia[0].GetCustomAttributes(typeof(UIMapAttribute), true);
				if(caa != null && caa.Length > 0)
				{		
					UIMapAttribute uim = caa[0] as UIMapAttribute;
					if(uim != null  && uim.UIMap == true)
					{	
						// Set the Text Property of the ListViewItem

						object objText = pia[0].GetValue(item, null);
						lvia[i].Text = objText.ToString();
					}
				}

				// iterate over the remaining properties and set the SubItem

				if(pia.Length > 1)
				{
					for(int j = 1; j < pia.Length; j++)
					{
						object[] sicaa = pia[j].GetCustomAttributes(
										typeof(UIMapAttribute), true);
						if(sicaa != null && sicaa.Length > 0)
						{
							UIMapAttribute siUim = sicaa[0] as UIMapAttribute;
							if(siUim != null && siUim.UIMap == true)
							{
								object siText = pia[j].GetValue(item, null);
								if(siText != null)
								      lvia[i].SubItems.Add(siText.ToString());
							}
						}
					}
				}
				
			}
		}
	}
	return lvia;
}

If I change the class that I used in the above screenshot so the UIMapAttribute of the Price property is false, you will see that we no longer map the price into the ListViewItem's.

class food
{
	private string item;
	private string category;
	private double price;

	[UIMap(true, 1)]
	public string Item
	{
		get{return item;}
		set{item = value;}
	}

	[UIMap(true, 2)]
	public string Category
	{
		get{return category;}
		set{category = value;}
	}

	[UIMap(false, 3)]
	public double Price
	{
		get{return price;}
		set{price = value;}
	}
}

Please feel free to leave comments below.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here