So, localizing display properties in the PropertyGrid has been done before. Probably ten different ways. However, until the .NET Framework 2.0 it required some messy implementation in the concrete class. Version 2.0 adds the TypeDescriptionProvider which can be tied to a particular class using the TypeDescriptionProviderAttribute. The attribute accepts a type that inherits the TypeDescriptionProvider base class.
To a certain extent the attribute limits the functionality of the TypeDescriptionProvider, because it forces the use of a parameterless constructor on the concrete provider type. There is also an option to register a provider manually using the TypeDescriptor.AddProvider method. This method will allow you to use a provider that requires parameters for the constructor.
However, in my design I wanted the provider to be attached to the class as an attribute. Above and beyond that my design decisions were all based on that attribute being the only “touch” on my class. I believe the rest of the implementation is pretty much the same as 1.1 except that there is now a CustomTypeDescriptor base class to inherit from instead of using the old ICustomTypeDescriptor interface. (That interface required you to implement a lot of junk; if you don't believe me create your own class by implementing that interface.
Here is the summary of my implementation:
- Create a concrete class from the PropertyDescriptor base class. This class receives a ResourceManager and is responsible for switching out the Category, Description, and DisplayName for a given PropertyDescriptor.
- Create a concrete class from the CustomTypeDescriptor base class. This class also receives the ResourceManager. It overrides the GetProperties methods and swaps out each PropertyDescriptor with a new localized PropertyDescriptor as defined in the first step.
- Create the concrete class from the TypeDescriptionProvider base class. Create the instance of the concrete CustomTypeDescriptor class from step 2 in the GetTypeDescriptor method.
- Add the TypeDescriptionProviderAttribute to my business class.
- Add the necessary resource strings to my resource file. The base resource strings are TypeNamePropertyNameCategory, TypeNamePropertyNameDescription, and TypeNamePropertyNameDisplayName.
This all went together very easily. However, I must warn you that I haven't tested it fully yet. In addition, there seems to be some caching functionality within the TypeDescriptionProvider base class that I need to look at utilizing. However, this is currently serving me well for a property dialog and performance isn't a problem with my class.
The worst part about this design is that it requires you to create a concrete provider class for each class that you want to attach the functionality to. In my situation that is acceptable and is better than the alternatives.
public sealed class LocalizedPropertyDescriptor : PropertyDescriptor
{
private PropertyDescriptor _descriptor;
private string _localCategory;
private string _localDescription;
private string _localDisplayName;
public LocalizedPropertyDescriptor (PropertyDescriptor baseDescriptor, ResourceManager manager)
: base(baseDescriptor)
{
if (baseDescriptor == null) throw new ArgumentNullException("baseDescriptor");
if (manager == null) throw new ArgumentNullException("manager");
_descriptor = baseDescriptor;
GetResources(manager);
}
private void GetResources(ResourceManager manager)
{
string prefix = _descriptor.ComponentType.Name + _descriptor.Name;
_localCategory= manager.GetString(prefix + "Category");
_localDescription = manager.GetString(prefix + "Description");
_localDisplayName = manager.GetString(prefix + "DisplayName");
}
// use the localized values; or the value defined by a custom attribute on the property if a
// value isn't available in the resource file
public override string Category { get { return (_localCategory != null ? _localCategory : _descriptor.Category); } }
public override string Description {
get { return (_localDescription != null ? _localDescription : _descriptor.Description); } }
public override string DisplayName {
get { return (_localDisplayName != null ? _localDisplayName : _descriptor.DisplayName); } }
// default implementation
public override Type ComponentType { get { return _descriptor.ComponentType; } }
public override bool IsReadOnly { get { return _descriptor.IsReadOnly; } }
public override Type PropertyType { get { return _descriptor.PropertyType; } }
public override bool CanResetValue(object component) { return _descriptor.CanResetValue(component); }
public override object GetValue(object component) { return _descriptor.GetValue(component); }
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); }
}
public sealed class LocalizedCustomTypeDescriptor : CustomTypeDescriptor
{
private ResourceManager _manager;
public LocalizedCustomTypeDescriptor (ICustomTypeDescriptor descriptor, ResourceManager manager)
: base(descriptor)
{
if (descriptor == null) throw new ArgumentNullException("descriptor");
if (manager == null) throw new ArgumentNullException("manager");
_manager = manager;
}
public override PropertyDescriptorCollection GetProperties()
{
return GetProperties(null);
}
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
PropertyDescriptorCollection localizedProperties = new PropertyDescriptorCollection(null);
foreach (PropertyDescriptor property in base.GetProperties(attributes))
{
LocalizedPropertyDescriptor localizedDescriptor = new LocalizedPropertyDescriptor (property, _manager);
localizedProperties.Add(localizedDescriptor);
}
return localizedProperties;
}
}
public sealed class MyObjectTypeDescriptionProvider : TypeDescriptionProvider
{
private TypeDescriptionProvider _baseProvider;
public MyObjectTypeDescriptionProvider() : base()
{
_baseProvider = TypeDescriptor.GetProvider(typeof(MyObject));
}
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
{
return new LocalizedCustomTypeDescriptor (_baseProvider.GetTypeDescriptor(objectType),
Resources.ResourceManager);
}
}
[TypeDescriptionProvider(typeof(MyObjectTypeDescriptionProvider))]
public class MyObject
{
// implementation here for business object
}