Is there a way to make the .NET Forms
PropertyGrid
respect the DisplayNameAttribute
when it sorts properties of multiple selected objects. When a single object is select the PropertyGrid
sorts based on the DisplayNameAttribute
but when multiple objects are selected, it uses the actual property name to sort.
The following code demonstrates the issue:
static class Program
{
[STAThread]
static void Main()
{
Form myForm1 = new Form();
myForm1.Width = 820;
myForm1.Height = 340;
PropertyGrid grid1 = new PropertyGrid();
grid1.Left = 0;
grid1.Top = 0;
grid1.Width = 400;
grid1.Height = 300;
myForm1.Controls.Add(grid1);
grid1.SelectedObject = new MyObject();
PropertyGrid grid2 = new PropertyGrid();
grid2.Left = 400;
grid2.Top = 0;
grid2.Width = 400;
grid2.Height = 300;
myForm1.Controls.Add(grid2);
object[] objects = new object[] { new MyObject(), new MyObject() };
grid2.SelectedObjects = objects;
Application.Run(myForm1);
}
}
public class MyObject
{
[DisplayName("ZZZZ")]
public int AProperty
{
get;
set;
}
[DisplayName("BBBB")]
public int BProperty
{
get;
set;
}
}
The previous code makes a Form
with two PropertyGrids
. The left grid contains a single object in its selection, while the right grid contains two objects in its selection.
All objects are of the same type. The left grid sorts the properties
based on the DisplayNameAttribute
while the right sorts based on the actual property name. In both cases the DisplayNameAttribute
is presented as the properties name in the grid:
Can I force the PropertyGrid
to always use the DisplayNameAttribute
when sorting?
So I have found an answer to my problem. Yes it is possible to "force
",or perhaps more correctly "trick
", the PropertyGrid
into always respecting the DisplayName
when sorting. The heart of the issue was in the fact the the 'PropertyGrid' uses the actual property name when sorting the properties of multiple selected object. So in order to get the desired behavior, we have to make the grid believe that the DisplayName
is the actual property name. The PropertyGrid
uses PropertyDescriptors
to discover the various attributes of an objects properties. We simply need a custom PropertyDescriptor
that will present the DisplayName
as the actual property name. Behold the following code:
public class DisplayNameEnforcingDescriptor : PropertyDescriptor
{
private PropertyDescriptor _descriptor;
public DisplayNameEnforcingDescriptor(PropertyDescriptor descriptor)
: base(descriptor)
{
this._descriptor = descriptor;
}
public override string Name
{
get
{
return string.IsNullOrEmpty(DisplayName) ? base.Name : DisplayName;
}
}
public override bool CanResetValue(object component)
{
return _descriptor.CanResetValue(component);
}
public override Type ComponentType
{
get
{
return _descriptor.ComponentType;
}
}
public override object GetValue(object component)
{
return _descriptor.GetValue(component);
}
public override bool IsReadOnly
{
get
{
return _descriptor.IsReadOnly;
}
}
public override Type PropertyType
{
get
{
return _descriptor.PropertyType;
}
}
public override void ResetValue(object component)
{
_descriptor.ResetValue(component);
}
public override void SetValue(object component, object value)
{
_descriptor.SetValue(component, value);
}
public override bool ShouldSerializeValue(object component)
{
return _descriptor.ShouldSerializeValue(component);
}
}
The previous class is used to wrap an existing PropertyDescriptor
and override the behavior of the 'Name' property. The Name
property will now return the DisplayName
(if not null or empty) or the actual properties name. All other functionality is delegated to the wrapped PropertyDescriptor
.
So now we have a way to alter the presented property name, we just need to have the PropertyGrid
utilize the new PropertyDescriptor
. To do so we need a customized TypeDescriptor
. Once again, behold the following code:
public class DisplayNameEnforcingConverter : ExpandableObjectConverter
{
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
PropertyDescriptorCollection original = base.GetProperties(context, value, attributes);
List<DisplayNameEnforcingDescriptor> descriptorList = new List<DisplayNameEnforcingDescriptor>();
foreach (PropertyDescriptor descriptor in original)
descriptorList.Add(new DisplayNameEnforcingDescriptor(descriptor));
return new PropertyDescriptorCollection(descriptorList.ToArray());
}
}
This class inherits from ExpandableObjectConverter
to utilize its existing behavior and minimize our implementation. We only need to override the GetProperties
method. This methods asks the base type to get the relevant PropertyDescriptorCollection
then wraps all the elements of this collection in our DisplayNameEnforcingDescriptor
. A new collection
is returned contaning our wrapped elements.
Now if we attribute the MyObject
class with the DisplayNameEnforcingConverter
, sorting will always happen based on a properties DisplayName
[TypeConverter(typeof(DisplayNameEnforcingConverter))]
public class MyObject
{
[DisplayName("ZZZZ")]
public int AProperty
{
get;
set;
}
[DisplayName("BBBB")]
public int BProperty
{
get;
set;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With