PropertyEditors causes errors, ICustomTypeDescriptor problem

Grids for WPF Forum

Posted 11 years ago by Aleksander Brzozowski
Version: 4.5.0486
Platform: .NET 3.5
Environment: Windows Vista (32-bit)
Avatar
Hi

For example, we have list with objects (different classes) with different properties.
To show properties in PropertyGrid we use PropertyEditors. Every time SelectedObject changed we must clear propertyEditors and add new for edited object.It causes that GetProperties(Attribute[] attributes) function from ICustomTypeDescriptor is called many times in different threads. If our function GetProperties returns TypeDescriptor.GetProperties(this, attributes, true) everything is ok, but when we override GetProperties and do something in this function(what get some time) property grid doesnt refresh correct selected object.

In addition we can get error "called thread cannot access to other thread", as I wrote in previous topic. Bellow is sample.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Threading;
using ActiproSoftware.Windows.Controls.PropertyGrid;
using ActiproSoftware.Windows.Controls.PropertyGrid.Editors;
using ActiproSoftware.Windows.Controls.PropertyGrid.Primitives;

namespace PropertyGridBugApp
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public static ListBox LogBox;
        public Window1()
        {
            InitializeComponent();
            DataTemplate template = new DataTemplate();
            template.VisualTree = new FrameworkElementFactory(typeof(SomeClassTemplate), "fef");
            lbList.ItemTemplate = template;
            List<object> list = new List<object>();
            for (int i = 0; i < 5; i++)
            {
                SomeClass someObject = new SomeClass();
                someObject.PropertyA = i;
                someObject.PropertyB = (decimal)i / 2;
                someObject.PropertyC = "Object " + i.ToString();
                list.Add(someObject);
            }
            for (int i = 0; i < 5; i++)
            {
                OtherClass someObject = new OtherClass();
                someObject.PropertyA = DateTime.Now;
                someObject.PropertyC = "Other Object " + i.ToString();
                list.Add(someObject);
            }
            for (int i = 5; i < 10; i++)
            {
                SomeClass someObject = new SomeClass();
                someObject.PropertyA = i;
                someObject.PropertyB = (decimal)i / 2;
                someObject.PropertyC = "Object " + i.ToString();
                list.Add(someObject);
            }
            lbList.ItemsSource = list;
            lbList.SelectionChanged += new SelectionChangedEventHandler(lbList_SelectionChanged);
            LogBox = log;
            LogBox.Items.Add("Main Thread Id = " + Thread.CurrentThread.ManagedThreadId.ToString());
        }

        void ClearAndLoadPropertyEditors(object obj)
        {
            pg.PropertyEditors.Clear();
            pg.CategoryEditors.Clear();
            if (obj is SomeClass)
            {
                PropertyEditor pe1 = new PropertyEditor();
                pe1.PropertyName = "PropertyA";
                pe1.PropertyType = typeof(int);
                pe1.ValueTemplate = new DataTemplate();
                pe1.ValueTemplate.VisualTree = new FrameworkElementFactory(typeof(Property1Template), "p1");

                PropertyEditor pe2 = new PropertyEditor();
                pe2.PropertyName = "PropertyB";
                pe2.PropertyType = typeof(decimal);
                pe2.ValueTemplate = new DataTemplate();
                pe2.ValueTemplate.VisualTree = new FrameworkElementFactory(typeof(Property2Template), "p2");

                PropertyEditor pe3 = new PropertyEditor();
                pe3.PropertyName = "PropertyC";
                pe3.PropertyType = typeof(string);
                pe3.ValueTemplate = new DataTemplate();
                pe3.ValueTemplate.VisualTree = new FrameworkElementFactory(typeof(Property3Template), "p3");

                pg.PropertyEditors.Add(pe1);
                pg.PropertyEditors.Add(pe2);
                pg.PropertyEditors.Add(pe3);
            }
            else
            {
                PropertyEditor pe3 = new PropertyEditor();
                pe3.PropertyName = "PropertyC";
                pe3.PropertyType = typeof(string);
                pe3.ValueTemplate = new DataTemplate();
                pe3.ValueTemplate.VisualTree = new FrameworkElementFactory(typeof(Property3Template), "p3");

                pg.PropertyEditors.Add(pe3);
            }
        }

        bool PropertyContained(string PropertyName)
        {
            for (int i = 0; i < pg.PropertyEditors.Count; i++)
            {
                if (pg.PropertyEditors[i].PropertyName.Equals(PropertyName)) return true;
            }
            return false;
        }

        void LoadPropertyEditorsIfNotContained(object obj)
        {
            //pg.PropertyEditors.Clear();
            //pg.CategoryEditors.Clear();
            if (obj is SomeClass)
            {
                PropertyEditor pe1 = new PropertyEditor();
                pe1.PropertyName = "PropertyA";
                pe1.PropertyType = typeof(int);
                pe1.ValueTemplate = new DataTemplate();
                pe1.ValueTemplate.VisualTree = new FrameworkElementFactory(typeof(Property1Template), "p1");

                PropertyEditor pe2 = new PropertyEditor();
                pe2.PropertyName = "PropertyB";
                pe2.PropertyType = typeof(decimal);
                pe2.ValueTemplate = new DataTemplate();
                pe2.ValueTemplate.VisualTree = new FrameworkElementFactory(typeof(Property2Template), "p2");

                PropertyEditor pe3 = new PropertyEditor();
                pe3.PropertyName = "PropertyC";
                pe3.PropertyType = typeof(string);
                pe3.ValueTemplate = new DataTemplate();
                pe3.ValueTemplate.VisualTree = new FrameworkElementFactory(typeof(Property3Template), "p3");

                
                if (!PropertyContained(pe1.PropertyName)) pg.PropertyEditors.Add(pe1);
                if (!PropertyContained(pe2.PropertyName)) pg.PropertyEditors.Add(pe2);
                if (!PropertyContained(pe3.PropertyName)) pg.PropertyEditors.Add(pe3);
            }
            else
            {
                PropertyEditor pe3 = new PropertyEditor();
                pe3.PropertyName = "PropertyC";
                pe3.PropertyType = typeof(string);
                pe3.ValueTemplate = new DataTemplate();
                pe3.ValueTemplate.VisualTree = new FrameworkElementFactory(typeof(Property3Template), "p3");

                if (!PropertyContained(pe3.PropertyName)) pg.PropertyEditors.Add(pe3);
            }
        }

        void lbList_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            LoadPropertyEditorsIfNotContained(e.AddedItems[0]);
            //ClearAndLoadPropertyEditors(e.AddedItems[0]); //doesnt work
            pg.SelectedObject = lbList.SelectedValue;
        }
    }

    class SomeClass : ICustomTypeDescriptor
    {
        private int a;
        private decimal b;
        private string s;

        public int PropertyA
        {
            set { this.a = value; }
            get { return this.a; }
        }

        public decimal PropertyB
        {
            set { this.b = value; }
            get { return this.b; }
        }

        public string PropertyC
        {
            set { this.s = value; }
            get { return this.s; }
        }

        #region ICustomTypeDescriptor members

        public String GetClassName()
        {
            return TypeDescriptor.GetClassName(this, true);
        }

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

        public String GetComponentName()
        {
            return TypeDescriptor.GetComponentName(this, true);
        }

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

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

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

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

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

        public EventDescriptorCollection GetEvents()
        {
            return TypeDescriptor.GetEvents(this, true);
        }

        void ShowLog(string log)
        {
            Window1.LogBox.Items.Insert(0, log);
        }

        public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
        {
            LogHandler lh = new LogHandler(ShowLog);
            Window1.LogBox.Dispatcher.BeginInvoke(lh, "Get Properties in Thread id = " + Thread.CurrentThread.ManagedThreadId.ToString());
            Thread.Sleep(80); // in thisplace could be function which do something (for example prepare properties)
            return TypeDescriptor.GetProperties(this, attributes, true);
        }

        public PropertyDescriptorCollection GetProperties()
        {
            return TypeDescriptor.GetProperties(this, true);
        }

        public object GetPropertyOwner(PropertyDescriptor pd)
        {
            return this;
        }

        #endregion
    }

    class OtherClass : ICustomTypeDescriptor
    {
        private DateTime dt;
        private string s;

        public DateTime PropertyA
        {
            set { this.dt = value; }
            get { return this.dt; }
        }

        public string PropertyC
        {
            set { this.s = value; }
            get { return this.s; }
        }

        #region ICustomTypeDescriptor members

        public String GetClassName()
        {
            return TypeDescriptor.GetClassName(this, true);
        }

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

        public String GetComponentName()
        {
            return TypeDescriptor.GetComponentName(this, true);
        }

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

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

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

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

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

        public EventDescriptorCollection GetEvents()
        {
            return TypeDescriptor.GetEvents(this, true);
        }

        public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
        {
            LogHandler lh = new LogHandler(ShowLog);
            Window1.LogBox.Dispatcher.BeginInvoke(lh, "Get Properties in Thread id = " + Thread.CurrentThread.ManagedThreadId.ToString());
            Thread.Sleep(50); // in thisplace could be function which do something (for example prepare properties)
            return TypeDescriptor.GetProperties(this, attributes, true);
        }

        public PropertyDescriptorCollection GetProperties()
        {
            return TypeDescriptor.GetProperties(this, true);
        }

        public object GetPropertyOwner(PropertyDescriptor pd)
        {
            return this;
        }

        void ShowLog(string log)
        {
            Window1.LogBox.Items.Insert(0, log);
        }

        #endregion
    }

    public delegate void LogHandler(string log);
}


I can send You all sample if You want.

Best regards

Alex

Comments (2)

Posted 11 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Aleksander,

Everytime you add or remove a property editor, or set the SelectedObject, the PropertyGrid will refresh the items displayed. Since you are clearing the property editors (1), adding one to three properties (2-4), and then setting the selected object, you will get 3 or 5 refreshes. You can batch updates by wrapping changes in PropertyGrid.BeginUpdate/EndUpdate calls. So in your lbList_SelectionChanged method, simply add code like this:
pg.BeginUpdate();
try {
    // ... existing code ....
}
finally {
    pg.EndUpdate();
}
This fixes the issue where the selected object is not updating correctly also.


Actipro Software Support

Posted 11 years ago by Aleksander Brzozowski
Avatar
Thanks for help,
It works
The latest build of this product (v2019.1 build 0683) was released 1 month ago, which was after the last post in this thread.

Add Comment

Please log in to a validated account to post comments.