Vishful thinking…

Simulating properties on a bindable list using ITypedList

Posted in .NET, C#, DataBinding by viswaug on August 29, 2007

In this blog post I am going to illustrate how to simulate properties on a bindable list using the ITypedList interface. The ITypedList interface is used by the .NET data-binding mechanism to discover the properties of the items in the list that is being bound. So in fact the ITypedList interface can be implemented by custom collections to customize the properties available for binding on the object to bind to. The .NET data-binding mechanisms use the GetItemProperties(…) method on the ITypedList interface to obtain a PropertyDescriptorCollection to obtain the list of properties available on the object in the list. So, the GetItemProperties(…) method can be overridden to return a custom collection of properties that will be used for data-binding.

Let us take for example, the ‘Distance’ class given below.

    public class Distance

    {

        double _tolerance = 0;

        esriUnits _units = esriUnits.esriFeet;

 

        public Distance(){}

 

        public Distance(double ptolerance, esriUnits punits)

        {

            _tolerance = ptolerance;

            _units = punits;

        }

 

        public double Tolerance

        {

            get { return _tolerance; }

            set { _tolerance = value; }

        }

 

        public esriUnits Units

        {

            get { return _units; }

            set { _units = value; }

        }

 

    }

 

The ‘Distance’ class above contains a tolerance measure and its units as it is public properties. Let’s say that we also have a typed collection ‘DistanceCollection’ of the ‘Distance’ objects.

    public class DistanceCollection

    {

 

        #region “CollectionBase Members”

 

        public Distance this[int index]

        {

            get

            {

                return ((Distance)List[index]);

            }

            set

            {

                List[index] = value;

            }

        }

 

        public int Add(Distance value)

        {

            return (List.Add(value));

        }

 

        public int IndexOf(Distance value)

        {

            return (List.IndexOf(value));

        }

 

        public void Insert(int index, Distance value)

        {

            List.Insert(index, value);

        }

 

        public void Remove(Distance value)

        {

            List.Remove(value);

        }

 

        public bool Contains(Distance value)

        {

            // If value is not of type Distance, this will return false.

            return (List.Contains(value));

        }

 

        protected override void OnInsert(int index, Object value)

        {

            // Insert additional code to be run only when inserting values.

            if (value.GetType() != typeof(Distance))

                throw new ArgumentException(“value must be of type Distance.”, “value”);

        }

 

        protected override void OnRemove(int index, Object value)

        {

            // Insert additional code to be run only when removing values.

            if (value.GetType() != typeof(Distance))

                throw new ArgumentException(“value must be of type Distance.”, “value”);

        }

 

        protected override void OnSet(int index, Object oldValue, Object newValue)

        {

            // Insert additional code to be run only when setting values.

            if (oldValue.GetType() != typeof(Distance))

                throw new ArgumentException(“oldValue must be of type Distance.”, “value”);

 

            if (newValue.GetType() != typeof(Distance))

                throw new ArgumentException(“newValue must be of type Distance.”, “value”);

        }

 

        protected override void OnValidate(Object value)

        {

            if (value.GetType() != typeof(Distance))

                throw new ArgumentException(“value must be of type Distance.”, “value”);

        }

 

        #endregion

 

    }

 

A new datasource could be added in Visual Studio for the ‘DistanceCollection’ type as shown below…

SimulateProperty1

SimulateProperty2

SimulateProperty3

SimulateProperty4

SimulateProperty5

SimulateProperty6

Our goal is to simulate a property called ‘ToleranceInMeters’ on the ‘DistanceCollection’ type so that we have an additional column on the DataGridView specifying the tolerance in meters. In order to achieve this we implement ITypedList on the ‘DistanceCollection’ type and override the GetItemProperties(…) method so that it returns a PropertyDescriptorCollection object with an added PropertyDescriptor object for the ‘ToleranceInMeters’ property.

To return an additional property in the PropertyDescriptorCollection that returns the tolerance in meters, we create a new class called ‘ToleranceInMetersPropertyDescriptor’. And in the GetValue(…) method of the new class we implement the logic to convert the tolerance value from the existing units into meters. In the below code, the ‘ToleranceInMetersPropertyDescriptor’ class has been implemented as an inner class inside ‘DistanceCollection’. We also build the new PropertyDescriptorCollection in the static constructor of ‘DistanceCollection’ and return it inside the GetItemProperties(…) method.

 

    public class DistanceCollection : CollectionBase, ITypedList

    {

 

        static DistanceCollection()

        {

            CalculatePropertyDescriptors();

        }

 

        #region “CollectionBase Members”

 

        public Distance this[int index]

        {

            get

            {

                return ((Distance)List[index]);

            }

            set

            {

                List[index] = value;

            }

        }

 

        public int Add(Distance value)

        {

            return (List.Add(value));

        }

 

        public int IndexOf(Distance value)

        {

            return (List.IndexOf(value));

        }

 

        public void Insert(int index, Distance value)

        {

            List.Insert(index, value);

        }

 

        public void Remove(Distance value)

        {

            List.Remove(value);

        }

 

        public bool Contains(Distance value)

        {

            // If value is not of type Distance, this will return false.

            return (List.Contains(value));

        }

 

        protected override void OnInsert(int index, Object value)

        {

            // Insert additional code to be run only when inserting values.

            if (value.GetType() != typeof(Distance))

                throw new ArgumentException(“value must be of type Distance.”, “value”);

        }

 

        protected override void OnRemove(int index, Object value)

        {

            // Insert additional code to be run only when removing values.

            if (value.GetType() != typeof(Distance))

                throw new ArgumentException(“value must be of type Distance.”, “value”);

        }

 

        protected override void OnSet(int index, Object oldValue, Object newValue)

        {

            // Insert additional code to be run only when setting values.

            if (oldValue.GetType() != typeof(Distance))

                throw new ArgumentException(“oldValue must be of type Distance.”, “value”);

 

            if (newValue.GetType() != typeof(Distance))

                throw new ArgumentException(“newValue must be of type Distance.”, “value”);

        }

 

        protected override void OnValidate(Object value)

        {

            if (value.GetType() != typeof(Distance))

                throw new ArgumentException(“value must be of type Distance.”, “value”);

        }

 

        #endregion

 

        #region “Simulated Property”

 

        private class ToleranceInMetersPropertyDescriptor : PropertyDescriptor

        {

            UnitConverterClass _converter = new UnitConverterClass();

 

            public ToleranceInMetersPropertyDescriptor(string name)

                : base(name, null)

            {

 

            }

 

            public override bool CanResetValue(object component)

            {

                return false;

            }

 

            public override System.Type ComponentType

            {

                get { return typeof(Distance); }

            }

 

            public override bool IsReadOnly

            {

                get { return true; }

            }

 

            public override System.Type PropertyType

            {

                get { return typeof(double); }

            }

 

            public override void ResetValue(object component)

            {

 

            }

 

            public override object GetValue(object component)

            {

 

                Distance d = (Distance)component;

                return _converter.ConvertUnits(d.Tolerance, d.Units, esriUnits.esriMeters);

            }

 

            public override void SetValue(object component, object value)

            {

 

            }

 

            public override bool ShouldSerializeValue(object component)

            {

                return false;

            }

        }

 

 

        #endregion

 

        #region ITypedList Members

 

 

        private static PropertyDescriptorCollection propertyDescriptors;

 

        PropertyDescriptorCollection ITypedList.GetItemProperties(PropertyDescriptor[] listAccessors)

        {

            return propertyDescriptors;

        }

 

        string ITypedList.GetListName(PropertyDescriptor[] listAccessors)

        {

            return “DistanceCollection”;

        }

 

        private static void CalculatePropertyDescriptors()

        {

            PropertyDescriptorCollection origProperties = TypeDescriptor.GetProperties(typeof(Distance));

            ArrayList properties = new ArrayList();

            foreach (PropertyDescriptor desc in origProperties)

            {

                properties.Add(desc);

            }

            properties.Add(new ToleranceInMetersPropertyDescriptor(“ToleranceInMeters”));

            propertyDescriptors = new PropertyDescriptorCollection((PropertyDescriptor[])properties.ToArray(typeof(PropertyDescriptor)));

        }

 

        #endregion

 

    }

 

What do you achieve by all this? Well, we get a property on the ‘DistanceCollection’ type in the Data Sources window in Visual Studio 2005 and when it is dragged on to the form the DataGridView now appears with a an additional column called ‘ToleranceInMeters’.

SimulateProperty7

In the constructor of the form, initialize the binding source for the DistanceCollection and the result will be what you see below.

 

DistanceCollection _Coll = new DistanceCollection();

_Coll.Add(new Distance(1, ESRI.ArcGIS.esriSystem.esriUnits.esriFeet));

_Coll.Add(new Distance(1, ESRI.ArcGIS.esriSystem.esriUnits.esriInches));

_Coll.Add(new Distance(1, ESRI.ArcGIS.esriSystem.esriUnits.esriKilometers));

_Coll.Add(new Distance(1, ESRI.ArcGIS.esriSystem.esriUnits.esriMiles));

_Coll.Add(new Distance(1, ESRI.ArcGIS.esriSystem.esriUnits.esriNauticalMiles));

_Coll.Add(new Distance(1, ESRI.ArcGIS.esriSystem.esriUnits.esriDecimalDegrees));

distanceCollectionBindingSource.DataSource = _Coll;

SimulateProperty8

Advertisements

Databinding Enum values to combo boxes

Posted in .NET, C#, DataBinding by viswaug on June 13, 2007

Even though I dislike the use of enumerations, there are cases where you can use them to make your life a lot easier. In those cases, there might be a requirement to have the enumeration property data bound to a combo box which lists all possible values of the enumeration in some meaningful text and also be able to select the right enumeration text when the object is bound to it. I have written a small little utility that will do just that. It returns a Read-Only collection with some meaningful text as the “Name” and the enum value as the “Value” member of the collection. In the sample, the generic class EnumCollection<T> has a static DataSource property that returns a ReadOnlyICollection that is data bindable to a combo box. The type T represents an enumeration type. The text to describe the enumeration values can be added as custom attribute decorators in the definition of the enumeration as below.

 

1 public enum EnumSample

2 {

3 [Text(“This is One.”)]

4 One,

5 [Text(“This is Two.”)]

6 Two,

7 [Text(“This is Three.”)]

8 Three,

9 [Text(“This is Four.”)]

10 Four

11 }

 

The EnumCollection<T> class creates its collection in a static constructor for the generic class and thus is able to return the required collection in a static “DataSource” property. The definition of the EnumCollection<T> class is below.

 

1 public class EnumCollection<T> : ReadOnlyCollection<EnumClass<T>>

2 {

3 static EnumCollection<T> _val;

4

5 static EnumCollection()

6 {

7 if (!typeof(T).IsEnum)

8 throw new InvalidCastException(“sbEnumCollection only supports ‘enum’ types.”);

9

10 List<EnumClass<T>> vals = new List<EnumClass<T>>();

11 Array values = Enum.GetValues(typeof(T));

12 foreach (Object item in values)

13 {

14 EnumClass<T> entry = new EnumClass<T>(GetText<T>(item), (T)item);

15 vals.Add(entry);

16 }

17 _val = new EnumCollection<T>(vals);

18 }

19

20 private static string GetText<U>(object item) where U : T

21 {

22 if (!typeof(U).IsEnum)

23 throw new InvalidCastException(“GetText only supports ‘enum’ types.”);

24

25 FieldInfo fi = typeof(U).GetField(item.ToString());

26 string result = null;

27 if (fi != null)

28 {

29 object[] attrs = fi.GetCustomAttributes(typeof(TextAttribute), true);

30 if (attrs != null && attrs.Length > 0)

31 result = ((TextAttribute)attrs[0]).Text;

32 }

33

34 if (result == null)

35 result = Enum.GetName(typeof(T), item);

36

37 return result;

38 }

39

40 internal EnumCollection(IList<EnumClass<T>> list) : base(list) { }

41

42 public static EnumCollection<T> DataSource

43 {

44 get

45 {

46 return _val;

47 }

48 }

49 }

 

Enumerations can be data bound to combo boxes easily as shown below.

 

 

1 comboBox1.DataSource = EnumCollection<EnumSample>.DataSource;

2 comboBox1.DisplayMember = “Name”;

3 comboBox1.ValueMember = “Value”;

 

The complete project can be downloaded here.