This project has moved and is read-only. For the latest updates, please go here.

PropertyGrid with object implementing ICustomTypeConverter to add extra properties

Aug 25, 2015 at 5:43 PM
If you utilise ICustomTypeConverter to add extra properties on an object, the PropertyGrid does not correctly use ICustomTypeConverter.GetPropertyOwner(). I believe that this is because in CreatePropertyItem() assumes SelectedObject is the correct instance, where it should be requesting the object instance using code similar to:
        public object GetValueInstance(object sourceObject)
        {
            ICustomTypeDescriptor customTypeDescriptor = sourceObject as ICustomTypeDescriptor;
            if (customTypeDescriptor != null)
                sourceObject = customTypeDescriptor.GetPropertyOwner(PropertyDescriptor);

            return sourceObject;
        }
For example, if you have the following the property grid will generate an error:
    public class TestSubClass
    {
        public string ExtendedField { get; set; }
    }

    public class TestClass : ICustomTypeDescriptor
    {
        public TestClass()
        {

        }
        public string Name { get; set; }

        [Browsable(false)]
        public TestSubClass SubInfo { get; set; }

        #region ICustomTypeDescriptor implementation

        #region Standard implementation

        AttributeCollection ICustomTypeDescriptor.GetAttributes()
        {
            return TypeDescriptor.GetAttributes(this, true);
        }

        string ICustomTypeDescriptor.GetClassName()
        {
            return TypeDescriptor.GetClassName(this, true);
        }

        string ICustomTypeDescriptor.GetComponentName()
        {
            return TypeDescriptor.GetComponentName(this, true);
        }

        TypeConverter ICustomTypeDescriptor.GetConverter()
        {
            return TypeDescriptor.GetConverter(this, true);
        }

        EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
        {
            return TypeDescriptor.GetDefaultEvent(this, true);
        }

        PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
        {
            return TypeDescriptor.GetDefaultProperty(this, true);
        }

        object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
        {
            return TypeDescriptor.GetEditor(this, editorBaseType, true);
        }

        EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
        {
            return TypeDescriptor.GetEvents(this, attributes, true);
        }

        EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
        {
            return TypeDescriptor.GetEvents(this, true);
        }
        #endregion

        #region Custom implmenetation
        PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
        {
            return GetCombinedProperties(attributes);
        }

        PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
        {
            return GetCombinedProperties(null);
        }

        PropertyDescriptorCollection GetCombinedProperties(Attribute[] attributes)
        {
            PropertyDescriptorCollection ourPropsCollection = TypeDescriptor.GetProperties(this, attributes, true);
            List<PropertyDescriptor> ourProps = new List<PropertyDescriptor>();
            foreach (PropertyDescriptor prop in ourPropsCollection)
                ourProps.Add(prop);

            if (SubInfo == null)
                SubInfo = new TestSubClass();

            if (SubInfo != null)
            {
                PropertyDescriptorCollection setProps = TypeDescriptor.GetProperties(SubInfo, attributes, true);
                if (setProps != null)
                {
                    foreach (PropertyDescriptor prop in setProps)
                    {
                       ourProps.Add(prop);
                    }
                }
            }
            return new PropertyDescriptorCollection(ourProps.ToArray());
        }

        object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
        {
            PropertyDescriptorCollection props = TypeDescriptor.GetProperties(this, true);
            if (props.Contains(pd))
                return this;
            else
                return SubInfo;
        }
        #endregion

        #endregion ICustomTypeDescriptor implementation
    }
Developer
Aug 25, 2015 at 8:34 PM
Hi,

Are you talking about ICustomTypeConverter or ICustomTypeDescriptor ?
Can you submit a complete sample of the propertyGrid generating an error ?
What is the error ?
What version of the Toolkit are you using ?

Thanks.
Aug 25, 2015 at 10:28 PM
I do apologise for the confusion. I actually mean the ICustomTypeDescriptor as per the above class. The class file I have attached to the issue I raised after realising I'd created a discussion http://wpftoolkit.codeplex.com/workitem/21986

This is against the community edition that I downloaded the source to in order to diagnose the error. I can get the fully exception which is trapped by a catch of the PropertyGrid and written using Debug.WriteLine if I recall.

To test, just create the test class and set SelectedObject to the created object on a PropertyGrid under WPF.
Developer
Aug 26, 2015 at 2:56 PM
Hi,

Using your TestClass, here is the code I used :
<Grid>
      <xctk:PropertyGrid x:Name="_propertyGrid" />
</Grid>

 public MainWindow()
    {
      InitializeComponent();

      _propertyGrid.SelectedObject = new TestClass()
      {
        Name = "Testing Name",
        SubInfo = new TestSubClass()
        {
          ExtendedField = "Testing Extended field"
        }
      };
    }
The propertyGrid loads normally without any error. Version used was Toolkit v2.5 under Windows 8.1.
Please create a sample project with the error,
Thanks.
Aug 27, 2015 at 1:31 PM
OK, It would appear I have given you the earlier version of TestClass that didn't cause the problem because it wasn't using INotifyPropertyChanged handling that the one below does use. It would appear that it is when you are creating the binding to the value so I am guessing it must be using one way to source binding without the PropertyChanged event rather than TwoWay. (Note, I can't see how to add a file on this discussion ... presume there isn't that facility).

Code being used, xaml:
    <Grid>
        <Grid.DataContext>
            <local:TestClass />
        </Grid.DataContext>
        <xctk:PropertyGrid SelectedObject="{Binding}" />
    </Grid>
Latest version of testclass.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;

 public class TestSubData : INotifyPropertyChanged
    {
        public TestSubData()
        {
        }

        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        public event PropertyChangedEventHandler PropertyChanged;


        private string mExtendedField;
        public string ExtendedField
        {
            get { return mExtendedField; }
            set
            {
                if (value != mExtendedField)
                {
                    mExtendedField = value;
                    OnPropertyChanged("ExtendedField");
                }
            }
        }
    }

    public class TestClass : ICustomTypeDescriptor
    {
        public TestClass()
        {
            SubInfo = new TestSubData();
            this.Name = "My Name " + (new Random((int)DateTime.Now.Ticks).Next()).ToString();
            this.SubInfo.ExtendedField = Name.Replace("Name", "Exended Value");
        }
        public string Name { get; set; }

        [Browsable(false)]
        public TestSubData SubInfo { get; set; }

        #region ICustomTypeDescriptor implementation

        #region Standard implementation

        AttributeCollection ICustomTypeDescriptor.GetAttributes()
        {
            return TypeDescriptor.GetAttributes(this, true);
        }

        string ICustomTypeDescriptor.GetClassName()
        {
            return TypeDescriptor.GetClassName(this, true);
        }

        string ICustomTypeDescriptor.GetComponentName()
        {
            return TypeDescriptor.GetComponentName(this, true);
        }

        TypeConverter ICustomTypeDescriptor.GetConverter()
        {
            return TypeDescriptor.GetConverter(this, true);
        }

        EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
        {
            return TypeDescriptor.GetDefaultEvent(this, true);
        }

        PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
        {
            return TypeDescriptor.GetDefaultProperty(this, true);
        }

        object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
        {
            return TypeDescriptor.GetEditor(this, editorBaseType, true);
        }

        EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
        {
            return TypeDescriptor.GetEvents(this, attributes, true);
        }

        EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
        {
            return TypeDescriptor.GetEvents(this, true);
        }
        #endregion

        #region Custom implmenetation
        PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
        {
            return GetCombinedProperties(attributes);
        }

        PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
        {
            return GetCombinedProperties(null);
        }

        PropertyDescriptorCollection GetCombinedProperties(Attribute[] attributes)
        {
            PropertyDescriptorCollection ourPropsCollection = TypeDescriptor.GetProperties(this, attributes, true);
            List<PropertyDescriptor> ourProps = new List<PropertyDescriptor>();
            foreach (PropertyDescriptor prop in ourPropsCollection)
                ourProps.Add(prop);

            if (SubInfo == null)
                SubInfo = new TestSubData();

            if (SubInfo != null)
            {
                PropertyDescriptorCollection setProps = TypeDescriptor.GetProperties(SubInfo, attributes, true);
                if (setProps != null)
                {
                    foreach (PropertyDescriptor prop in setProps)
                    {
                        try
                        {
                            ourProps.Add(prop);
                        }
                        catch (Exception ex)
                        {
                            if (ex == null)
                                throw;
                        }
                    }
                }
            }
            return new PropertyDescriptorCollection(ourProps.ToArray());
        }

        object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
        {
            PropertyDescriptorCollection props = TypeDescriptor.GetProperties(this, true);
            if (props.Contains(pd))
                return this;
            else
                return SubInfo;
        }
        #endregion

        #endregion ICustomTypeDescriptor implementation
    }
Aug 27, 2015 at 1:40 PM
My fix for this was to add the following code to DescriptorPropertyDefinition:
        public object GetValueInstance(object sourceObject)
        {
            ICustomTypeDescriptor customTypeDescriptor = sourceObject as ICustomTypeDescriptor;
            if (customTypeDescriptor != null)
                sourceObject = customTypeDescriptor.GetPropertyOwner(PropertyDescriptor);

            return sourceObject;
        }
Then I updated the CreateBinding() method to be:
        protected override BindingBase CreateValueBinding()
        {
            //Bind the value property with the source object.
            var binding = new Binding(PropertyDescriptor.Name)
            {
                Source = GetValueInstance(SelectedObject),
                Mode = PropertyDescriptor.IsReadOnly ? BindingMode.OneWay : BindingMode.TwoWay,
                ValidatesOnDataErrors = true,
                ValidatesOnExceptions = true
            };

            return binding;
        }
Aug 27, 2015 at 1:44 PM
P.S. I have just managed to update a test project that has the above two bits of code in it. You will need to link the toolkit project or fix up the reference to make it work.