Home › Forums › WPF controls › Other WPF controls › PropertyGrid : Dictionary Not Displaying Values using ICustomTypeDescriptor
-
AuthorPosts
-
#44724 |
Currently I can only get the names of the dynamic properties to display not the values, the values result in the follow errors:
System.Windows.Data Error: 40 : BindingExpression path error: 'Test' property not found on 'object' ''ConcurrentDictionary<code>2' (HashCode=47097466)'. BindingExpression:Path=Test.With.Dots; DataItem='ConcurrentDictionary</code>2' (HashCode=47097466); target element is 'DescriptorPropertyDefinition' (HashCode=52822235); target property is 'Value' (type 'Object') System.Windows.Data Error: 40 : BindingExpression path error: 'Test2' property not found on 'object' ''ConcurrentDictionary<code>2' (HashCode=47097466)'. BindingExpression:Path=Test2.With.Dots; DataItem='ConcurrentDictionary</code>2' (HashCode=47097466); target element is 'DescriptorPropertyDefinition' (HashCode=57448811); target property is 'Value' (type 'Object') System.Windows.Data Error: 40 : BindingExpression path error: 'Test' property not found on 'object' ''ConcurrentDictionary<code>2' (HashCode=47097466)'. BindingExpression:Path=Test; DataItem='ConcurrentDictionary</code>2' (HashCode=47097466); target element is 'DescriptorPropertyDefinition' (HashCode=25770419); target property is 'Value' (type 'Object') System.Windows.Data Error: 40 : BindingExpression path error: 'Test2' property not found on 'object' ''ConcurrentDictionary<code>2' (HashCode=47097466)'. BindingExpression:Path=Test2; DataItem='ConcurrentDictionary</code>2' (HashCode=47097466); target element is 'DescriptorPropertyDefinition' (HashCode=24589509); target property is 'Value' (type 'Object')
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit" Title="MainWindow" Width="525" Height="350" mc:Ignorable="d"> <Grid> <xctk:PropertyGrid x:Name="PropertyGrid" /> </Grid> </Window>
MainWindow.xaml.cs
namespace WpfApplication1 { using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Windows; /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { #region Constructors and Destructors public MainWindow() { this.InitializeComponent(); this.Variables["Test"] = false; this.Variables["Test2"] = 200; this.Variables["Test.With.Dots"] = 200.5; this.Variables["Test2.With.Dots"] = "help"; this.PropertyGrid.SelectedObject = new DictionaryPropertyGridAdapter<string, object>(this.Variables); } #endregion public IDictionary<string, object> Variables { get; set; } = new ConcurrentDictionary<string, object>(); } }
DictionaryPropertyGridAdapter.cs
namespace WpfApplication1 { using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Reflection; using System.Runtime.CompilerServices; [RefreshProperties(RefreshProperties.All)] public class DictionaryPropertyGridAdapter<T, U> : ICustomTypeDescriptor, INotifyPropertyChanged { #region Fields private readonly IDictionary<T, U> dictionary; #endregion #region Constructors and Destructors public DictionaryPropertyGridAdapter(IDictionary<T, U> dictionary) { this.dictionary = dictionary; } #endregion #region Events public event PropertyChangedEventHandler PropertyChanged; #endregion [Browsable(false)] public U this[T key] { get { return this.dictionary[key]; } set { this.dictionary[key] = value; } } public AttributeCollection GetAttributes() { return TypeDescriptor.GetAttributes(this, true); } public string GetClassName() { return TypeDescriptor.GetClassName(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 null; } public object GetEditor(Type editorBaseType) { return TypeDescriptor.GetEditor(this, editorBaseType, true); } public EventDescriptorCollection GetEvents(Attribute[] attributes) { return TypeDescriptor.GetEvents(this, attributes, true); } public PropertyDescriptorCollection GetProperties(Attribute[] attributes) { ArrayList properties = new ArrayList(); foreach (var e in this.dictionary) { properties.Add(new DictionaryPropertyDescriptor(this.dictionary, e.Key)); } PropertyDescriptor[] props = (PropertyDescriptor[])properties.ToArray(typeof(PropertyDescriptor)); return new PropertyDescriptorCollection(props); } public object GetPropertyOwner(PropertyDescriptor pd) { return this.dictionary; } EventDescriptorCollection ICustomTypeDescriptor.GetEvents() { return TypeDescriptor.GetEvents(this, true); } PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties() { return ((ICustomTypeDescriptor)this).GetProperties(new Attribute[0]); } protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } public class DictionaryPropertyDescriptor : PropertyDescriptor { #region Fields private readonly IDictionary<T, U> dictionary; private readonly T key; #endregion #region Constructors and Destructors internal DictionaryPropertyDescriptor(IDictionary<T, U> dictionary, T key) : base(key.ToString(), null) { this.dictionary = dictionary; this.key = key; } #endregion public override Type ComponentType => null; public override bool IsReadOnly => false; public override Type PropertyType => this.dictionary[this.key].GetType(); public override bool CanResetValue(object component) { return false; } public override object GetValue(object component) { return this.dictionary[this.key]; } public override void ResetValue(object component) { } public override void SetValue(object component, object value) { this.dictionary[this.key] = (U)value; } public override bool ShouldSerializeValue(object component) { return false; } } } }
Hi,
Your code will work if you modify your “object GetPropertyOwner( PropertyDescriptor pd )” method to return “this” instead of “this.dictionary”. The indexer of this class already return the dictionary wanted key value.
When the PropertyGrid tries to do a binding between DescriptorPropertyDefinitionBase.Value and a SelectedObject.(PropertyItem.Name) (read DictionaryPropertyGridAdapter.Test when GetPropertyOwner returns this) it will work because DictionaryPropertyGridAdapter will return dictionary[“Test”].
When the PropertyGrid tries to do a binding between DescriptorPropertyDefinitionBase.Value and a SelectedObject.(PropertyItem.Name) (read dictionary.Test when GetPropertyOwner returns dictionary) it will not work because Test is not a property from dictionary.
Just make sure to not use dots in your dictionary indexes. With an index like “Test.With.Dots”, the binding will try to search for a “With” in object “Test” and won’t find it.
Thanks that worked. Now I just need to see if I can set attributes on these so I can change the display names to include dots as that’s what my variables are known to be named as.
Hi Krush
How did you do that dot notation in dictionary keys.. i ran into same situation…
eg: this.Variables[“Test.With.Dots”] = 200.5;
Thanks in Advance
I added:
public override string DisplayName => this.attributeDictionary?[this.key].DisplayName ?? base.DisplayName;
to DictionaryPropertyDescriptor which allows me to set the displayed named differently than the actual name.
For example:
this.Variables[“Test.With.Dots”] = 200.5;
becomesthis.Variables[“Test_With_Dots”] = 200.5;
I made a helper that does this:
private void AddVariable( string name, object value, string category = null, string displayName = null, string description = null) { this.variables[name] = value; this.variableAttributes[name] = new DictionaryPropertyGridAdapter<string, object>.PropertyAttributes { Category = category ?? "", DisplayName = displayName ?? "", Description = description ?? "", IsReadOnly = false }; }
And here’s my updated DictionaryPropertyGridAdapter.cs
// -------------------------------------------------------------------------------------------------------------------- // <copyright file="DictionaryPropertyGridAdapter.cs" company="Lost Minions"> // Copyright (c) Lost Minions. All rights reserved. // </copyright> // <summary> // Defines the StressVariables type. // </summary> // -------------------------------------------------------------------------------------------------------------------- namespace EmptyWPF_CSharp.Utility { using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Runtime.CompilerServices; using EmptyWPF_CSharp.Properties; [RefreshProperties(RefreshProperties.All)] public class DictionaryPropertyGridAdapter<TKey, TValue> : ICustomTypeDescriptor, INotifyPropertyChanged { #region Fields private readonly IDictionary<TKey, PropertyAttributes> propertyAttributeDictionary; private readonly IDictionary<TKey, TValue> propertyValueDictionary; #endregion #region Constructors and Destructors public DictionaryPropertyGridAdapter( IDictionary<TKey, TValue> propertyValueDictionary, IDictionary<TKey, PropertyAttributes> propertyAttributeDictionary = null) { this.propertyValueDictionary = propertyValueDictionary; this.propertyAttributeDictionary = propertyAttributeDictionary; } #endregion #region Events public event PropertyChangedEventHandler PropertyChanged; #endregion public AttributeCollection GetAttributes() { return TypeDescriptor.GetAttributes(this, true); } public string GetClassName() { return TypeDescriptor.GetClassName(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 null; } public object GetEditor(Type editorBaseType) { return TypeDescriptor.GetEditor(this, editorBaseType, true); } public EventDescriptorCollection GetEvents(Attribute[] attributes) { return TypeDescriptor.GetEvents(this, attributes, true); } public PropertyDescriptorCollection GetProperties(Attribute[] attributes) { ArrayList properties = new ArrayList(); foreach (var kvp in this.propertyValueDictionary) { properties.Add( new DictionaryPropertyDescriptor( kvp.Key, this.propertyValueDictionary, this.propertyAttributeDictionary)); } PropertyDescriptor[] props = (PropertyDescriptor[])properties.ToArray(typeof(PropertyDescriptor)); return new PropertyDescriptorCollection(props); } public object GetPropertyOwner(PropertyDescriptor pd) { return this; } EventDescriptorCollection ICustomTypeDescriptor.GetEvents() { return TypeDescriptor.GetEvents(this, true); } PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties() { return ((ICustomTypeDescriptor)this).GetProperties(new Attribute[0]); } [NotifyPropertyChangedInvocator] protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } public class PropertyAttributes { public string Category { get; set; } public string Description { get; set; } public string DisplayName { get; set; } public bool IsReadOnly { get; set; } } internal class DictionaryPropertyDescriptor : PropertyDescriptor { #region Fields private readonly IDictionary<TKey, PropertyAttributes> attributeDictionary; private readonly TKey key; private readonly IDictionary<TKey, TValue> valueDictionary; #endregion #region Constructors and Destructors internal DictionaryPropertyDescriptor( TKey key, IDictionary<TKey, TValue> valueDictionary, IDictionary<TKey, PropertyAttributes> attributeDictionary = null) : base(key.ToString(), null) { this.valueDictionary = valueDictionary; this.attributeDictionary = attributeDictionary; this.key = key; } #endregion public override string Category => this.attributeDictionary?[this.key].Category ?? base.Category; public override Type ComponentType => null; public override string Description => this.attributeDictionary?[this.key].Description ?? base.Description; public override string DisplayName => this.attributeDictionary?[this.key].DisplayName ?? base.DisplayName; public override bool IsReadOnly => this.attributeDictionary?[this.key].IsReadOnly ?? false; public override Type PropertyType => this.valueDictionary[this.key].GetType(); public override bool CanResetValue(object component) { return false; } public override object GetValue(object component) { return this.valueDictionary[this.key]; } public override void ResetValue(object component) { } public override void SetValue(object component, object value) { this.valueDictionary[this.key] = (TValue)value; } public override bool ShouldSerializeValue(object component) { return false; } } } }
-
AuthorPosts
- You must be logged in to reply to this topic.