Sunday, October 16, 2005

Visual Inheritance and Localization

I was playing around with Whidbey/VS 2005 beta 2 and I couldn't get any of the Windows Forms Visual Inheritance stuff to work. I was interested in seeing how the resource handling in the Visual Inheritance scenario might be different from VS 2003.

In VS 2003, there was an interesting behavior with the resx files and Visual Inheritance in a localization scenario (where only the resx files are handed off to the localizers). Whether or not the localizers could change the localizable properties on a derived UI depended on a given properties accessibility (public/internal/protected or private) and on whether or not that property was modified in the derived UI using the Windows Forms designer.

Accessibility Affecting Localizability
For controls that are marked private, which is the default accessibility when you drop a control onto a Form or UserControl in the Windows Forms designer, subclassed UIs (Forms or UserControls) cannot alter any of the properties on those controls defined in the base class.

In the localization scenario, this means that resx changes on the base UI are propagated to any derived UIs (for example, the size and position of a controls on on the base UI will be the same as that on the derived UI).

For example, given a simple BaseForm and DerivedForm where the only control on that form is a private button1, the DerivedForm's InitializeComponent() is as follows (abbreviated):

private void InitializeComponent()
{
System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(DerivedForm));
//
// DerivedForm
//
this.AutoScaleBaseSize = ((System.Drawing.Size)(resources.GetObject("$this.AutoScaleBaseSize")));
this.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("$this.BackgroundImage")));
this.ClientSize = ((System.Drawing.Size)(resources.GetObject("$this.ClientSize")));
this.Font = ((System.Drawing.Font)(resources.GetObject("$this.Font")));
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Location = ((System.Drawing.Point)(resources.GetObject("$this.Location")));
this.MaximumSize = ((System.Drawing.Size)(resources.GetObject("$this.MaximumSize")));
this.MinimumSize = ((System.Drawing.Size)(resources.GetObject("$this.MinimumSize")));
this.Name = "DerivedForm";
this.StartPosition = ((System.Windows.Forms.FormStartPosition)(resources.GetObject("$this.StartPosition")));
this.Text = resources.GetString("$this.Text");
..
}

Notice that there aren't any properties being loaded from the resx file for the private button1. That means that the localizers don't have any control over that button in the DerivedForm.

Since marking button1 as private means that only the base class UI can access that member, this behavior is expected.

At least for a developer it's obvious. That said, many localizers are not developers and the lack of context or knowledge can be a source of confusion. But wait! There's more...

Overriding Control Properties in resx Files for a Derived UI
A slightly more interesting scenario is the one where a control is marked in such a way that the subclassed UI can override that controls properties (public, protected and in some cases internal). The behavior can actually vary.

For example, suppose I have a simple base class as follows (code abbreviated):

public class Form1 : System.Windows.Forms.Form
{
protected System.Windows.Forms.Button button1;
private System.ComponentModel.Container components = null;

public Form1()
{
InitializeComponent();
}

#region Windows Form Designer generated code

private void InitializeComponent()
{
System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(Form1));
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// button1
//
this.button1.Font = ((System.Drawing.Font)(resources.GetObject("button1.Font")));
this.button1.Location = ((System.Drawing.Point)(resources.GetObject("button1.Location")));
this.button1.Name ="button1";
this.button1.Size = ((System.Drawing.Size)(resources.GetObject("button1.Size")));
//
// Form1
//
this.Controls.Add(this.radioButton1);
this.Controls.Add(this.label1);
this.Controls.Add(this.button1);
this.Font = ((System.Drawing.Font)(resources.GetObject("$this.Font")));
this.ResumeLayout(false);
}
#endregion
}


If I create a derived Form from the above code, I get the following:

public class DerivedForm : VisualInheritanceTest.Form1
{
private System.ComponentModel.IContainer components = null;
public DerivedForm()
{
InitializeComponent();
}

#region Designer generated code
private void InitializeComponent()
{
System.Resources.ResourceManager resources = new
System.Resources.ResourceManager(typeof(DerivedForm));
this.SuspendLayout();
//
// button1
//
this.button1.Name = "button1";
//
// DerivedForm
//
this.Font = ((System.Drawing.Font)(resources.GetObject("$this.Font")));
this.Location = ((System.Drawing.Point)(resources.GetObject("$this.Location")));
this.ResumeLayout(false);
}
#endregion
}

The key thing to notice is that in the derived class, by default, the InitializeComponent() method does not have any code to load the button1 properties from the derived class' resx file.

This prevents the localizers from being able to modify button1 independently of the base UI. In other words, by default, modifying the properties in the resx for the base UI will affect all of the derived UIs.

Suppose the developer were to modify the size and location of button1 in the derived UI using the Windows Forms designer. This causes the forms designer to generate code in the derived class' InitializeComponent() method to load the size and location of button1 from the resx file.

New code after modifying the size and position of button1:

//
// button1
//
this.button1.Location = ((System.Drawing.Point)(resources.GetObject("button1.Location")));
this.button1.Size = ((System.Drawing.Size)(resources.GetObject("button1.Size")));

Now the derived class' resources would be used to determine the size and location of those controls and changing button1's properties in the base UI resources will no longer affect the derived UI.

In other words:

  • If the developer hadn't modified a property of a given control in the WinForms designer on the derived UI then that property would not be changeable from the subclassed UI's resources and would be inherited from the base UI resources
  • If the developer had modified a property of a given control on the derived UI then it would be changeable from that subclassed UI's resources and changing the base UI resources would not have an effect on any derived UIs.
The result is that from a localizer's point of view, changing a property in the base UI may or may not affect the derived UI and changing a property on a control in the derived UI may or may not be possible.

Because many localizers aren't developers, this behavior can seem to be very inconsistent and hard to work with.

Finally
In order to resolve this problem, it's important for the developers to provide context and information to the localizers on where Visual Inheritance is used. Additionally, for subclassed UIs with controls whose properties are overridable, it would be good to adopt some kind of convention or add consistency to make the localizers job easier.

In the past, I solved this by ensuring that all of the control properties on a subclassed UI were overridable in the derived UIs resources. Although this caused a bit more work in situations where a common property needed to be updated in the base UI and all derived UIs, giving the localizers control and consistency was more important.

It'll be interesting to see how Whidbey addresses this, the changes to the localization model will probably alter this behavior. In a mere few weeks I'll have the released Visual Studio 2005 installed and I'll blog about how the Visual Inheritance and Localization behavior has changed.

0 Comments:

Post a Comment

<< Home