Click here to Skip to main content
6,293,171 members and growing! (11,910 online)
Email Password   helpLost your password?
Languages » C# » Attributes     Intermediate

Custom Data Binding Through Reflection

By Nick Parker

Using reflection and a custom attribute to bind data to UI elements.
C#, VC7.1, VB 6.NET 1.1, Win2K, WinXP, Win2003VS.NET2003, Dev
Posted:11 Apr 2005
Updated:12 Apr 2005
Views:56,691
Bookmarked:38 times
Announcements
Loading...
 
Search    
Advanced Search
printPrint   Broken Article?Report       add Share
  Discuss Discuss   Recommend Article Email
12 votes for this article.
Popularity: 4.69 Rating: 4.34 out of 5

1
1 vote, 8.3%
2
3 votes, 25.0%
3
2 votes, 16.7%
4
6 votes, 50.0%
5

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

About the Author

Nick Parker


Member
Nick graduated from Iowa State University with a B.S. in Management Information System and a minor in Computer Science. Nick works for GeoLearning.

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 recently awarded the Visual C# MVP award from Microsoft.

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.
Occupation: Software Developer (Senior)
Location: United States United States

Other popular C# articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 16 of 16 (Total in Forum: 16) (Refresh)FirstPrevNext
GeneralGreat article - from experience. PinsupporterJohn Cardinal11:13 17 May '06  
GeneralSimiliar problem or... Pinmemberrune@vrk.dk2:29 15 Jul '05  
GeneralRe: Similiar problem or... PinsupporterJohn Cardinal11:15 17 May '06  
GeneralRe: Similiar problem or... Pinmemberrune@vrk.dk22:23 17 May '06  
GeneralGood Article Pinmemberskarin5:37 13 Apr '05  
GeneralRe: Good Article PinprotectorNick Parker6:00 13 Apr '05  
GeneralMakes it easy, yes, BUT! PinmemberMarc Scheuner20:22 12 Apr '05  
GeneralRe: Makes it easy, yes, BUT! PinprotectorNick Parker3:51 13 Apr '05  
GeneralRe: Makes it easy, yes, BUT! PinsupporterMarc Clifton6:15 13 Apr '05  
GeneralRe: Makes it easy, yes, BUT! PinmemberTimothy Paul Narron17:29 20 Apr '05  
GeneralRe: Makes it easy, yes, BUT! PinmemberMaksimP4:44 12 May '05  
GeneralRe: Makes it easy, yes, BUT! Pinmemberrune@vrk.dk3:48 15 Jul '05  
GeneralRe: Makes it easy, yes, BUT! PinsupporterJohn Cardinal10:54 17 May '06  
GeneralNice Article PinmemberJavier Lozano17:25 11 Apr '05  
GeneralRe: Nice Article PinprotectorNick Parker19:02 11 Apr '05  
GeneralRe: Nice Article PinmemberBrickChen21:43 19 Apr '05  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 12 Apr 2005
Editor: Nick Parker
Copyright 2005 by Nick Parker
Everything else Copyright © CodeProject, 1999-2009
Web11 | Advertise on the Code Project