Click here to Skip to main content
15,886,091 members
Articles / Mobile Apps / Windows Mobile
Article

Custom Data Binding Through Reflection

Rate me:
Please Sign up or sign in to vote.
4.45/5 (13 votes)
12 Apr 20052 min read 107.9K   918   53   16
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


Written By
Software Developer (Senior)
United States United States
Nick graduated from Iowa State University with a B.S. in Management Information System and a minor in Computer Science. Nick works for Zetetic.

Nick has also been involved with the Iowa .NET User Group since it's inception, in particular giving presentations over various .NET topics. Nick was awarded the Visual C# MVP award from Microsoft for four years in a row.

In his mystical spare time he is working on a development project called "DeveloperNotes" which integrates into Visual Studio .NET allowing developers easy access to common code pieces. He is also a fan of using dynamically typed languages to perform unit testing, not to mention how he loves to talk about himself in the third person.

Comments and Discussions

 
GeneralGreat article - from experience. Pin
Member 9617-May-06 10:13
Member 9617-May-06 10:13 
GeneralSimiliar problem or... Pin
RuneFS15-Jul-05 1:29
RuneFS15-Jul-05 1:29 
GeneralRe: Similiar problem or... Pin
Member 9617-May-06 10:15
Member 9617-May-06 10:15 
GeneralRe: Similiar problem or... Pin
RuneFS17-May-06 21:23
RuneFS17-May-06 21:23 
GeneralGood Article Pin
skarin13-Apr-05 4:37
skarin13-Apr-05 4:37 
GeneralRe: Good Article Pin
Nick Parker13-Apr-05 5:00
protectorNick Parker13-Apr-05 5:00 
GeneralMakes it easy, yes, BUT! Pin
Marc Scheuner12-Apr-05 19:22
professionalMarc Scheuner12-Apr-05 19:22 
GeneralRe: Makes it easy, yes, BUT! Pin
Nick Parker13-Apr-05 2:51
protectorNick Parker13-Apr-05 2:51 
GeneralRe: Makes it easy, yes, BUT! Pin
Marc Clifton13-Apr-05 5:15
mvaMarc Clifton13-Apr-05 5:15 
GeneralRe: Makes it easy, yes, BUT! Pin
Timothy Paul Narron20-Apr-05 16:29
Timothy Paul Narron20-Apr-05 16:29 
GeneralRe: Makes it easy, yes, BUT! Pin
MaksimP12-May-05 3:44
MaksimP12-May-05 3:44 
GeneralRe: Makes it easy, yes, BUT! Pin
RuneFS15-Jul-05 2:48
RuneFS15-Jul-05 2:48 
GeneralRe: Makes it easy, yes, BUT! Pin
Member 9617-May-06 9:54
Member 9617-May-06 9:54 
I must disagree vehemently with what you have written here.
I have authored a very large business application that I am now "refactoring" and one of the most important new elements I'm adding is attributes in fields for business collections that are used to suggest their display type to the ui level.

There are in fact many cases in the real world where it makes sense to put some information that is consumed by the UI layer in the business object layer.

For example we have business object collections that contain decimal values. These values may be currency or quantity. Using an attribute I can specify which they are so that at the UI level I can make a generic form that will work with many different business object collections, read through their attributes and modify a datagrid to display accordingly. I can also then create a web ui using the same business objects and method.

Another example are fields that should never be displayed. A perfect example is one where we have a column that displays in a datagrid in a user chosen status colour. One column contains the text of the status, another column contains the ARGB colour value to use for the status column. Using an attribute at the business object level I can specify that the status column should be displayed in colour and that the colour should come from teh ARGB field. I also set an attribute that the ARGB field is not user viewable directly so that it won't display in the grid.

At the UI level using reflection I can quickly determine this and display the grid properly.

Using this method I elminate a huge amount of UI code and programming and in effect I'm not really telling the UI how to do it's job from the business object, I'm merely specifying more detail about the data itself (i.e. it's a currency value or it's displayed in colour or never displayed etc).

Where else but in the business object itself is a better place to store this information? I have over 50 different objects that are business object collections, it certainly makes the most practical sense to have that information as close as possible to where it's defined which makes attributes ideal. Putting it elsewhere with that many objects is just a recipe for disaster and very time consuming maintenance.

It's easy to state that something shouldn't be done a certain way from an academic point of view, but from a pragmatic real world point of view there are many, many times when "rules" are broken for very good reasons.

-- modified at 15:55 Wednesday 17th May, 2006
GeneralNice Article Pin
Javier Lozano11-Apr-05 16:25
Javier Lozano11-Apr-05 16:25 
GeneralRe: Nice Article Pin
Nick Parker11-Apr-05 18:02
protectorNick Parker11-Apr-05 18:02 
GeneralRe: Nice Article Pin
BrickChen19-Apr-05 20:43
BrickChen19-Apr-05 20:43 

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.