In the previous chapter, you saw how strings and bitmaps can be localized using ResourceManager and satellite assemblies. The same process could be used to localize forms, but it would be extremely tedious. Every string and bitmap would need to be manually loaded, but the job wouldn't stop there. Numerous other properties, such as Size, Location, RightToLeft, and ImeMode, might also need to be localized. This would amount to a significant amount of additional, repetitive code. Fortunately, Visual Studio has functionality for localizing forms in a neat and simple manner. Here we will follow an example of localizing a form. This example is deliberately simplistic, to focus on the details of localizing a form instead of providing a real-world example. Create a new Windows Forms application, add a button in the bottom-right corner, and set its Text to "Close" (see Figure 4.1). Figure 4.1. Simple Form Localization Example Here's the first part of the InitializeComponent code generated by Visual Studio 2005 in the Form1.Designer.cs file: private void InitializeComponent() { this.button1 = new System.Windows.Forms.Button(); this.SuspendLayout(); // // button1 // this.button1.Location = new System.Drawing.Point(205, 231); this.button1.Name = "button1"; this.button1.Size = new System.Drawing.Size(75, 23); this.button1.TabIndex = 0; this.button1.Text = "Close"; this.button1.UseVisualStyleBackColor = true; // etc. // etc. Visual Studio 2003's generated code (in Form1.cs) differs by the inclusion or exclusion of properties that are specific to a version of the .NET Framework. Notice the assignment to the button's Text property; clearly, the "Close" text is hard-coded and cannot be localized in its current form. Property Assignment ModelNow set Form1.Localizable to TRue (in the Properties Window) and look at the code again. What you see depends on whether you are using Visual Studio 2003 or Visual Studio 2005. Regardless of the version of Visual Studio, I recommend that you follow the explanation for both versionsVisual Studio 2005 developers will learn why Visual Studio 2005 works the way it does, and Visual Studio 2003 developers will learn what they can look forward to. In Visual Studio 2003, you will see this in the Form1.cs file: 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.AccessibleDescription = resources.GetString("button1.AccessibleDescription"); this.button1.AccessibleName = resources.GetString("button1.AccessibleName"); this.button1.Anchor = ((System.Windows.Forms.AnchorStyles) (resources.GetObject("button1.Anchor"))); this.button1.BackgroundImage = ((System.Drawing.Image) (resources.GetObject("button1.BackgroundImage"))); this.button1.Dock = ((System.Windows.Forms.DockStyle) (resources.GetObject("button1.Dock"))); this.button1.Enabled = ((bool)(resources.GetObject("button1.Enabled"))); this.button1.FlatStyle = ((System.Windows.Forms.FlatStyle) (resources.GetObject("button1.FlatStyle"))); this.button1.Font = ((System.Drawing.Font) (resources.GetObject("button1.Font"))); this.button1.Image = ((System.Drawing.Image) (resources.GetObject("button1.Image"))); this.button1.ImageAlign = ((System.Drawing.ContentAlignment) (resources.GetObject("button1.ImageAlign"))); this.button1.ImageIndex = ((int) (resources.GetObject("button1.ImageIndex"))); this.button1.ImeMode = ((System.Windows.Forms.ImeMode) (resources.GetObject("button1.ImeMode"))); this.button1.Location = ((System.Drawing.Point) (resources.GetObject("button1.Location"))); this.button1.Name = "button1"; this.button1.RightToLeft = ((System.Windows.Forms.RightToLeft) (resources.GetObject("button1.RightToLeft"))); this.button1.Size = ((System.Drawing.Size) (resources.GetObject("button1.Size"))); this.button1.TabIndex = ((int) (resources.GetObject("button1.TabIndex"))); this.button1.Text = resources.GetString("button1.Text"); this.button1.TextAlign = ((System.Drawing.ContentAlignment) (resources.GetObject("button1.TextAlign"))); this.button1.Visible = ((bool) (resources.GetObject("button1.Visible"))); // etc. // etc. This code is significantly longer than the original, unlocalizable code. Let's look at the important differences. First, and most important, the first line creates a new ResourceManager object, passing in the type of Form1, and assigns the new object to a local variable called resources: System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(Form1)); The subsequent code uses this resource manager to retrieve all the string, bitmap, and other resources that are necessary to localize this form. Take a look at the assignment to the button's Text property. It has changed from this: this.button1.Text = "Close"; to this: this.button1.Text = resources.GetString("button1.Text"); In the localizable form, a "button1.Text" string resource is being loaded from the resource manager. In Solution Explorer, click the Show All Files button, expand the Form1.cs node, and double-click Form1.resx (see Figure 4.2). Figure 4.2. Form1.resx in Visual Studio 2003 after Localizable Is Set to True![]() You can see that the "button1.Text" entry has a value of "Close". So the approach used to associate each property with its localized value is to load a resource whose name is made from the name of the object plus a period, plus the name of the property. Simple and effective. Now take a look at the button's Location property. It has changed from this: this.button1.Location = new System.Drawing.Point(205, 231); to this: this.button1.Location = ((System.Drawing.Point) (resources.GetObject("button1.Location"))); The Location property is of type System.Drawing.Point, so the code uses ResourceManager.GetObject to retrieve the value and then casts the resulting object to a System.Drawing.Point. The next difference between the unlocalized and localized code is that, whereas the button on the unlocalized form includes 6 properties (4 properties in Visual Studio 2003) to assign values to, this localized version includes 20. In the unlocalized form, Visual Studio 2003 has simply listed every property that has a different value than its default value. In the localized form, Visual Studio 2003 has listed every property that has a Localizable attribute, regardless of whether its value is different than its default value. This approach to serializing the form is called the property assignment model. Visual Studio 2003 deliberately ignores whether the value is different than its default value because the value that it is comparing against the default value is only the value that is used in the fallback assembly. It does not follow that the property would have the same value in a different culture (the RightToLeft property, for example, would clearly be different in a right-to-left culture). Instead, it has had to take the approach that any property that could be localizable (i.e., has a Localizable attribute) must be localizable. Consequently, all Localizable properties are saved to the resource file, and all their values are loaded at runtime. Naturally, this occurs even when the property has its default value. So the buttons' Dock property is assigned its default value from the button1.Dock resource. In this example, this redundancy occurs for 16 properties for Visual Studio 2003. It is possible to argue that this is an unnecessary performance hit. Visual Studio 2005 addresses this issue, but if you are using Visual Studio 2003 and do not intend to upgrade in the near future, you should consider two points. The first is that, accepting that all performance judgments are relative, this problem possibly sounds worse than it actually is. The fear for a developer is that a lot of unnecessary resource accesses and property assignments are going on that will affect performance. Clearly, these will affect performance, but, to borrow a tenet from Extreme Programming, don't engineer solutions to problems that you don't know you have. Start by proving that you actually do have a problem. In the tests that I have performed, in which I continually have opened and closed an unlocalized form and then continually opened and closed the same localized form, I cannot see a difference. Clearly, there is a difference, but remember that we are looking at this problem from the user's perspective, and users can't measure in clock cycles. Of course, this is a scaling issue; if you see the issue at all, you are likely to see it only on forms with a very large number of components. The second point is that if this issue still bothers you and you can't upgrade to Visual Studio 2005, you should take a look at the Localization Filter (http://windowsforms.net/articles/localizationfilter.aspx), from Brian Pepin, who works in Microsoft's development tools group. The Localization Filter is a design-time component that enables developers to specify which properties should be localized (and which should not). As a consequence, it reduces the number of properties on a localized form and improves the performance of localized forms. This is also an interesting example of how to interfere with the form's serialization process. Property Reflection ModelVisual Studio 2005 is used in the same way (i.e., you set Form1.Localizable to true), but the way in which properties are loaded differs. Take a look at the InitializeComponent method: private void InitializeComponent() { System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1)); this.button1 = new System.Windows.Forms.Button(); this.SuspendLayout(); // // button1 // resources.ApplyResources(this.button1, "button1"); this.button1.Name = "button1"; this.button1.UseVisualStyleBackColor = true; // // Form1 // resources.ApplyResources(this, "$this"); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.Controls.Add(this.button1); this.Name = "Form1"; this.ResumeLayout(false); } The first line creates a System.ComponentModel.ComponentResourceManager instead of a System.Resources.ResourceManager. ComponentResourceManager inherits from ResourceManager, so it has all the same properties and methods. ComponentResourceManager, which is part of both the .NET Framework 1.1 and 2.0, adds a single method, ApplyResources, and does not override any methods. You can see the ApplyResources method being used in InitializeComponents: resources.ApplyResources(this.button1, "button1"); this.button1.Name = "button1"; this.button1.UseVisualStyleBackColor = true; The 20 assignments used in Visual Studio 2003 have been reduced to just these 3 assignments in Visual Studio 2005. The ApplyResources method looks through every entry in the resource for a key that meets the following criteria:
For each such entry, it assigns the value in the resource to the object's property. This model is called the property reflection model because it uses reflection to probe the object for corresponding properties. If you take a look at Form1.resx in Visual Studio 2005, you will see that only those Localizable properties that do not have the same value as their default are included in the resource file. When the form is localized for a different culture and more or fewer properties are localized, the properties will still be assigned their values, and the resources need to be only as large as is necessary for that culture. The intention is that the performance savings of assigning only those properties that need to be localized is greater than the performance loss of having to use reflection to probe the object and assign its value. With a smaller number of properties, such as we have with the button in the example, the gain outweighs the loss. You should be aware, however, that in the unlikely event that the number of localized properties approaches the number of localizable properties, the loss will outweigh the gain. However, this scenario is rather unlikely. The .NET Framework 2.0 supports both the property assignment model used by Visual Studio 2003 and the property reflection model used by Visual Studio 2005. If you write your own form designer or need to generate form-serialization code yourself, you can specify which model you want to use with the CodeDomLocalizationModel enumeration, shown in Table 4.1.
You pass the desired CodeDomLocalizationModel enumeration to the Code-DomLocalizationProvider constructor to tell it how to serialize forms. However, Visual Studio 2005 does not have any facility for setting this value; although the framework supports the capability to change the model, the IDE does not surface this capability, so you cannot choose the property assignment model. Localizing a FormNow that our form is localizable, we can localize it. Select the form and, in the Properties Window, drop down the Languages combo box. The list is populated using CultureInfo.GetCultures. This method accepts a parameter specifying what kind of cultures to get. The parameter value that is passed is CultureTypes.All-Cultures. In Visual Studio 2003, this returns a list that is hard-coded in the .NET Framework 1.1. In Visual Studio 2005, this returns the list of cultures available to the .NET Framework, the operating system, and the user-defined cultures. Note that the combo box does not support an incremental search, so if you type "Fr", you will get "Romanian" instead of "French" (because the "F" goes first to "French" and the "r" then goes to "Romanian"). Select French from the list. In Visual Studio 2005, the Form Designer caption changes to show that you are designing the French version of this form (see Figure 4.3). In Visual Studio 2003, there is no visual indication of which form you are localizing, except for the Languages property in the Properties Window. Figure 4.3. Visual Studio 2005 Localizing a Form![]() Change Button1.Text to "Fermer" (the French for "Close"). In the Solution Explorer, expand the Form1.cs node; you will see a new file, Form1.fr.resx, which holds the French resources for this form. Currently, this resx file has an entry for Button1.Text but no entry for Button1.Size because this property does not differ from the fallback resource (Form1.resx). Recall from the previous chapter that if a resource is not found in a neutral culture (i.e., WindowsApplication1.Form1.fr.resources), ResourceManager falls back to the default resources (WindowsApplication1. Form1.resources), so it is necessary to list only the differences from the parent. If you build the solution now, you will find a new folder ("fr") beneath the output folder that holds the French resources ("WindowsApplication1.resources.dll").
Finally, to illustrate a point, move the button to a different location and change its size. In the Form1.Language combo box, select "(Default)"; Visual Studio shows the fallback resource. You can alternate between the different languages, modifying the localized form(s) as necessary. You can save your project with any language version of your form loaded in the editor; the selected language has no effect at runtime. Despite this, I recommend that you return Language to "(Default)" when you are finished making changes. In Visual Studio 2003, this is essential because there is no immediate visual feedback on which form you are editing; it is easy to save the project with a specific language visible and open it again later (or for a different developer to open it), and forget or not realize that the default is not selected before you start making changes to a language-specific version of the form instead of to the default. The important point to grasp from moving the button is that the resources behave according to the rules of inheritance. Because of the way the ResourceManager falls back to more generalized resources when specific resources are missing, you can think of this as inheritance for resources. So the French resource is a more specific kind of the default resource. It is simply a list of differences from its base, just as all classes are simply a list of differences from their base. This relationship is reflected in the French resource file, Form1.fr.resx. <data name="button1.Location" type="System.Drawing.Point, System.Drawing"> <value>170, 12</value> </data> <data name="button1.Size" type="System.Drawing.Size, System.Drawing"> <value>110, 32</value> </data> <data name="button1.Text" xml:space="preserve"> <value>Fermer</value> </data> These elements are the only entries that refer to button1 after the Location, Size, and Text properties have been changed. This demo is fine as far as it goes, but it doesn't cover the fundamental truth of development; everything changes. It is a certainty that we will change this form at some point in the future. How does this model hold up when the form is changed? With Language set to "(Default)", add a Label and a TextBox to the form and set label1.Text to "Company name". Now set Language to French, and you can see that the French version of the form includes the newly added components. If you go back to the default version and increase the size of the TextBox, for example, the change is reflected in the French form. However, if you change the size of the TextBox on the French form, the link with the default form is lost, and changes to the TextBox's Location on the default form are not applied to the French form. Changes to other properties, such as Font and Size, are still linked and are applied to the French form. One final piece of the localization jigsaw is missing. If you modify a property on a culture-neutral or culture-specific form by mistake and you want to revert to the default form's property, how can you do this? Simply changing the property on the culture-neutral or culture-specific form to be the same value as the property on the default form doesn't have the right effect. Sure, they will have the same value, but they are not linked; if you change the property on the default form again, the property on the culture-neutral or culture-specific form will not change. The answer is to dive into the culture-neutral or culture-specific resx file, find the entry for the offending property, and delete it. Adding and Deleting ComponentsYou can easily delete components on a form in the normal way by selecting them and then pressing the Del key. If you delete the newly added TextBox and then select the French form, you will see that the TextBox has been deleted from all versions of the form. This rule holds true even if the currently selected Language is not the default, so if you delete the newly added Label from the French form, it will be deleted from the default and all other forms. We've also seen that when components are added to the default form, they are added to all versions of the forms. If you add a component to a form other than the default form, the behavior you see depends on the version of Visual Studio you are using. In Visual Studio 2003, you get the dialog in Figure 4.4, stating that the component will be added to all versions of the form. In Visual Studio 2005, you get a dialog stating that controls cannot be added in localization mode, and you will have to change the language back to the default. Figure 4.4. Visual Studio 2003 Warning Explaining the Effect of Adding Components to Specific Language Forms![]() From this adding and deleting behavior, you can conclude that components either exist or do not exist on all versions of the form; only the properties can differ, not the existence of a component. Whereas this makes life much easier in terms of localization, it can make customization more difficult. What do you do if you want a button to appear on the French form but not on any other form (including the default)? The answer is that you employ a little trickery. Add the button to the default form and set its Visible property to False. In the French form, set its Visible property to true. Problem solved. |