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();
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)
{
object objText = pia[0].GetValue(item, null);
lvia[i].Text = objText.ToString();
}
}
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.