How to change the DisplayNameAttribute on runtime to use in a Property Grid C#
Asked Answered
D

5

5

I am wondering how to change the DisplayNameAttribute on runtime, I want the displayName to be Feet instead of Meters in my property grid when I do some conversions, is that possible?

[DisplayName("Meters")]
public double Distance
  {
     get{return distance;}
  }
Directed answered 10/3, 2010 at 21:47 Comment(2)
You could always set it to 'Distance' and include the units in the value, e.g. "Distance | 34 meters" and "Distance | 112 feet".Nonchalance
How would I do that if my type is a double for example?Directed
G
12

There are a number of different ways to do this. The simplest is to do something akin to how certain i18n products do it - subclass the attribute and intercept the text; but this only works if you own the type, and from an attribute you can't access the context.

The next things to look at would be TypeConverter, since this provides access to the component-model view on the properties, and is simpler than the next two options ;-p This will work with PropertyGrid, but not DataGridView etc.

Next on the list is ICustomTypeDescriptor - not a fun interface to implement, but you can swap in your own property-descriptor. This requires that you own the type (in order to provide the interface support).

Finally, CustomTypeDescriptor is like the last, but works even for types you don't own, and allows access to tweaked metadata at both the type and object level (everything else only supports object).

Which to choose? I suspect that TypeConverter would be the most sensible; you need the object-context (which a subclassed attribute doesn't provide), but you don't need the extra complexity.

Here's an example; note that in the TypeConverter code we have access to the object-context. If the name is simple, then putting it in the TypeConverter (when creating the property) should suffice.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
class MyFunkyTypeConverter : ExpandableObjectConverter
{
    public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
    {
        PropertyDescriptorCollection props = base.GetProperties(context, value, attributes);
        List<PropertyDescriptor> list = new List<PropertyDescriptor>(props.Count);
        foreach (PropertyDescriptor prop in props)
        {
            switch (prop.Name)
            {
                case "Distance":
                    list.Add(new DisplayNamePropertyDescriptor(
                        prop, "your magic code here"));
                    break;
                default:
                    list.Add(prop);
                    break;
            }
        }
        return new PropertyDescriptorCollection(list.ToArray(), true);
    }
}
class DisplayNamePropertyDescriptor : PropertyDescriptor
{
    private readonly string displayName;
    private readonly PropertyDescriptor parent;
    public DisplayNamePropertyDescriptor(
        PropertyDescriptor parent, string displayName) : base(parent)
    {
        this.displayName = displayName;
        this.parent = parent;
    }
    public override string  DisplayName
    {get { return displayName; } }

    public override bool ShouldSerializeValue(object component)
    { return parent.ShouldSerializeValue(component); }

    public override void SetValue(object component, object value) {
        parent.SetValue(component, value);
    }
    public override object GetValue(object component)
    {
        return parent.GetValue(component);
    }
    public override void ResetValue(object component)
    {
        parent.ResetValue(component);
    }
    public override bool CanResetValue(object component)
    {
        return parent.CanResetValue(component);
    }
    public override bool IsReadOnly
    {
        get { return parent.IsReadOnly; }
    }
    public override void AddValueChanged(object component, EventHandler handler)
    {
        parent.AddValueChanged(component, handler);
    }
    public override void RemoveValueChanged(object component, EventHandler handler)
    {
        parent.RemoveValueChanged(component, handler);
    }
    public override bool SupportsChangeEvents
    {
        get { return parent.SupportsChangeEvents; }
    }
    public override Type PropertyType
    {
        get { return parent.PropertyType; }
    }
    public override TypeConverter Converter
    {
        get { return parent.Converter; }
    }
    public override Type ComponentType
    {
        get { return parent.ComponentType; }
    }
    public override string Description
    {
        get { return parent.Description; }
    }
    public override PropertyDescriptorCollection GetChildProperties(object instance, Attribute[] filter)
    {
        return parent.GetChildProperties(instance, filter);
    }
    public override string Name
    {
        get { return parent.Name; }
    }

}

[TypeConverter(typeof(MyFunkyTypeConverter))]
class MyFunkyType
{
    public double Distance {get;set;}

    public double AnotherProperty { get; set; }
}
static class Program
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.Run(new Form { Controls = {
            new PropertyGrid { Dock = DockStyle.Fill,
                SelectedObject = new MyFunkyType {
                    Distance = 123.45
                }}
        }});
    }
}
Godhood answered 11/3, 2010 at 5:16 Comment(0)
S
1

If the displayname attribute already exists (like this situation) and you just want to change the name, you can use the propertydescriptor of this property. Just look through the attributes property until you find the attribute and change the value.

Spokesman answered 20/12, 2010 at 17:28 Comment(0)
G
0

Attributes are compiled as part of the type, so they cannot be changed at runtime.

An alternative solution could be to determine an internal unit of measure which you always store all values. Meters is a good candidate. Then create "translator" services which sit between your consumer class and the original class, which is responsible for converting all the values to a different format.

Garrott answered 10/3, 2010 at 21:47 Comment(4)
Can't even do it via Reflection?Valenevalenka
The best you could do would be to generate a new assembly with the attribute you want. But I don't know if would be any use.Richard
@Valenevalenka reflection is "read-only" - e.g. it can tell you information about types, including the information necessary to execute them, but you can't change them. As @Nader points out, you can generate a new assembly with the changes you want, but that probably doesn't solve any problems.Garrott
while attributes may well be part of the compiled metadata, that doesn't mean that there aren't runtime options. As it happens, there are at least 4 ways I can think of doing this.Godhood
W
0

It is a bit odd to set the unit in the display name, but if this is really what you want to do, then your only solution is to publish your properties with custom PropertyDescriptors (thanks to a TypeConverter or a custom Type Descriptor) and to override the DisplayName property.

Answered here too: Change DisplayName attribute for a property

Womack answered 11/3, 2010 at 0:32 Comment(0)
F
0

I don't know if this will work, but your DisplayName is an Attribute. Each class and each class' members may have attributes set. That said, it makes sens that the PropertyInfo will give you acces to this Attribute. Now, if you go this way and come accross PropertyInfo.GetCustomAttributes() or something like it, and you retrieve your Attribute value, is that it you say that is read-only as you said to Nick?

Ferrocene answered 11/3, 2010 at 0:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.