Support of MetadataType?

Grids for WPF Forum

Posted 10 years ago by Sascha Schwegelbauer
Version: 9.1.0505
Avatar
Hi together,

I'm using auto generated (from xsd) partial classes.
Now I'd like to add attributes to them, so that property grid can categorize, make readonly and so on.
I'm trying to do it via the new MetadataType Attribute, but PropertyGrid ignores it?
How can I make PropertyGrid obey the addon-attributes?

Sample Code:

[MetadataType (typeof (BookingMetadata))]
public partial class Booking
{ // This is my custom partial class which is used by propertygrid
}

public class BookingMetadata{ 

[Required, StringLength(15)] 
public object ClientName { get; set; } 

[Range(1, 20)] 
public object NumberOfGuests { get; set; } 

[Required, DataType(DataType.Date)] 
public object ArrivalDate { get; set; }
}
[Modified at 10/01/2009 09:09 AM]

Comments (3)

Posted 10 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Sascha,

You need to use a custom TypeDescriptionProvider, but fortunately there's a built-in one called AssociatedMetadataTypeTypeDescriptionProvider. You should be able to add the following static constructor to your Booking class to register it:
static Booking() {
    TypeDescriptor.AddProvider(new AssociatedMetadataTypeTypeDescriptionProvider(typeof(Booking)), typeof(Booking));
}


Actipro Software Support

Posted 10 years ago by Sascha Schwegelbauer
Avatar
Hi,
I'm sorry, but your code has no effect :-(
What else could I try?
Posted 10 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Sascha,

Sorry, it looks like the TypeDescriptor provided by Microsoft doesn't completely implement the needed TypeDescriptor overrides. Specifically, it should override GetProperties() *and* GetProperties(Attribute[]). But it currently only implements the latter.

You can work around this by extending the classes they provide. In addition, you can have it automatically check the validation before (or after) the user sets the value via the PropertyGrid.

The code is as follows:
/// <summary>
/// Represents an extension of AssociatedMetadataTypeTypeDescriptionProvider that wraps the
/// TypeDescriptor returned with a custom TypeDescriptor.
/// </summary>
public class MyAssociatedMetadataTypeTypeDescriptionProvider : AssociatedMetadataTypeTypeDescriptionProvider {

    public MyAssociatedMetadataTypeTypeDescriptionProvider(Type type)
        : base(type) {
        // No-op
    }

    public MyAssociatedMetadataTypeTypeDescriptionProvider(Type type, Type associatedMetadataType)
        : base(type, associatedMetadataType) {
        // No-op
    }

    private ICustomTypeDescriptor Descriptor {
        get;
        set;
    }

    public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) {
        if (null == this.Descriptor)
            this.Descriptor = new MyCustomTypeDescriptor(base.GetTypeDescriptor(null, null));
        return this.Descriptor;
    }
}

/// <summary>
/// Represents a custom TypeDescriptor that wraps the real TypeDescriptor, but overrides
/// both GetProperties() *and* GetProperties(Attribute[]).
/// </summary>
public class MyCustomTypeDescriptor : CustomTypeDescriptor {

    public MyCustomTypeDescriptor(ICustomTypeDescriptor wrappedTypeDescriptor) {
        this.WrappedTypeDescriptor = wrappedTypeDescriptor;
    }

    private ICustomTypeDescriptor WrappedTypeDescriptor {
        get; set;
    }

    public override AttributeCollection GetAttributes() {
        return this.WrappedTypeDescriptor.GetAttributes();
    }

    public override PropertyDescriptorCollection GetProperties() {
        PropertyDescriptorCollection properties = this.WrappedTypeDescriptor.GetProperties();

        List<PropertyDescriptor> list = new List<PropertyDescriptor>();
        foreach (PropertyDescriptor descriptor in properties)
            list.Add(new MyPropertyDescriptor(descriptor));

        return new PropertyDescriptorCollection(list.ToArray(), true);
    }

    public override PropertyDescriptorCollection GetProperties(Attribute[] attributes) {
        return this.GetProperties();
    }
}

/// <summary>
/// Represents a custom PropertyDescriptor that wraps the real PropertyDescriptor, and enforces
/// any ValidationAttributes defined on the associated property.
/// </summary>
public class MyPropertyDescriptor : PropertyDescriptor {

    public MyPropertyDescriptor(PropertyDescriptor wrappedPropertyDescriptor)
        :base (wrappedPropertyDescriptor){
        this.WrappedPropertyDescriptor = wrappedPropertyDescriptor;
    }

    private PropertyDescriptor WrappedPropertyDescriptor {
        get;
        set;
    }

    public override void AddValueChanged(object component, EventHandler handler) {
        this.WrappedPropertyDescriptor.AddValueChanged(component, handler);
    }

    public override bool CanResetValue(object component) {
        return this.WrappedPropertyDescriptor.CanResetValue(component);
    }

    public override Type ComponentType {
        get {
            return this.WrappedPropertyDescriptor.ComponentType;
        }
    }

    public override bool IsReadOnly {
        get {
            return this.WrappedPropertyDescriptor.IsReadOnly;
        }
    }

    public override object GetValue(object component) {
        return this.WrappedPropertyDescriptor.GetValue(component);
    }

    public override Type PropertyType {
        get {
            return this.WrappedPropertyDescriptor.PropertyType;
        }
    }

    public override void RemoveValueChanged(object component, EventHandler handler) {
        this.WrappedPropertyDescriptor.RemoveValueChanged(component, handler);
    }

    public override void ResetValue(object component) {
        this.WrappedPropertyDescriptor.ResetValue(component);
    }

    public override void SetValue(object component, object value) {
        List<Attribute> attributes = new List<Attribute>();
        this.FillAttributes(attributes);

        foreach (Attribute attribute in attributes) {
            ValidationAttribute validationAttribute = attribute as ValidationAttribute;
            if (null == validationAttribute)
                continue;

            if (!validationAttribute.IsValid(value))
                throw new ValidationException(validationAttribute.ErrorMessage, validationAttribute, component);
        }

        this.WrappedPropertyDescriptor.SetValue(component, value);
    }

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

    public override bool SupportsChangeEvents {
        get {
            return this.WrappedPropertyDescriptor.SupportsChangeEvents;
        }
    }
}
You would again apply this via a static constructor like so:
static Booking() {
    TypeDescriptor.AddProvider(new MyAssociatedMetadataTypeTypeDescriptionProvider(typeof(Booking)), typeof(Booking));
}
If you do not want the validation to be automatically applied, you can remove MyPropertyDescriptor and update MyCustomTypeDescriptor.GetProperties() to simply return the wrapped properties.

Finally, if you want the value of the underlying object to be set before the validation is applied, then you would need to move "this.WrappedPropertyDescriptor.SetValue(component, value)" to the top of the SetValue method in MyPropertyDescriptor.

Hope this helps.


Actipro Software Support

The latest build of this product (v2019.1 build 0683) was released 1 month ago, which was after the last post in this thread.

Add Comment

Please log in to a validated account to post comments.