How to make a Readonly expandable properties in the winforms PropertyGrid - c#

I am working on a winforms application that uses the standard winforms property grid in a fairly involved way.
I provides custom type descriptors and type converters for class and also specified if certain properties are to be readonly, and not editable in the property grid.
I am having problems determining how to have an expandable type that is also readonly - in the documentation it seems that overriding public bool IsReadOnly is the way to go but as soon as I specify in the type converter that there are child properties it stops working.
I have managed to distill my code to a small example:
Here is the custom object:
public sealed class TestNode : ICustomTypeDescriptor
{
private readonly bool _isEditable;
private readonly List<TestNode> _childNodes = new List<TestNode>();
public TestNode(string name, bool isEditable = true)
{
_isEditable = isEditable;
Name = name;
}
public string Name { get; private set; }
public bool IsEditable
{
get { return _isEditable; }
}
public List<TestNode> ChildNodes
{
get { return _childNodes; }
}
public void AddChild(TestNode testNode)
{
ChildNodes.Add(testNode);
}
#region ICustomTypeDescriptor
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 EventDescriptorCollection GetEvents()
{
return TypeDescriptor.GetEvents(this, true);
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(this, attributes, true);
}
public PropertyDescriptorCollection GetProperties()
{
return GetProperties(new Attribute[0]);
}
public object GetEditor(Type editorBaseType)
{
return TypeDescriptor.GetEditor(this, editorBaseType, true);
}
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
return new PropertyDescriptorCollection(new PropertyDescriptor[]{new TestNodeDescriptor(this)}, true);
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
#endregion
}
Which has a descriptor like this, where I can specify if it shows as readonly in the propertyGrid:
public class TestNodeDescriptor : PropertyDescriptor
{
private readonly TestNode _testNode;
public TestNodeDescriptor(TestNode testNode) : base("Test", null)
{
_testNode = testNode;
}
public override bool CanResetValue(object component)
{
return false;
}
public override object GetValue(object component)
{
return _testNode;
}
public override void ResetValue(object component)
{
}
public override void SetValue(object component, object value)
{
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
public override Type ComponentType
{
get { return null; }
}
public override bool IsReadOnly
{
get { return !_testNode.IsEditable; }
}
public override Type PropertyType
{
get { return typeof(TestNode); }
}
public override TypeConverter Converter
{
get
{
return new TestTypeConverter(_testNode);
}
}
}
And finally the type converter, where I specify the conversion to string for display and the required expandable type converter to show child properties:
public class TestTypeConverter : ExpandableObjectConverter
{
private readonly TestNode _testNode;
public TestTypeConverter(TestNode testNode)
{
_testNode = testNode;
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(string);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
return _testNode.Name;
}
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
var descriptors = _testNode.ChildNodes.Select(c => (PropertyDescriptor) (new TestNodeDescriptor(c))).ToArray();
return new PropertyDescriptorCollection(descriptors);
}
public override bool GetPropertiesSupported(ITypeDescriptorContext context)
{
return _testNode.ChildNodes.Any();
}
}
The odd thing is that if the method in the type converter public override bool GetPropertiesSupported(ITypeDescriptorContext context) return true then the children are shown as expected but its not as readonly.
For example with this test code:
public partial class Form1 : Form
{
readonly TestNode _parentNode = new TestNode("Parent");
public Form1()
{
InitializeComponent();
var child1 = new TestNode("Child1", false);
var child2 = new TestNode("Child2", false);
child2.AddChild(new TestNode("Grandchild", false));
_parentNode.AddChild(child2);
_parentNode.AddChild(child1);
propertyGrid1.SelectedObject = _parentNode;
}
}
I would get a dialog show like this:
I was expecting all child nodes to be readonly. Has anyone got something like this to work successfully with the PropertyGrid?
Its been a very frustrating experience and any tips would be greatly appreciated.

Related

Properly trapping OnCollectionChanged and OnPropertyChanged events

First, no comments about using CollectionBase as a base class. I know, I know ...
I have the following:
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Diagnostics;
using System.Collections.Specialized;
namespace PropertyGridSample
{
[TypeConverter(typeof(LayerConverter))]
public class Layer
{
public Image image { get; set; }
public Layer() { }
}
public class Layers
{
LayerCollection layercollection { get; set; }
public Layers()
{
layercollection = new LayerCollection();
Layer[] layz = new Layer[2];
Layer lay1 = new Layer(); Layer lay2 = new Layer(); //Create two test layers and add them to the layer collection
layercollection.Add(lay1); layercollection.Add(lay2);
}
[TypeConverter(typeof(LayerCollectionConverter))]
public LayerCollection Layer_Collection { get { return layercollection; } }
}
public class LayerCollection : CollectionBase, ICustomTypeDescriptor, INotifyCollectionChanged, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
Debug.Write(propertyName);
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (CollectionChanged != null)
{
CollectionChanged(this, e);
}
Debug.Write("Will Never show, when built in collection editor is used.");
}
#region collection impl
public void Add(Layer lay) { List.Add(lay); OnPropertyChanged("ADDLAYER"); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, 0)); } //Adds a layer object to the collection
public void Remove(Layer lay) { List.Remove(lay); OnPropertyChanged("REMOVELAYER"); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, 0)); } //Removes a layer object from the collection
public Layer this[int index] { get { return (List.Count > -1 && index < List.Count) ? (Layer)List[index] : null; } } //Return a layer object at index position
#endregion
#region ICustomTypeDescriptor impl
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 TypeDescriptor.GetDefaultProperty(this, true); }
public EventDescriptorCollection GetEvents(Attribute[] attributes) { return TypeDescriptor.GetEvents(this, attributes, true); }
public EventDescriptorCollection GetEvents() { return TypeDescriptor.GetEvents(this, true); }
public object GetEditor(Type editorBaseType) { return TypeDescriptor.GetEditor(this, editorBaseType, true); }
public object GetPropertyOwner(PropertyDescriptor pd) { return this; }
//Called to get the properties of this type. Returns properties with certain attributes. this restriction is not implemented here.
public PropertyDescriptorCollection GetProperties(Attribute[] attributes) { return GetProperties(); }
//Called to get the properties of this type.
public PropertyDescriptorCollection GetProperties()
{
PropertyDescriptorCollection pds = new PropertyDescriptorCollection(null); // Create a collection object to hold property descriptors
// Iterate the list of layers and create a property descriptor for each layer item and add to the property descriptor collection
for (int i = 0; i < this.List.Count; i++) { LayerCollectionPropertyDescriptor pd = new LayerCollectionPropertyDescriptor(this, i); pds.Add(pd); }
return pds; // return the descriptor collection
}
#endregion
}
class LayerConverter : ExpandableObjectConverter
{
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destType)
{
return (destType == typeof(string) && value is Layer) ? "Layer Data": base.ConvertTo(context, culture, value, destType);
}
}
class LayerCollectionConverter : ExpandableObjectConverter
{
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destType)
{
return (destType == typeof(string) && value is LayerCollection) ? "Items": base.ConvertTo(context, culture, value, destType);
}
}
public class LayerCollectionPropertyDescriptor : PropertyDescriptor
{
private LayerCollection collection = null;
private int index = -1;
public LayerCollectionPropertyDescriptor(LayerCollection coll, int idx) : base("#" + idx.ToString(), null)
{
collection = coll; index = idx;
}
public override AttributeCollection Attributes { get { return new AttributeCollection(null); } }
public override bool CanResetValue(object component) { return true; }
public override bool IsReadOnly { get { return false; } }
public override bool ShouldSerializeValue(object component) { return true; }
public override string Description { get { return "Layer Description"; } }
public override string DisplayName { get { return "Layer " + index.ToString(); } }
public override string Name { get { return "#" + index.ToString(); } }
public override object GetValue(object component) { return collection[index]; }
public override Type ComponentType { get { return collection.GetType(); } }
public override Type PropertyType { get { return collection[index].GetType(); } }
public override void ResetValue(object component) { }
public override void SetValue(object component, object value) { } // this.collection[index] = value;
}
}
In the LayerCollection class, I've tried to add Change notifications. In my main form, I want to assign a function, to the OnCollectionChanged and OnPropertyChanged events. But ... First, I have a problem. I only get the collection change & property change events, once at startup. Cause, I manually add two items, to the collection. Just for testing. Anyhow, if I use the built-in collection editor:
The events don't get fired, if I add or remove items with it. I really need, the events. Especially, exposed, to later capture in my main form.

Add display attribute to a variable on runtime

I have a class
class demo
{
public int var1 {set; get;}
}
I want to add Display attributes on that variable on runtime instead of doing this,
class Demo
{
[Display (Name="Any Name", GroupName= "My Group 1")]
public int var1 {set; get;}
}
Any possible way to change or Assign those attributes from any other class?
Very simple example, it can be build and in PropertyGrid you will see something.
You need read about ICustomTypeDescriptor and PropertyDescriptor.
On Form Load:
propertyGrid1.SelectedObject = new MyType(new[] { "Property1", "Property2" });
Types:
public class MyType : ICustomTypeDescriptor
{
private string[] _properties;
public MyType(string[] properties)
{
_properties = properties;
}
public AttributeCollection GetAttributes()
{
return null;
}
public string GetClassName()
{
return nameof(MyType);
}
public string GetComponentName()
{
throw new NotImplementedException();
}
public TypeConverter GetConverter()
{
return null;
}
public EventDescriptor GetDefaultEvent()
{
throw new NotImplementedException();
}
public PropertyDescriptor GetDefaultProperty()
{
return null;
}
public object GetEditor(Type editorBaseType)
{
return null;
}
public EventDescriptorCollection GetEvents()
{
throw new NotImplementedException();
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
throw new NotImplementedException();
}
public PropertyDescriptorCollection GetProperties()
{
return GetProperties(null);
}
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
var props = new PropertyDescriptor[_properties.Length];
for (int i = 0; i < _properties.Length; i++)
props[i] = new CustomPropertyDescriptor(_properties[i],
new Attribute[]
{
new DisplayNameAttribute(#"Displ Value " + i),
new CategoryAttribute("Category" + i%2)
});
return new PropertyDescriptorCollection(props);
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
}
public class CustomPropertyDescriptor : PropertyDescriptor
{
public CustomPropertyDescriptor(string name, Attribute[] attrs) : base(name, attrs)
{
}
public override bool CanResetValue(object component)
{
return true;
}
public override object GetValue(object component)
{
return "1";
}
public override void ResetValue(object component)
{
throw new NotImplementedException();
}
public override void SetValue(object component, object value)
{
throw new NotImplementedException();
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
public override Type ComponentType { get; }
public override bool IsReadOnly { get { return false; } }
public override Type PropertyType { get { return typeof (string); } }
}

Trying to use DataGridView together with ICustomTypeDescriptor

I'm trying to use the DataGridView to display a list of objects.
In the class that I want to present properties for I have some C# properties and I also want to create properties dynamically for some reasons.
Here I have an example, that works fine for the C# property (FeatureId) but the dynamically created property (Name) returns the value of the first instance for all the instances. Why?
First a class that implements the ICustomPropertyDescriptor interface
public abstract class PropertyPresentationSubBase : ICustomTypeDescriptor
{
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 virtual PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
PropertyDescriptorCollection rtn = TypeDescriptor.GetProperties(this);
//rtn = FilterReadonly(rtn, attributes);
return new PropertyDescriptorCollection(rtn.Cast<PropertyDescriptor>().ToArray());
}
public virtual PropertyDescriptorCollection GetProperties()
{
return TypeDescriptor.GetProperties(this, true);
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
[Browsable(false)]
public PropertyPresentationSubBase Parent
{
get
{
return m_Parent;
}
set
{
m_Parent = value;
}
}
PropertyPresentationSubBase m_Parent = null;
[Browsable(false)]
public Type ValueType
{
get
{
return valueType;
}
set
{
valueType = value;
}
}
private Type valueType = null;
[Browsable(false)]
public string Name
{
get
{
return sName;
}
set
{
sName = value;
}
}
public abstract object GetValue();
private string sName = string.Empty;
public abstract void Change(object value);
}
}
I also have a class that inherit from PropertyDescriptor
public class MyCustomPropertyDescriptor : PropertyDescriptor
{
PropertyPresentationSubBase m_Property;
public MyCustomPropertyDescriptor(PropertyPresentationSubBase myProperty, Attribute[] attrs, int propertyNo)
: base(myProperty.Name + propertyNo, attrs)
{
m_Property = myProperty;
}
#region PropertyDescriptor specific
public override bool CanResetValue(object component)
{
return false;
}
public override string Name
{
get
{
return "MyName";
}
}
public override Type ComponentType
{
get
{
return null;
}
}
public override object GetValue(object component)
{
return m_Property.GetValue();
}
public override string Description
{
get
{
return "Description";
}
}
public object Value
{
get
{
return m_Property;
}
}
public override string Category
{
get
{
return "Category";
}
}
public override string DisplayName
{
get
{
return m_Property.Name;
}
}
public override bool IsReadOnly
{
get
{
return false;
}
}
public override void ResetValue(object component)
{
//Have to implement
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
public override void SetValue(object component, object value)
{
m_Property.Change(value);
}
public override Type PropertyType
{
get
{
if ((m_Property != null) && (m_Property.ValueType != null))
{
return m_Property.ValueType;
}
else
{
return System.Type.Missing.GetType();
}
}
}
#endregion
}
A small class that holds the data:
public class QuadriFeatureItem
{
public QuadriFeatureItem(int featureId, string name)
{
m_featureId = featureId;
m_name = name;
}
public int m_featureId;
public string m_name;
}
My class that is sent to the grid (containing both the FeatureId property and the dynamically created property)
class FeaturePropertyPresentation : PropertyPresentationSubBase
{
public int FeatureId
{
get
{
return m_feature.m_featureId;
}
set { m_feature.m_featureId = value; }
}
public FeaturePropertyPresentation(QuadriFeatureItem item)
{
m_feature = item;
}
private QuadriFeatureItem m_feature;
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
PropertyDescriptorCollection rtn = base.GetProperties(attributes);
CreateNameAttribute(ref rtn, attributes);
return rtn;
}
private void CreateNameAttribute(ref PropertyDescriptorCollection pdc, Attribute[] attributes)
{
NameProperty namePres = null;
namePres = new NameProperty(m_feature, this);
pdc.Add(new MyCustomPropertyDescriptor(namePres, attributes, pdc.Count));
}
public override void Change(object value)
{
throw new NotImplementedException();
}
public override object GetValue()
{
return this;
}
}
A class that implements the nameproperty:
class NameProperty : PropertyPresentationSubBase
{
public NameProperty(QuadriFeatureItem feature, FeaturePropertyPresentation parent)
: base()
{
m_quadriFeatureItem = feature;
Parent = parent;
ValueType = typeof(string);
}
private QuadriFeatureItem m_quadriFeatureItem;
public override void Change(object value)
{
m_quadriFeatureItem.m_name = (string)value;
}
public override object GetValue()
{
return m_quadriFeatureItem.m_name;
}
}
And my formcode:
public Form1()
{
InitializeComponent();
ShowGrid();
}
private void ShowGrid()
{
QuadriFeatureItem no1 = new QuadriFeatureItem(1, "Nummer1");
QuadriFeatureItem no2 = new QuadriFeatureItem(2, "Nummer2");
QuadriFeatureItem no3 = new QuadriFeatureItem(3, "Nummer3");
BindingSource source = new BindingSource();
FeaturePropertyPresentation no1Pres = new FeaturePropertyPresentation(no1);
FeaturePropertyPresentation no2Pres = new FeaturePropertyPresentation(no2);
FeaturePropertyPresentation no3Pres = new FeaturePropertyPresentation(no3);
source.Add(no1Pres);
source.Add(no2Pres);
source.Add(no3Pres);
dataGridView1.DataSource = source;
Show();
}
But the grid shows "Nummer1" for all rows. Why? I use this presentation classes in a propertygrid and it works fine. I also use this MyCustomPropertyDescriptor in a propertygrid.
My wish is now to be able to reuse this presentationclasses and MyCustomPropertyDescriptor in a datagridview. Is it possible with any modification in the MyCustomPropertyDescriptor or PropertyPresentationSubBase?
The problem is that your custom property descriptor is bound to a concrete instance. Which works when you use single item data binding (like TextBox to your object property or selecting your object in a PropertyGrid control). However, when you use a control that requires list data binding (like DataGridView, ListView, ListBox, ComboBox list etc.) this technique doesn't work. In order to auto populate the columns, DataGridView needs a set of properties that are common to all items. In order to do that, it tries several ways to obtain that information (a good explanation can be found here DataGridView not showing properites of objects which implement ICustomTypeDescriptor), and one of them is to take the first item of the list and ask for properties (hence your debugging experience). Anyway, in order to make this work in a list binding scenarios, your property descriptor needs to be implemented differently.
Notice the signature of the PropertyDescriptors GetValue/SetValue methods. Both they have an argument object component. This is the object instance you need to return or set the value. You can think of property descriptor being an inverse of what we usually use in a programming language. So instead of
var val = obj.Property;
obj.Property = val;
we have
var val = propertyDescriptor.GetValue(obj);
propertyDescriptor.SetValue(obj, val);
In other words, you should not "embed" your object instance inside the property descriptor, but use the passed argument.
Here is a sample generic implementation of a property descriptor doing just that:
public class SimplePropertyDescriptor<TComponent, TValue> : PropertyDescriptor
where TComponent : class
{
private readonly Func<TComponent, TValue> getValue;
private readonly Action<TComponent, TValue> setValue;
private readonly string displayName;
public SimplePropertyDescriptor(string name, Attribute[] attrs, Func<TComponent, TValue> getValue, Action<TComponent, TValue> setValue = null, string displayName = null)
: base(name, attrs)
{
Debug.Assert(getValue != null);
this.getValue = getValue;
this.setValue = setValue;
this.displayName = displayName;
}
public override string DisplayName { get { return displayName ?? base.DisplayName; } }
public override Type ComponentType { get { return typeof(TComponent); } }
public override bool IsReadOnly { get { return setValue == null; } }
public override Type PropertyType { get { return typeof(TValue); } }
public override bool CanResetValue(object component) { return false; }
public override bool ShouldSerializeValue(object component) { return false; }
public override void ResetValue(object component) { }
public override object GetValue(object component) { return getValue((TComponent)component); }
public override void SetValue(object component, object value) { setValue((TComponent)component, (TValue)value); }
}
sample usage with your stuff:
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
var properties = base.GetProperties(attributes);
// Custom name property
properties.Add(new SimplePropertyDescriptor<FeaturePropertyPresentation, string>("FeatureName", attributes,
getValue: component => component.m_feature.m_name,
setValue: (component, value) => component.m_feature.m_name = value, // remove this line to make it readonly
displayName: "Feature Name"
));
return properties;
}
and, putting it all together, a small example equivalent to yours:
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows.Forms;
namespace Samples
{
// Generic implemenation of a property descriptor
public class SimplePropertyDescriptor<TComponent, TValue> : PropertyDescriptor
where TComponent : class
{
private readonly Func<TComponent, TValue> getValue;
private readonly Action<TComponent, TValue> setValue;
private readonly string displayName;
public SimplePropertyDescriptor(string name, Attribute[] attrs, Func<TComponent, TValue> getValue, Action<TComponent, TValue> setValue = null, string displayName = null)
: base(name, attrs)
{
Debug.Assert(getValue != null);
this.getValue = getValue;
this.setValue = setValue;
this.displayName = displayName;
}
public override string DisplayName { get { return displayName ?? base.DisplayName; } }
public override Type ComponentType { get { return typeof(TComponent); } }
public override bool IsReadOnly { get { return setValue == null; } }
public override Type PropertyType { get { return typeof(TValue); } }
public override bool CanResetValue(object component) { return false; }
public override bool ShouldSerializeValue(object component) { return false; }
public override void ResetValue(object component) { }
public override object GetValue(object component) { return getValue((TComponent)component); }
public override void SetValue(object component, object value) { setValue((TComponent)component, (TValue)value); }
}
// Your stuff
public abstract class PropertyPresentationSubBase : ICustomTypeDescriptor
{
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 virtual PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
PropertyDescriptorCollection rtn = TypeDescriptor.GetProperties(this);
//rtn = FilterReadonly(rtn, attributes);
return new PropertyDescriptorCollection(rtn.Cast<PropertyDescriptor>().ToArray());
}
public virtual PropertyDescriptorCollection GetProperties()
{
return TypeDescriptor.GetProperties(this, true);
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
[Browsable(false)]
public PropertyPresentationSubBase Parent
{
get
{
return m_Parent;
}
set
{
m_Parent = value;
}
}
PropertyPresentationSubBase m_Parent = null;
[Browsable(false)]
public Type ValueType
{
get
{
return valueType;
}
set
{
valueType = value;
}
}
private Type valueType = null;
[Browsable(false)]
public string Name
{
get
{
return sName;
}
set
{
sName = value;
}
}
public abstract object GetValue();
private string sName = string.Empty;
public abstract void Change(object value);
}
public class QuadriFeatureItem
{
public QuadriFeatureItem(int featureId, string name)
{
m_featureId = featureId;
m_name = name;
}
public int m_featureId;
public string m_name;
}
class FeaturePropertyPresentation : PropertyPresentationSubBase
{
public int FeatureId
{
get
{
return m_feature.m_featureId;
}
set { m_feature.m_featureId = value; }
}
public FeaturePropertyPresentation(QuadriFeatureItem item)
{
m_feature = item;
}
private QuadriFeatureItem m_feature;
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
var properties = base.GetProperties(attributes);
// Custom name property
properties.Add(new SimplePropertyDescriptor<FeaturePropertyPresentation, string>("FeatureName", attributes,
getValue: component => component.m_feature.m_name,
setValue: (component, value) => component.m_feature.m_name = value, // remove this line to make it readonly
displayName: "Feature Name"
));
return properties;
}
public override void Change(object value)
{
throw new NotImplementedException();
}
public override object GetValue()
{
return this;
}
}
// Test
static class Test
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var dataSet = Enumerable.Range(1, 10).Select(n => new FeaturePropertyPresentation(new QuadriFeatureItem(n, "Nummer" + n))).ToList();
var form = new Form();
var dg = new DataGridView { Dock = DockStyle.Fill, Parent = form };
dg.DataSource = dataSet;
Application.Run(form);
}
}
}
result:

Adding a TypeConverter to dynamic properties

I am coding a C# forms application where an object has custom properties for a PropertyGrid. These custom properties are displayed at runtime.
Here is my code:
private readonly List<CustomProperty> props = new List<CustomProperty>();
[Browsable(false)]
public List<CustomProperty> properties { get { return props; } }
private Dictionary<string, object> values = new Dictionary<string, object>();
public object this[string name]
{
get { object val; values.TryGetValue(name, out val); return val; }
set
{
values[name] = value;
}
}
private class CustomWebpageObjectTypeUserObjectConverter : ExpandableObjectConverter
{
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
var stdProps = base.GetProperties(context, value, attributes);
CustomWebpageObjectTypeUserObject obj = value as CustomWebpageObjectTypeUserObject;
List<CustomProperty> customProps = obj == null ? null : obj.properties;
PropertyDescriptor[] props = new PropertyDescriptor[stdProps.Count + (customProps == null ? 0 : customProps.Count)];
stdProps.CopyTo(props, 0);
if (customProps != null)
{
int index = stdProps.Count;
foreach (CustomProperty prop in customProps)
{
CustomWebpageObjectTypeUserObjectCustomPropertyDescriptor customWebpageObjectTypeUserObjectCustomPropertyDescriptor = new CustomWebpageObjectTypeUserObjectCustomPropertyDescriptor(prop);
props[index++] = customWebpageObjectTypeUserObjectCustomPropertyDescriptor;
}
}
return new PropertyDescriptorCollection(props);
}
}
public class CustomWebpageObjectTypeUserObjectCustomPropertyDescriptor : PropertyDescriptor
{
private readonly CustomProperty prop;
public CustomWebpageObjectTypeUserObjectCustomPropertyDescriptor(CustomProperty prop)
: base(prop.name, null)
{
this.prop = prop;
}
public override string Category { get { return prop.category; } }
public override string Description { get { return prop.description; } }
public override string Name { get { return prop.name; } }
public override bool ShouldSerializeValue(object component) { return ((CustomWebpageObjectTypeUserObject)component)[prop.name] != null; }
public override void ResetValue(object component) { ((CustomWebpageObjectTypeUserObject)component)[prop.name] = null; }
public override bool IsReadOnly { get { return false; } }
public override Type PropertyType { get { return prop.type; } }
public override bool CanResetValue(object component) { return true; }
public override Type ComponentType { get { return typeof(CustomWebpageObjectTypeUserObject); } }
public override void SetValue(object component, object value)
{
((CustomWebpageObjectTypeUserObject)component)[prop.name] = value;
prop._value = value;
}
public override object GetValue(object component)
{
return ((CustomWebpageObjectTypeUserObject)component)[prop.name] ?? prop._value;
}
}
This code works correctly with simple set and get values. What I am needing some advice with is how to display a drop down list with dynamic properties that are created at runtime.
I understand how to use a TypeConverter to get a StandardValuesCollection, yet am not sure how to do this with dynamic properties. Do I need to add this attribute to a dynamic property, and does this need to be done in the CustomWebpageObjectTypeUserObjectCustomPropertyDescriptor class? Is there an override that I need to implement to add this attribute? Do I need to do this another way?
Thanks in advance.

Custom PropertyDescriptor always readonly

I made the following custom PropertyDescriptor
public class CustomProperty : PropertyDescriptor
{
private PropertyDescriptor _innerPropertyDescriptor;
private bool _ronly;
public CustomProperty(PropertyDescriptor inner, Attribute[] attrs)
: base(inner.Name, attrs)
{
_innerPropertyDescriptor = inner;
_ronly = inner.IsReadOnly;
}
public override object GetValue(object component)
{
return _innerPropertyDescriptor.GetValue(component);
}
public override bool SupportsChangeEvents
{
get { return true; }
}
public override Type PropertyType
{
get { return _innerPropertyDescriptor.GetType(); }
}
public override void ResetValue(object component)
{
// Not relevant.
}
public override void SetValue(object component, object value)
{
_innerPropertyDescriptor = (CustomProperty)value;
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
public override bool CanResetValue(object component)
{
return true;
}
public override Type ComponentType
{
get { return _innerPropertyDescriptor.GetType(); }
}
public override bool IsReadOnly
{
get
{
return false;
}
}
}
This PropertyDescriptor will be used for the following class
public class MyClass : ICustomTypeDescriptor
{
#region MyClass Properties
......
#endregion
#region ICustomTypeDescriptor Implementation
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 TypeDescriptor.GetDefaultProperty(this, true);
}
public object GetEditor(System.Type editorBaseType)
{
return TypeDescriptor.GetEditor(this,editorBaseType, true);
}
public EventDescriptorCollection GetEvents(System.Attribute[] attributes)
{
return TypeDescriptor.GetEvents(this,attributes, true);
}
public EventDescriptorCollection GetEvents()
{
return TypeDescriptor.GetEvents(this, true);
}
public PropertyDescriptorCollection GetProperties(System.Attribute[] attributes)
{
PropertyDescriptorCollection originalCollection = TypeDescriptor.GetProperties(this,attributes,true);
PropertyDescriptor[] pds = new PropertyDescriptor[originalCollection.Count];
originalCollection.CopyTo(pds,0);
PropertyDescriptorCollection newCollection = new PropertyDescriptorCollection(pds);
for (int i = 0; i < originalCollection.Count; i++)
{
PropertyDescriptor pd = originalCollection[i];
List<Attribute> la = new List<Attribute>();
foreach (Attribute attribute in pd.Attributes)
la.Add(attribute);
CustomProperty cp = new CustomProperty(pd, la.ToArray());
newCollection.RemoveAt(i);
newCollection.Insert(i, cp);
}
return newCollection;
}
public PropertyDescriptorCollection GetProperties()
{
return TypeDescriptor.GetProperties(this, true);
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
#endregion
}
What i did in this implementation, is to rewrite the MyClass properties to have the possibility to manage the Reset functionality from the Visual Studio PropertyGrid.
Everything seems working good, but this implementation causes a wrong effect: all my new properties stored in the PropertyDescriptorCollection are all ReadOnly!! I can't understand why!? I tried everything, i put also a return false; in the IsReadOnly property of CustomProperty but no way. The properties appears always ReadOnly in the PropertGrid.
Anyone have an idea?
Your PropertyType and ComponentType implementations are broken. They sould return the inner property's PropertyType / ComponentType. By returning GetType you are returning something like ReflectionPropertyDescriptor, which is neither editable nor convertible.
public override Type PropertyType
{
get { return _innerPropertyDescriptor.PropertyType; }
}

Categories

Resources