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

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: