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.


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 the 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 in 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 of 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 localized 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. Then 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 additionally 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 purposes, 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
History
- 15th June, 2003: Initial version
License
This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.
A list of licenses authors might use can be found here.