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.
Related
I am using Dev Express PropertyGrid control to display an complex object. Everything is looking good but I want to be able to customize some aspects, so here they are :
1)When displaying a list of items, for every row instead of [0],[1],[2]... index, I want to be able to display a friendly name.
I implemented ICustomTypeDescriptor interface for my Collection:
public class CustomObservableCollection : ObservableCollection<PropertyGridWorkSheetViewModel>, ICustomTypeDescriptor
{
public CustomObservableCollection(IEnumerable<PropertyGridWorkSheetViewModel> items) :
base(items)
{ }
public override string ToString() => $"{this.Items.Count()} Worksheets Available";
#region ICustomTypeDescriptor impl
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 object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
/// <summary>
/// Called to get the properties of this type. Returns properties with certain
/// attributes. this restriction is not implemented here.
/// </summary>
/// <param name="attributes"></param>
/// <returns></returns>
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
return GetProperties();
}
/// <summary>
/// Called to get the properties of this type.
/// </summary>
/// <returns></returns>
public PropertyDescriptorCollection GetProperties()
{
// Create a collection object to hold property descriptors
var pds = new PropertyDescriptorCollection(null);
// Iterate the list of employees
for (int i = 0; i < this.Items.Count; i++)
{
// Create a property descriptor for the employee item and add to the property descriptor collection
var pd = new CustomCollectionPropertyDescriptor(this, i);
pds.Add(pd);
}
// return the property descriptor collection
return pds;
}
#endregion
}
Also extended the PropertyDescriptor class for my custom collection like this :
public class CustomCollectionPropertyDescriptor : PropertyDescriptor
{
private CustomObservableCollection collection = null;
private int index = -1;
public CustomCollectionPropertyDescriptor(CustomObservableCollection coll, int idx) :
base("#" + idx.ToString(), null)
{
this.collection = coll;
this.index = idx;
}
public override AttributeCollection Attributes
{
get
{
return new AttributeCollection(null);
}
}
public override bool CanResetValue(object component)
{
return true;
}
public override Type ComponentType
{
get
{
return this.collection.GetType();
}
}
public override string DisplayName => "DisplayName";
public override string Description => "Description";
public override object GetValue(object component)
{
return this.collection[index];
}
public override bool IsReadOnly
{
get { return false; }
}
public override string Name
{
get { return "#" + index.ToString(); }
}
public override Type PropertyType
{
get { return this.collection[index].GetType(); }
}
public override void ResetValue(object component)
{
}
public override bool ShouldSerializeValue(object component)
{
return true;
}
public override void SetValue(object component, object value)
{
// this.collection[index] = value;
}
}
but without any success, Property Grid is displaying [0],[1] ... indexes for every row of the collection
And 2nd aspect :
I would like to be able to bind my row data to the checkbox present on every row of my Collection, and then write my own logic, just use that checkbox.
I have attached an image to describe visual my two requests :
https://ibb.co/dizwqS
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.
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:
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; }
}
Let me provide a bit of a history as to how I've reached this point.
I originally had a Property within my class that derived from CollectionsBase and had this collection mapped to the PropertyGrid and the user could Add/Edit/Remove items from the list at will.
However, I couldn't map the CollectionsBase with NHibernate, thus I had to scrap my initial implementation and instead of deriving from CollectionsBase, I had the class derive from IList.
Now I can map to NHibernate, but I am unable to edit the collection via the PropertyGrid.
I need some help getting the 2 to play nice with each other.
In my main class I have a property defined as:
public virtual ZoneCollection Zones
{
get { return zones; }
set { zones = value; }
}
My Zone Collection that inherits IList is defined as follows:
public class ZoneCollection : IList<Zone>, ICustomTypeDescriptor
{
private IList<Zone> _list;
public IList<Zone> _List
{
get { return _list; }
}
public ZoneCollection()
{
_list = new List<Zone>();
}
#region Implementation of IEnumerable
public IEnumerator<Zone> GetEnumerator()
{
return _list.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
#region Implementation of ICollection<Zone>
public void Add(Zone item)
{
_list.Add(item);
}
public void Clear()
{
_list.Clear();
}
public bool Contains(Zone item)
{
return _list.Contains(item);
}
public void CopyTo(Zone[] array, int arrayIndex)
{
_list.CopyTo(array, arrayIndex);
}
public bool Remove(Zone item)
{
return _list.Remove(item);
}
public int Count
{
get { return _list.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
#endregion
#region Implementation of IList<Zone>
public int IndexOf(Zone item)
{
return _list.IndexOf(item);
}
public void Insert(int index, Zone item)
{
_list.Insert(index, item);
}
public void RemoveAt(int index)
{
_list.RemoveAt(index);
}
public Zone this[int index]
{
get { return (Zone)_list[index]; }
set { _list[index] = value; }
}
#endregion
// Implementation of interface ICustomTypeDescriptor
#region ICustomTypeDescriptor impl
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 object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
/// <summary>
/// Called to get the properties of this type. Returns properties with certain
/// attributes. this restriction is not implemented here.
/// </summary>
/// <param name="attributes"></param>
/// <returns></returns>
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
return GetProperties();
}
/// <summary>
/// Called to get the properties of this type.
/// </summary>
/// <returns></returns>
public PropertyDescriptorCollection GetProperties()
{
// Create a collection object to hold property descriptors
PropertyDescriptorCollection pds = new PropertyDescriptorCollection(null);
// Iterate the list of zones
for (int i = 0; i < this._list.Count; i++)
{
// Create a property descriptor for the zone item and add to the property descriptor collection
ZoneCollectionPropertyDescriptor pd = new ZoneCollectionPropertyDescriptor(this, i);
pds.Add(pd);
}
// return the property descriptor collection
return pds;
}
#endregion
}
/// <summary>
/// Summary description for CollectionPropertyDescriptor.
/// </summary>
public class ZoneCollectionPropertyDescriptor : PropertyDescriptor
{
private ZoneCollection collection = null;
private int index = -1;
public ZoneCollectionPropertyDescriptor(ZoneCollection coll, int idx) :
base("#" + idx.ToString(), null)
{
this.collection = coll;
this.index = idx;
}
public override AttributeCollection Attributes
{
get
{
return new AttributeCollection(null);
}
}
public override bool CanResetValue(object component)
{
return true;
}
public override Type ComponentType
{
get
{
return this.collection.GetType();
}
}
public override string DisplayName
{
get
{
Zone zone = this.collection[index];
return zone.ID.ToString();
}
}
public override string Description
{
get
{
Zone zone = this.collection[index];
StringBuilder sb = new StringBuilder();
sb.Append(zone.ID.ToString());
if (zone.Streets.Route != String.Empty || zone.Streets.Crossing != String.Empty)
sb.Append("::");
if (zone.Streets.Route != String.Empty)
sb.Append(zone.Streets.Route);
if (zone.Streets.Crossing != String.Empty)
{
sb.Append(" and ");
sb.Append(zone.Streets.Crossing);
}
return sb.ToString();
}
}
public override object GetValue(object component)
{
return this.collection[index];
}
public override bool IsReadOnly
{
get { return false; }
}
public override string Name
{
get { return "#" + index.ToString(); }
}
public override Type PropertyType
{
get { return this.collection[index].GetType(); }
}
public override void ResetValue(object component)
{
}
public override bool ShouldSerializeValue(object component)
{
return true;
}
public override void SetValue(object component, object value)
{
// this.collection[index] = value;
}
}
}
Now, my ICustomTypeDescriptor and PropertyDescriptor worked fine when this class derived from CollectionsBase, but now it just shows the class name ZoneCollection in the property name without the "..." button to add/edit/remove the items from the list.
What am I doing wrong now that it is inherited from IList that this isn't working?
If I add:
[TypeConverter(typeof(ExpandableObjectConverter))]
To the beginning of the ZoneCollection, I get the items in the list listed in an expandable tree, but that's not what I am looking for. Where did the "..." button go that opened up a popup window that enabled me to add/edit/remove the items in the collection when I inherited from IList instead of CollectionBase?
The PropertyGrid is an old grumpy beast. It needs the non-generic IList explicit implementation, not the generic one.
As a site note, you could derive ZoneCollection directly from List<Zone>, and you don't need any ICustomTypeDescriptor / PropertyDescriptor, with regards to this PropertyGrid issue.
Here is an implementation that seems to work:
public class ZoneCollection : IList<Zone>, IList
{
private List<Zone> _list = new List<Zone>();
public ZoneCollection()
{
}
public int IndexOf(Zone item)
{
return _list.IndexOf(item);
}
public void Insert(int index, Zone item)
{
_list.Insert(index, item);
}
public void RemoveAt(int index)
{
_list.RemoveAt(index);
}
public Zone this[int index]
{
get
{
return _list[index];
}
set
{
_list[index] = value;
}
}
public void Add(Zone item)
{
_list.Add(item);
}
public void Clear()
{
_list.Clear();
}
public bool Contains(Zone item)
{
return _list.Contains(item);
}
public void CopyTo(Zone[] array, int arrayIndex)
{
_list.CopyTo(array, arrayIndex);
}
public int Count
{
get { return _list.Count; }
}
public bool IsReadOnly
{
get { return ((IList)_list).IsReadOnly; }
}
public bool Remove(Zone item)
{
return _list.Remove(item);
}
public IEnumerator<Zone> GetEnumerator()
{
return _list.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
int IList.Add(object value)
{
int index = Count;
Add((Zone)value);
return index;
}
bool IList.Contains(object value)
{
return Contains((Zone)value);
}
int IList.IndexOf(object value)
{
return IndexOf((Zone)value);
}
void IList.Insert(int index, object value)
{
Insert(index, (Zone)value);
}
bool IList.IsFixedSize
{
get { return ((IList)_list).IsFixedSize; }
}
bool IList.IsReadOnly
{
get { return ((IList)_list).IsReadOnly; }
}
void IList.Remove(object value)
{
Remove((Zone)value);
}
object IList.this[int index]
{
get
{
return this[index];
}
set
{
this[index] = (Zone)value;
}
}
void ICollection.CopyTo(Array array, int index)
{
CopyTo((Zone[])array, index);
}
bool ICollection.IsSynchronized
{
get { return ((ICollection)_list).IsSynchronized; }
}
object ICollection.SyncRoot
{
get { return ((ICollection)_list).SyncRoot; }
}
}