Thursday, November 03, 2005

Visual Studio 2005 Visual Inheritance Localization

I have now installed the release version of Visual Studio 2005 and have started doing some prototype development with it.

So far, I'm really liking the refreshed UI and the new features. Especially the editor features which make the tedious parts of coding fun -- snippets, surround with..., refactor, generate method stub...

I was working with Visio yesterday and was thinking "man, Visio needs snap lines" -- so yeah, really liking the new designer features as well.

Anyhow, I wanted to revisit the Visual Inheritance localization topic I started in a previous entry since the localization model has changed in Visual Studio 2005.

Property Reflection Resource Model
In .Net 1.1, resources were loaded from the resx file by the ResourceManager on a property by property basis:

this.Font = ((System.Drawing.Font)(resources.GetObject("$this.Font")));

In .Net 2.0, this is handled all via reflection by the ComponentResourceManager on a control by control basis:

resources.ApplyResources(this.checkBox1, "checkBox1");

Any resources in the resx file that correspond to checkBox1 are applied to checkBox1. This is different from .Net 1.1 where an entry in the resx file had to have corresponding code in InitializeComponent() in order for it to be applied.

The implication is that in order for a property on a Control to be localizable, it doesn't have to be serialized to the resx file and can be added later -- as long as the resources.ApplyResources() method is being called for that Control.

Keeping that in mind, only the properties that have been changed from their default value (as defined by the DefaultValueAttribute) are serialized into the resx file.

In the Visual Inheritance scenario, instead of the "default value", it's based on whether that property's value is different from the base value.

Initially, I was concerned when I didn't see the Font being serialized into the resx file -- I thought that the Font wouldn't be localizable, as would be the case in .Net 1.1, however a quick test showed that I could still localize the Font by adding the Font information to a culture specific resx file.

If you define your own localizable properties, you need to ensure that you are using a good localization tool and that it has access to any corresponding assemblies (on top of the resx files). By reflecting over the assemblies, the tool can find any of the custom localizable properties on your user created controls and find the type information required to populate a value for any of those properties in a resx file.

Localization and Visual Inheritance
I created a new Windows Application and put a couple of simple controls on a Form and marked their modifier property to protected.

I set the Localizable property to true in the designer and then added an inherited Form (set localizable to true for that Form as well) and added a button to it.

The InitializeComponent() method for the base Form is now:
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
this.button1 = new System.Windows.Forms.Button();
this.checkBox1 = new System.Windows.Forms.CheckBox();
this.label1 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// button1
//
resources.ApplyResources(this.button1, "button1");
this.button1.Name = "button1";
this.button1.UseVisualStyleBackColor = true;
//
// checkBox1
//
resources.ApplyResources(this.checkBox1, "checkBox1");
this.checkBox1.Name = "checkBox1";
this.checkBox1.UseVisualStyleBackColor = true;
//
// label1
//
resources.ApplyResources(this.label1, "label1");
this.label1.Name = "label1";
//
// Form1
//
resources.ApplyResources(this, "$this");
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.label1);
this.Controls.Add(this.checkBox1);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.ResumeLayout(false);
this.PerformLayout();

}
And the InitializeComponent() method for the inherited Form is:
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(DerivedForm));
this.button2 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// button2
//
resources.ApplyResources(this.button2, "button2");
this.button2.Name = "button2";
this.button2.UseVisualStyleBackColor = true;
//
// DerivedForm
//
resources.ApplyResources(this, "$this");
this.Controls.Add(this.button2);
this.Name = "DerivedForm";
this.Controls.SetChildIndex(this.button2, 0);
this.ResumeLayout(false);
this.PerformLayout();
}
As you can see from the ComponentResourceManager.ApplyResources() calls, resources are being applied to all of the controls defined on the base Form from the base Form's resources. In the inherited Form, resources are only being applied to "button2" which was the button that was added directly to the inherited Form.

In other words, by default, on an inherited Form, controls defined in the base Form get their resources from the base resx file.

If I modify the properties (I moved the CheckBox and changed its text on the inherited Form) of a CheckBox control from the base Form in the inherited Form, the InitializeComponent() method in the inherited Form looks like this:
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(DerivedForm));
this.button2 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// checkBox1
//
resources.ApplyResources(this.checkBox1, "checkBox1");
//
// button2
//
resources.ApplyResources(this.button2, "button2");
this.button2.Name = "button2";
this.button2.UseVisualStyleBackColor = true;
//
// DerivedForm
//
resources.ApplyResources(this, "$this");
this.Controls.Add(this.button2);
this.Name = "DerivedForm";
this.Controls.SetChildIndex(this.checkBox1, 0);
this.Controls.SetChildIndex(this.label1, 0);
this.Controls.SetChildIndex(this.button2, 0);
this.ResumeLayout(false);
this.PerformLayout();
}

Note the addition of the line

resources.ApplyResources(this.checkBox1, "checkBox1");

The inherited Forms resources are now being applied to "checkBox1".

The behavior is pretty much the same as was described in my previous article on Visual Inheritance and Localization (.Net 1.1).

In other words, as mentioned in my previous post on this topic, the situation can still be very confusing to localizers who are working only with resx files since the localizable properties of a control will vary depending on what properties the developer has modified on controls in the inherited Form.

The solution is the same in the .Net 1.1 case and it involves awareness of the problem, communicating the Visual Inheritance relationships and behaviors to the localizers and deliberately modifying control properties in the inherited Form in order to get them to show up in the inherited Form's resx file.

One improvement is that once you modify any property on a control from the base Form on that inherited Form, you get the resources.ApplyResources() code added. In .Net 1.1, you had to fiddle with every property you wanted to expose to the inherited Form's resources in order to get the serialization code for that property to be added.

Finally
The new resources model in Whidbey certainly does cut down the size of the resources and the InitializeComponent() method, especially in localizable scenarios which was an issue for me in the past.

That said, the behavior with Visual Inheritence is still the same as 1.1 and is something to be aware of.

3 Comments:

At 1:35 AM, Anonymous Markus said...

Hi Jim,

thank's for your articles on localization and inherited forms. I am struggling with the same problem and your articles helped me understand the cause of my problem. I agree with you that the Designer's behavior is a problem for localizers and I hope Microsoft will provide a better model in the future.

 
At 10:55 PM, Anonymous Anonymous said...

Thanks for the blog. I've been tearing my hair out with localised inherited forms for the past week (VS2005). For some reason, when the application is run on a PC with a different DPI to the PC on which the form was originally designed, the Font for some forms is picked up correctly when ApplyResources is called for an inherited localised form. This results in the form being shown with the wrong size (because AutoScaleMode is set to Font) and half the controls are cut off.

Never again will I use inherited forms. Period.

 
At 10:13 AM, Anonymous Anonymous said...

Who knows where to download XRumer 5.0 Palladium?
Help, please. All recommend this program to effectively advertise on the Internet, this is the best program!

 

Post a Comment

<< Home