Introduction
In my article 'Globalized property grid' I demonstrated how to localize the property names of an object. This article is based on this article incrementally.
Corresponding to questions I would like to address following topics here:
- Localizing category names
- Inheritance of globalized objects
Localizing category names were left out in my previous article although it is as much of interest. However, localization support of category names is limited as you will see below.
Another question I want to answer is regarding inheritance of objects. Although I answered the question on my previous article, I still want to address it here.
Let's start with category names first.
Category names
A property grid is able to organize the properties of an object in categories. You can assign a category to the properties of your objects by using an attribute of type CategoryAttribute.
Here is an example how to assign the property LastName to a category named Required Data:
[Category("Required Data")]
public string LastName { get {...}; set {...}; }
If no category attribute is assigned to a property, then a property will be assigned to a default category that you can find in a static property called CategoryAttribute.Default ("Misc").
CategoryAttribute has a property called Category that is used to access the category name.
If you use category names in a globalized property grid then it is wishful to have the category names localized, too.
Localizing category names
There is support included to localize a category name.
You cannot override the Category property to return a lolcalized string. Instead, the first time the Category property is accessed CategoryAttribute will call an overridable method called GetLocalizedString. This method has a protected scope and is overridable. Derive your own class from CategoryAttribute and override GetLocalizedString. You get the assigned category name (see "Required Data" in the example above) as a parameter and may return a localized string instead:
[AttributeUsage(AttributeTargets.Property, AllowMultiple=false,
Inherited=true)]
class GlobalizedCategoryAttribute : CategoryAttribute
{
string table = "";
...
protected override string GetLocalizedString( string value )
{
string baseStr = base.GetLocalizedString( value );
ResourceManager rm =
new ResourceManager(table, Assembly.GetExecutingAssembly() );
try
{
return rm.GetString(value);
}
catch( Exception )
{
return value;
}
}
}
Use this class to decorate a property:
[GlobalizedCategory("Required")]
public string LastName { get {...}; set {...}; }
GlobalizedCategoryAttribute uses the original category names as a symbolic name for accessing the resources. The member table optionally specifies the resource table and may be set either explicitly. If no resource table name is provided, the class name will be used as the table name (see sample).
But there is a big drawback in the implementation of CategoryAttribute provided by the .NET framework (version 1.0).
GetLocalizedString is only called once on the first access. The consequence is we are able to select a language on startup only. The it will work fine. And maybe this will be sufficient for a lot of applications. In case of switching the language at runtime, it will not work. I demonstrate this issue in the sample provided. On startup you can select a language. you'll see the property and category names are displayed in the selected language. Pressing the button on the dialog to select another language works for property names only, category names don't change.
I don't know if the behaviour of the CategoryAttribute class has changed in .NET Framework 1.1. Maybe the community can answer.
Let's get over to the second topic to address.
Inheritance of globalized objects
In the sample of my previous article (Globalized property grid) I just used one class called Person inheriting the GlobalizedObject. Using the defaults the localized property names were extracted from the resource file Person.resx or Person.en.resx. If you have tried the sample code and enhanced it by deriving from Person class, then the property names of the derived classes may not be shown. Even though you have placed localized strings in the resource file person.resx.
The answer is relatively simple. By default, the class name will be used to access a localized resource. That means, The person class gets its localized names from person.resx. So, a derived class Employee gets its localized names from Employee.resx.
If you want to use one resource file only, you have to tell it explicitly. Here two options are possible.
Using the GlobalizedPropertyAttribute class where you can explicitly name the resource table to use on every property individually. Referring to the person.resx resource table may be done as follows:
[GlobalizedProperty("",Table="GlobalizedPropertyGrid.Person")]
public string Department
{
get { return department; }
set { department = value; }
}
Alternatively, an attribute class may be additional used to define the resource table name on the class level. This is demonstrated by a class GlobalizedObjectAttribute, which contains one property only that specifies a resource table to access. For example, to access a resource table named SpecialStringTable use it as follows:
[GlobalizedObjectAttribute("GlobalizedPropertyGrid.SpecialStringTable")]
public class Employee : Person
{
...
The class GlobalizedPropertyDescriptor detects the resource table name by using the following sequence (see implementation of the properties DisplayName and Description):
- Check, if there's a
GlobalizedPropertyAttribute on property level.
If yes, additionally check if there is a resource table specified.
- If no resource table name provided yet, check, if a
GlobalizedObjectAttribute is provided on the class level.
- If still no resource table name provided yet, use the class name.
Creating a sample project
For demonstration purpose I have extended the sample project. For a detailed description of the sample project refer to the previous article (Globalized property grid). There are only some modifications and additions:
Demonstrating localization of categories
I have added an attribute class called GlobalizedCategoryAttribute (Attributes.cs) to localize category names. This attribute is applied to the properties as mentioned above. To demonstrate that selecting a language is possible on startup only, I have modified the application a bit. On startup a dialog box will be displayed to select a language.

Selecting a language (e.g. English) at this time works for localized category names. The category and property names will be displayed in the selected language in the property grid on the main form.

Switching the current language (e.g. to German) by pressing the button in the upper right corner on the main form doesn't affect the category names. Property names have changed correctly.

Maybe this limited behaviour of the CategoryAttribute class will change in future releases.
Demonstrating localization of properties in case of inheritance
I have added an attribute class called GlobalizedObjectAttribute (Attributes.cs) to specify a custom resource table name on class level.
I also have added a class Employee (Employee.cs) inheriting the Person class to demonstrate inheritance.
</SUMMARY>
public class Employee : Person
{
private string department = "";
public Employee()
{
}
public string Department
{
get { return department; }
set { department = value; }
}
}
This class will be instantiated by the Form1 class:
Employee emp = new Employee();
emp.FirstName = "Max";
emp.LastName = "Headroom";
emp.Age = 42;
emp.Department = "Channel5";
this.person = emp;
Employee.resx and Employee.en.resx have been to have the property names of Employee displayed correctly.

Summary
This article is an incremental version of its base article Globalized property grid . It discusses the localization of category names and localized property names in case of inheritance.
To localize the category names there are two steps necessary:
- Create a class and derive it from
CategoryAttribute and override the method GetLocalizedString. This method should retrieve the localized string from a resource table.
- Add this attribute to your properties. Provide a name for the category that will be used as a key to the resource string table.
But note:CategoryAttribute only reads the localized string on the first access of the category name. Switching languages at runtime is not possible.
Supporting inherited classes based on the sample provided requires to either define a resource string table for each class with the name of the class or referring to a certain resource table by explicit usage of custom attributes: GlobalizedPropertyAttribute on property level or GlobalizedObjectAttribute on class level.
References