|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionThe property grid is a nice control to display properties and values. You create an instance of your class and assign it to the property grid.
By using reflection a property grid extracts the properties of the class and displays its values.
So how to handle these requirements? Fortunately there is a excellent support for international software in .NET integrated. Even so it is possible to customize the displaying of the property names and descriptions. Now let us see how to apply these stuff. Globalization and LocalizationFirst, let's have a short look on developing international software with .NET. It is a process that mainly takes two steps: Globalization and Localization. Simply defined:Globalization means the process of preparing your code to be able to support different languages. This is done by eliminating language or culture dependencies from your code to become culture-neutral. That is to avoid using hardcoded strings or message to be displayed to the user. Localization means the process of separation of regional settings from the application code. Instead provide them separately as resources. .NET has a bunch of classes integrated to support the development of international software. These classes are located in the namespaces Thread.CurrentThread.CurrentUICulture = new CultureInfo("de");
The example sets German as the current language. The languages identifiers are standard by ISO 639-1.
The application resources are requested by using an instance of ResourceManager. The resource manager uses the currently set CultureInfo object to access the correct local resources.ResourceManager rm = new ResourceManager("MyStringTable",this.GetType().Assembly);
string message = rm.GetString ("MyMessage");
The example accesses the string named 'MyMessage' from the stringtable named 'MyStringTable'.
We will use this support later on when we are localizing the property names to be displayed by the property grid. But before let us define a sample project for demonstration purpose. Creating a sample projectFor demonstration purpose select a windows application as a new project type. Use the main form of type Additionally define a test class that provides some properties to be displayed in the property grid. Select "Add class..." and add a c# class named Person.cs to the project. The test class models a person and should look like this: // Person is the test class defining three properties: first name, last name and age.
public class Person : GlobalizedObject
{
private string firstName = "";
private string lastName = "";
private int age = 0;
public Person() {}
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
public int Age
{
get { return age; }
set { age = value; }
}
}
Now we are prepared for an initial version.
Initial versionAn instance of private void Form1_Load(object sender, System.EventArgs e)
{
// Instantiate test class and set some data
person = new Person();
person.FirstName = "Max";
person.LastName = "Headroom";
person.Age = 42;
// Assign to property grid
PropertyGrid1.SelectedObject = person;
}
After compiling the initial version displays the public properties of the person class in the property grid with property name and value. The displayed property name matches exactly the name of the
property name of the class. The property 'LastName' is displayed as 'LastName'.
That is fine for startup. Now let us customize the displaying of property names. Localizing property namesTo customize how properties are displayed, By default, there is the property name of the class returned as display name and an empty string as description.
The base class is called /// <summary>
/// GlobalizedObject implements ICustomTypeDescriptor.
/// The main task of this class is to instantiate our
// own specialized property descriptor.
/// </summary>
public class GlobalizedObject : ICustomTypeDescriptor
{
...
Our implementation overrides GetProperties() only and creates a collection of custom property descriptors of type GlobalizedPropertyDescriptor and returns them to the caller instead of the default ones.
public PropertyDescriptorCollection GetProperties()
{
// Only do once
if ( globalizedProps == null)
{
// Get the collection of properties
PropertyDescriptorCollection baseProps =
TypeDescriptor.GetProperties(this, true);
globalizedProps = new PropertyDescriptorCollection(null);
// For each property use a property descriptor of our own that is able to
// be globalized
foreach( PropertyDescriptor oProp in baseProps )
{
// create our custom property descriptor and add it to the collection
globalizedProps.Add(new GlobalizedPropertyDescriptor(oProp));
}
}
return globalizedProps;
}
The rest of the methods are delegating the call to the .NET class TypeDescriptor providing static methods for default type information.
The custom property descriptor class /// <summary>
/// GlobalizedPropertyDescriptor enhances the base class bay obtaining the
/// display name for a property
/// from the resource.
/// </summary>
public class GlobalizedPropertyDescriptor : PropertyDescriptor
{
private PropertyDescriptor basePropertyDescriptor;
private String localizedName = "";
private String localizedDescription = "";
public GlobalizedPropertyDescriptor(PropertyDescriptor basePropertyDescriptor) :
base(basePropertyDescriptor)
{
this.basePropertyDescriptor = basePropertyDescriptor;
}
...
The focus of interest are the properties DisplayName and Description. DisplayName will be
overridden to return a
string obtained from resources.
public override string DisplayName
{
get
{
// Build the resource string table name. This sample uses the class name
// prefixed by the namespace.
string tableName = basePropertyDescriptor.ComponentType.Namespace + "." +
basePropertyDescriptor.ComponentType.Name;
// Build the resource identifier. This sample uses the default property name
string displayName = this.basePropertyDescriptor.DisplayName;
// Now use resource table name and string id to access the resources.
ResourceManager rm = new ResourceManager(
tableName,basePropertyDescriptor.ComponentType.Assembly);
// Get the string from the resources.
// If this fails, then use default display name (usually the property name)
string s = rm.GetString(displayName);
// Store the localized display name
this.localizedName = (s!=null)? s : this.basePropertyDescriptor.DisplayName;
return this.localizedName;
}
}
The implementation of DisplayName uses the class name as a resource string table name and the property name as the string identifier by default.
The implementation of property public class Person : GlobalizedObject
Okay, having done this, our code is prepared to be globalized. Now let us do localization. We define resources for supported languages and make them selectable.
Defining resourcesOur sample will support two languages, English and German. For each language and class we add an assembly resource file to our sample project.
Due to the fact that we
use the class name as resource table name, we have to name the resource file same as the class:
Note: It is good to have a default language integrated into your application. In our case I have used
German, so I name the German resource file
There are two entries for each property, name and description. Now that we have different sets of resources and globalized code that is able to extract strings to be displayed depending on the current language. Our code should be updated to allow switching between supported languages. Switching of current languageThe only thing left is to make the two languages selectable. First we construct an array of supported languages in the constructor of the main form. We use the ISO 639-1 standard format for identifying languages as used by .NET:en for English, and de for German should be enough for this sample. Also, an instance of Person is created here.
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
supportedLanguages = new string[2];
supportedLanguages[0] = "en";
supportedLanguages[1] = "de";
// Instantiate test class and set some data
person = new Person();
person.FirstName = "Max";
person.LastName = "Headroom";
person.Age = 42;
}
Second, we add a combobox named cbLang to the main form. This combo box is filled with the displayable language names. The displayable language names are obtained by
using a CultureInfo object. Moreover the Person object is assigned to the property grid. We use the form load event handler for this.
private void Form1_Load(object sender, System.EventArgs e)
{
// Setup combo box with languages available
cbLang.Items.Insert(0,
(new CultureInfo(supportedLanguages[0])).DisplayName);
cbLang.Items.Insert(1,
(new CultureInfo(supportedLanguages[1])).DisplayName);
// Preselect the first one
cbLang.SelectedIndex = 0;
// Assign person to property grid
PropertyGrid1.SelectedObject = person;
}
Last but not least, we define an event handler to be notified when the selected index changes in the combo box because we want to change the current language.
We inform the current thread about the new current language by setting its static property
CurrentUIThread to a new instance of CultureInfo initialized
with the ISO 639-1 name. After setting the new language a refresh of the property grid is necessary.
private void cbLang_SelectedIndexChanged(object sender, System.EventArgs e)
{
// get the language selected from combo box
int lang = cbLang.SelectedIndex;
if( lang == -1 )
return;
// Set selected language as the current one
Thread.CurrentThread.CurrentUICulture = new CultureInfo(supportedLanguages[lang]);
// Refresh displayed properties
PropertyGrid1.Refresh();
}
That is a basic version that demonstrates how to display custom property names and descriptions. In the sample code there is one enhancement provided.
EnhancementThe default selection of the resource string is by the class name as string table name and the property name as the string definition. This can be superposed by using a .NET attribute.
The attribute [GlobalizedProperty("Surname",Description="ADescription",
Table="GlobalizedPropertyGrid.MyStringTable")]
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
The example defines the display name for the property name LastName can be found in stringtable GlobalizedPropertyGrid.MyStringTable and the string is identified by
Surname, the optional description text is identified by ADescription.
To get this enhancement working an addition has to be made to the public override string DisplayName
{
get
{
// First lookup the property if GlobalizedPropertyAttribute
// instances are available.
// If yes, then try to get resource table name and display name
// id from that attribute.
string tableName = "";
string displayName = "";
foreach( Attribute oAttrib in this.basePropertyDescriptor.Attributes )
{
if( oAttrib.GetType().Equals(typeof(GlobalizedPropertyAttribute)) )
{
displayName = ((GlobalizedPropertyAttribute)oAttrib).Name;
tableName = ((GlobalizedPropertyAttribute)oAttrib).Table;
}
}
// If no resource table specified by attribute, then build it itself by
// using namespace and class name.
if( tableName.Length == 0 )
tableName = basePropertyDescriptor.ComponentType.Namespace + "." +
basePropertyDescriptor.ComponentType.Name;
// If no display name id is specified by attribute, then construct it by
// using default display name (usually the property name)
if( displayName.Length == 0 )
displayName = this.basePropertyDescriptor.DisplayName;
// Now use table name and display name id to access the resources.
ResourceManager rm = new ResourceManager(
tableName,basePropertyDescriptor.ComponentType.Assembly);
// Get the string from the resources.
// If this fails, then use default display name (usually the property name)
string s = rm.GetString(displayName);
this.localizedName = (s!=null)? s : this.basePropertyDescriptor.DisplayName;
return this.localizedName;
}
}
This enhancement is commented out in the sample project. Remove the comment and recompile to see it working.
SummaryThese are the steps to localize the names and descriptions displayed in the property grid:
References
ContributesThis code is inspired by a fellow who showed how to provide user friendly names using vb code. I have adopted his work, using C# instead and prepared the code to be international and meet some recurrent real world requirements. | ||||||||||||||||||||