Welcome to WindowsClient.net | Sign in | Join


Improving Performance of Localized Forms

by Brian Pepin

Get the samples for this article

If anyone has had experience with the automatic form localization provided for Windows Forms in Visual Studio .NET, you know that it is a convenient way to provide a rich client user interface in multiple languages.  If you are creating complex forms, however, you may be dismayed to find that this wonderful localization support comes at a price:  startup performance.  A form that is localized loads and displays slower than one that is not localized.  To understand why this is, let’s focus for a bit on what actually happens when you set the Localizable property of the form to True.

Primer to Windows Forms Localization

If you’ve ever looked at the generated source code for a form both before and after the Localizable property is set to true, you’ll see that there is a big difference in the way the form code is written. When localization is turned off, code for a simple form looks something like this:

	private void InitializeComponent() {
		this.Text = “Hello, World”;
		this.Size = new Size(300, 300);
		this.Name = “Form1”;
	}

When localization is turned on, however, many of the form’s properties are read out of a resource file:

	private void InitializeComponent() {
		ResourceManager rm = new ResourceManager(typeof(Form1));
		this.Text = rm.GetString(“Form1_Text”);
		this.Size = (Size)rm.GetValue(“Form1_Size”);
		this.Name = “Form1”;
	}

Notice that while the Text and Size properties were accessed through the resource manager, the Name property wasn’t.  This is because Name is not a localizable property.  Its value is the same regardless of the user’s language, so it is skipped.  But how does the code generator know which properties to localize?  Controls can provide this information by adorning their properties with the Localizable attribute.  If the attribute is present on a property and set to True, the property will be localized.

Performance Issues with Localization

If you did look at the difference between a localized and a non-localized form, there is one other thing you would notice.  For the non-localized form, only those properties that differ from the form’s default values are written to code.  For localized forms, however, all of the localized properties are written out, even if they contain their default values.  Why?  Well, the answer is, “just in case”.  All the properties need to be written out just in case a value differs based on culture and needs to deviate from the default value.  Let’s look at the Font property for example.  In nearly all cases, the default font for the operating system will work fine for any language.  In Chinese, however, the default font for the operating system is too small.  It must be replaced with a larger, newer version called New Ming Li.  To do this, the Chinese RESX file would have a font declared for New Ming Li.  But, if there wasn’t a statement in code that assigned the value in the RESX file into the form’s Font property, the font wouldn’t actually change.  This is why Microsoft writes all values into the form when localization is turned on:  just in case a value needs to be set in a RESX file.

All this additional information is what slows down the form’s loading time when it is localized.  For many forms, the difference is small and not noticeable.  For forms with a lot of complex graphics or many controls, the difference in performance can be noticed.

Possible Solution?

Microsoft had to account for every possible scenario when deciding what properties to make as localized.  Because of this, most of the properties on controls and forms are localizable.  In many applications, however, most of these properties really don’t need to be localized.  Text strings, sizes and locations of controls and fonts are typically the only properties that really need to be localized in an application.  If it were possible for developers to change which properties the controls offer as localizable, developers could custom tailor the set of localizable properties to fit their application.  They could pick and choose which properties should be localized based on their application’s needs, and gain performance because properties their application never needs to localize do not go into the resource file.  Great idea; now all we need is a way to implement it.

The Localization Filter

The solution I’m proposing here is a component you can place on a form.  This component maintains a list of properties you need to localize for the form.  Only these properties would be localized by the code generator, and the properties can be modified in the forms designer by updating a collection.  Before I dig into the details of writing such an object, let’s take a quick peek of what it looks like in action.

Below is a picture of a form that contains an instance of my “localization filter” component.  The only visual evidence of the filter is a component in the form’s component tray.  I have setup the localization filter to filter make the following properties localizable:  Location, Size, Text and Font by clicking on the LocalizableProperties property in the property window:



Besides this component in the tray that has a list of property names, there isn’t any other user interface to my localization filter component.  The localization filter is silently working under the covers, however, to change the way all the other objects on the form generate code.  Let’s look at the generated code for button1:

	this.button1.Font = ((System.Drawing.Font)(resources.GetObject("button1.Font")));
	this.button1.ImeMode = System.Windows.Forms.ImeMode.NoControl;
	this.button1.Location = ((System.Drawing.Point)(resources.GetObject("button1.Location")));
	this.button1.Name = "button1";
	this.button1.Size = ((System.Drawing.Size)(resources.GetObject("button1.Size")));
	this.button1.TabIndex = 0;
	this.button1.Text = resources.GetString("button1.Text");

This isn’t far from the code generated for a button when localization is turned off.  Only a few key properties – those properties listed in the localization filter’s Localizable Properties dialog – have been localized.  The rest of the properties have been stored in normal code statements.  This can provide a real savings in terms of form load time and binary size for large, complex forms.  In the next section I’ll take a look at how the localization filter component works.

The Plumbing

Writing a component like the localization filter isn’t as hard as it sounds.  The designer architecture that the Windows Forms designer is built on top of provides all the hooks and knobs we need.  First, I should list the steps involved in creating my localization filter class:

  1. I need a runtime class, called LocalizationFilter, that derives from Component so I can place it on a form.
  2. Next, this class needs to offer my LocalizableProperties collection so that I can pick and choose what properties I want to localize.
  3. Something, somewhere, must read this collection and change the way the form designer generates code.

The first two steps don’t sound very hard.  I’m going to start off by designing a new component, called the LocalizationFilter, which is responsible for choosing which properties are localized.  Here is the complete implementation of LocalizationFilter:

	[Designer(typeof(FilterDesigner))]
	public sealed class LocalizationFilter : Component {
	}

As you can see, there’s not much to it.  The localization component still needs to be on the form, even in runtime, so I want to make it as simple as possible.  Therefore, I will place all of the filter’s logic in another class, called a designer.

LocalizationFilter’s behavior comes from its designer.  A designer is simply a class that implements IDesigner and is associated with any component through a designer attribute.  Here is the class definition for LocalizationFilter’s designer, which I am calling FilterDesigner:

	internal sealed class FilterDesigner : ComponentDesigner {
	}

Notice that FilterDesigner is declared as internal.  Unless you have a need to expose your designer to end users, you should make it internal.  The forms designer will still be able to create an instance of it.  FilterDesigner also derives from ComponentDesigner.  This base class implements IDesigner for me so I can concentrate on my own features rather than worry about a bunch of plumbing. 

Now that I’ve defined my FilterDesigner class, I need to define the properties it will expose.  I want my LocalizationFilter to offer a collection of strings through a property called “LocalizableProperties”.  While I could have declared this property on my LocalizationFilter component itself, that would require the property to be saved in the user’s source code.  I want the smallest possible runtime size for my LocalizationFilter component, so I would rather not define this property on my component.  Also, since the collection of strings this property contains is only valuable when a form is loaded in the designer, I’d be better off leaving my LocalizableProperties collection off my LocalizationFilter component.  So, where else can I define my collection if I want it to show up in the property window at design time?  Why, on the FilterDesigner, of course!  Here’s what the code looks like, with the new property code in bold:

	internal sealed class FilterDesigner : ComponentDesigner {
		
		private StringCollection localizablePrpoperties;
		 
		private StringCollection LocalizableProperties {
			get {
				if (localizableProperties == null) {
					localizableProperties = new StringCollection();
				}
				return localizableProperties;
			}
		}
		
	}

If you run this code now you’ll be disappointed.  The LocalizableProperties property won’t show up in the property window when you select the LocalizationFilter component.  That’s because the property window browses properties on the component, not the designer.  Somehow, I’ve got to add this property to the set of properties offered by the LocalizationFilter component.  The .NET Framework’s designer support allows me to do this through a technique called property filtering.  Property filtering is a feature that is built into the .NET Framework that allows objects to change properties that are exposed by other objects.  One object can add, remove or change the implementation of another object’s properties with property filtering.  Let’s take a look at how property filtering works in detail.

Property Filtering

Property filtering starts at an object called TypeDescriptor.  TypeDescriptor is a single point where developers can ask about an object’s properties.  The property window uses TypeDescriptor when it wants to display a set of properties for a component.  TypeDescriptor, always looks for a filtering service, called ITypeDescriptorFilterService, on each component it sees.  If it finds this service, it builds the set of properties for the component, and then hands these properties to the filter service.  The filter can then make changes.

This filtering technique is used extensively at design time to control which properties are exposed in the properties window.  Visual Studio .NET implements ITypeDescriptorFilterService and passes on the filter request to the designer associated with a component.  Graphically, the process looks something like this:



If all that sounds complicated, don’t worry.  Visual Studio .NET is doing all of the hard work.  All I’ve got to do to get the property window to display my LocalizableProperties property is add it to my designer’s filter.  I can do this by overriding the protected FilterProperties method on my FilterDesigner class:

	internal sealed class FilterDesigner : ComponentDesigner {
	
		protected override void PreFilterProperties(IDictionary properties) {
			PropertyDescriptor newProperty;
			base.PreFilterProperties(properties);
			
			newProperty = TypeDescriptor.CreateProperty(typeof(FilterDesigner),
				“LocalizableProperties”, typeof(StringCollection), null));
			properties.Add(“LocalizableProperties”, newProperty);
		}
	
	}

I’m doing quite a lot in this method.  Let’s examine each line:

base.PreFilterProperties(properties);

The first thing I’m doing is invoking the base class’s version of this method.  There are two filter methods on ComponentDesigner:  PreFilterProperties and PostFilterProperties.  PreFilter should be used to add new properties.  PostFilter should be used to hide existing properties.  By following this convention multiple layers of derived designers can all filter properties without bumping heads.  The main rule for PreFilter is that I must always call base as the first line in my PreFilter method.  The reverse holds true for the PostFilter method:  I should always call base last. So, just to follow convention, I’m calling the base version of PreFilterProperties as my first line of code.

newProperty = TypeDescriptor.CreateProperty(typeof(FilterDesigner), 
	“LocalizableProperties”, typeof(StringCollection), null));

This rather verbose line of code actually creates a new property.  While I defined the property in code, TypeDescriptor needs to somehow know about that property.  I could have called TypeDescriptor’s GetProperties method and just passed in my designer, but that would have found all the public properties on my designer.  I defined LocalizableProperties as private, however, so even this wouldn’t find the property.  Why private?  I think it’s just good practice.  I am creating this special property specifically with the intent to add it to another object through this special filtering mechanism.  Keeping it private prevents me from accidentally using it elsewhere, and helps me remind myself what it is for.  Also, if I made it public and used TypeDescriptor.GetProperties to return the properties of the designer, there is always the chance that a quick slip of the “public” keyword could add additional properties to the property window that I didn’t intend.  Instead, I’m calling TypeDescriptor’s CreateProperty method.  Properties that are explicitly created this way can stay private to my object.  To create a property I need to pass a bunch of parameters that describe the property.  The parameters to CreateProperty include the following:

·         The data type the property is defined on: FilterDesigner

·         The name of the property: LocalizableProperties

·         The data type of the property:  StringCollection

·         Any additional attributes:  null, because I don’t need anything special.

After calling this method, I have a fresh new PropertyDescriptor object that describes my LocalizableProperties property.  All I’ve got to do is add this to the filter, which is the last line of code:

properties.Add(“LocalizableProperties”, newProperty);

The filter passed me a dictionary.  The keys to this dictionary are property names, and the values are property descriptors.  I’ve got my property descriptor, and I know I want to name my property “LocalizableProperties”, so I just pass these two arguments to the dictionary’s Add method.

That’s all there is to it.  Now when I place my LocalizationFilter component on a form, I see my new LocalizableProperties property sitting in the property window.   But, there’s one small problem:  values I set for my LocalizationFilter property are saved into source code, and because the property doesn’t actually exist, I get a bunch of compile errors when I build the project.  We’ll tackle this in the next section, Design Time Attributes.

Design Time Attributes

When I added my LocalizableProperties collection I got an unintended surprise:  the collection was saved in source code.  But I don’t want this; I want this collection to only be saved at design time, because the collection doesn’t actually exist.  I can do this by placing an attribute on my property called DesignOnlyAttribute:

	[DesignOnly(true)]
	private StringCollection LocalizableProperties { get; }

By placing this attribute on my property, the value of the property will be saved into a design time resource file, rather than the user’s source code.  Great!  But, doing this alone isn’t enough.  Design time properties must be saved into a resource file, and because of this they have additional restrictions:  they must be “simple” read-write properties, and their values must be serializable.  Otherwise, there is no way to store their value in a resource file.  This means that by adding the DesignOnlyAttribute here my property won’t be saved into code, but it won’t be saved anywhere else, either. 

There are several things I could do to make this property work as a design only property.  I could change it to an array and make the property read-write.  That would allow it to be saved into the design time resource file, but it would also make it look like an array in the property window, which isn’t want I want.  I want to make it look like a collection.  I’ll solve this problem by introducing another property on my designer.  One property, LocalizableProperties, I’ll keep as a collection and display it to the user in the property window.  A new property, LocalizablePropertiesArray, I’ll hide from the user.  It’s this second property that I’ll write out into the design time resource file.  Both properties access the same collection in different ways; one as a collection, another as an array.  I’ve shown the property descriptions and attributes below:

	[DesignOnly(true)]
	[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
	private StringCollection LocalizableProperties { get; }
	[DesignOnly(true)]
	[Browsable(false)]
	private string[] LocalizablePropertiesArray { get; set; }

I’ve added a couple of new attributes here, which I’ll summarize below:

BrowsableAttribute is used to show or hide a property in the property window.  If not provided the default value is true.  Here, I am hiding my LocalizablePropertiesArray property from the user’s view.

DesignerSerializationVisibilityAttribute is used to control how the designer serializes properties.  The default value is “visible”, which means that the designer should try to serialize the property.  Here I’ve set the value to “hidden” which means that the property should never be serialized by the designer.

With these changes my collection of localizable properties will be correctly written to the design time resource file.  The last thing I want to do is provide a custom editor so the property window will have a nice user interface.  I can do this through the use of an EditorAttribute.  Since there are already quite a few articles covering editors, I won’t go into detail about my simple string editor here.  If you’re curious just take a peek at the project code.

Well, that just about wraps it up.  Except, of course, for my third step above:

3. Something, somewhere, must read this collection and change the way the form designer generates code.

That’s worthy of a whole section, however.

Making Changes to Code Generation

I described earlier that the Visual Studio .NET code generator uses the Localizable attribute on a property to decide what type of statement to create to assign the property’s value.  For example, given the two properties on an object:

	[Localizable(true)]
	public string AProp { get; set; }
	public string BProp { get; set; }
	The following code would be generator for an object named “MyComponent”:
	ResourceManager rm = new ResourceManager(typeof(Form1));
	MyComponent.AProp = rm.GetString(“MyComponent_AProp”);
	MyComponent.BProp = “Hello”;

What I want to do is dynamically change the value of the Localizable attribute on various properties based on the strings contained in my LocalizableProperties collection.  I can’t do this by overriding the Pre or Post filter methods of a designer because I need to modify the attributes on components I don’t own.  I need some place where I can modify the metadata for all components on the design surface.  If you remember back to the section on property filtering, you’ll remember that there is a service called ITypeDescriptorFilterService which is responsible for filtering metadata for components.  This service is provided by Visual Studio .NET and isn’t implemented on a public class.  However, I can use a technique called “service chaining” to replace Visual Studio .NET’s version of this service with my own.  I have to be very careful when doing this; one wrong slip or bug could mean curtains for the form designer.  The steps for creating a service chain our outlined below:

  1. Call GetService for IServiceContainer.
  2. Call GetService for the type of service you want to replace.
  3. Create a new object that implements the service interface, passing the original service into the new object.
  4. Call RemoveService on the service container to remove the existing service.
  5. Call AddService on the service container.
  6. Remember to put the original service back when you’re disposed.

Once I create an object that implements ITypeDescriptorFilterService and add it into the service container, my object will be the object that TypeDescriptor queries to filter metadata.  Inside my implementation of this filter service I will modify the LocalizableAttribute on each property.

Implementing ITypeDescriptorFilterService

I don’t really need to create another object to implement this; I can just implement the interface on my designer.  The interface has three methods: FilterAttributes, FilterEvents and FilterProperties.  I want to filter properties, so I have implemented both FilterAttributes and FilterEvents to return whatever the original filter returned.

FilterProperties, on the other hand, requires more attention.  I want to modify any properties that fall into the following category:

  • Are currently marked with the Localizable(true) attribute.
  • Are not in my collection’s list of localizable properties.

For each one of these properties, I will replace it with a new property.  The new property is identical to the original except that I want to replace Localizable(true) with Localizable(false).  That will tell the code generator to generate normal code for the property rather than save it in a resource.  Here is my complete implementation of FilterProperties that will do just that:

	bool FilterProperties(IComponent component, IDictionary dict) {
		bool retVal = existingFilter.FilterProperties(component, properties);
		// Update any properties based on our localized names
		//
		if (localizableProperties != null && localizableProperties.Count > 0)
		{
			Attribute[] nonLoc = new Attribute[] {LocalizableAttribute.No};
			string[] propNames = new string[properties.Keys.Count];
			properties.Keys.CopyTo(propNames, 0);
			foreach(string propName in propNames) 
			{
				bool localize = false;
				foreach(string name in localizableProperties)
				{
					if (name.Equals(propName)) 
					{
						localize = true;
						break;
					}
				}
				if (!localize)
				{
					PropertyDescriptor prop;
					prop = (PropertyDescriptor)properties[propName];
					if (prop != null)
					{
					if (prop.Attributes.Contains(LocalizableAttribute.Yes))
					{
						prop = TypeDescriptor.CreateProperty(
							prop.ComponentType, 
							prop,
							nonLoc);
						properties[propName] = prop;
					}
				}
			}
		}
		return retVal;
	}

This code uses the same CreateProperty method on TypeDescriptor I had used earlier.  Here, I’m passing back the original component type for the property, however, and I’m also passing the original property back too.  This causes TypeDescriptor to create a new property descriptor that is entirely based on the existing one, complete with the same set of attributes.  The last parameter I pass, the one named “nonLoc”, is an array of new attributes.  My “new attributes” is a single attribute : LocalizableAttribute.No.

Trying it Out

Now that I’ve shown you all the code necessary to build my “LocalizationFilter” component, let’s actually try it out.  Load up the LocalizationFilter solution into Visual Studio .NET and build it.  Inside this solution I have two projects:  LocalizationFilter, which contains the actual filter, and LocalizationFilterTestApplication, which is a Windows Forms project that utilizes the filter.  Inside this test application project are three forms:  NormalForm, FilterForm and StartupForm.

NormalForm and FilterForm are identical except that FilterForm uses the LocalizationFilter component.  Both have a tab control with ten tabs.  Each tab of the tab control has eight controls.  This gives a total of eighty three controls for each form (the tab contents, the tab control, and OK and Cancel buttons I threw on to make the form look complete).

StartupForm is a form that has two buttons:  one button launches NormalForm and another launches FilterForm.

What are the results?  While I won’t get into timings here, bringing up the FilterForm on my P3-600 laptop feels instantaneous.  NormalForm, on the other hand, while not too sluggish, is visibly slower to start than FilterForm.  A quick scan of the designer code gen tells me why:  while FilterForm’s class file has about 1350 lines of code in it, NormalForm’s class file almost 2650.  It’s twice as big!  This directly translates into slower runtime performance, larger executable size, and decreased customer happiness.  I think I’ll use LocalizationFilter for all my forms.

Wrapping Up

The LocalizationFilter demonstrates several key technologies in the .NET Framework:  designers, services and code serialization.  These extensibility points provide developers a lot of power when creating controls.  But, as I’ve shown here, they are also available to IT shops to create tools that help tailor the design environment to the needs of the shop.