Adding Resources to Assemblies

 
Chapter 8 - Assemblies
bySimon Robinsonet al.
Wrox Press 2002
  

In this section, we will look at the .NET architecture's support for resource files, their use to facilitate internationalization, and the creation of satellite assemblies.

The advantage of using resource files instead of storing strings or pictures directly in program code is that non-programmers can easily change these resource files; although it may take a programmer, or at least the use of a few batch files to recompile them into new resource files. It's not necessary to search through the sourcecode for strings when using resource files, as the strings are all in one place. It is also advantageous to have strings or images in resource files when programs are localized to different languages, as translators simply need to edit the resource files. To this end, we can make use of satellite assemblies for localization; they hold resources, but no program code.

In this section, we shall explore:

  • Creating resource files, using the resgen utility and the ResourceWriter object

  • Using resource files, and accessing embedded resources with the ResourceManager class

  • Localization by using satellite assemblies

  • Visual Studio .NET localization support for Windows applications

Creating Resource Files

Resource files contain such things as pictures and string tables. A resource file is created using either a normal text file, or a .resX file that utilizes XML. We will start with a simple text file.

A resource that embeds a string table can be created using a normal text file. The text file just assigns strings to keys. The key is a name that can be used from a program to get the value. Spaces are allowed in both keys and values.

This example shows a simple string table in the file strings.txt :

   Title = Professional C#     Chapter = Assemblies     Author = Christian Nagel     Publisher = WROX Press   

ResGen

The resgen.exe utility can be used to create a resource file out of strings.txt . Typing:

  resgen strings.txt  

will create the file strings.resources . This resulting resource file can be added to an assembly either as an external file or embedded into the DLL or EXE. ResGen also supports the creation of resource files with XML-based .resX files. One easy way to build an XML file is by using ResGen itself: resgen strings.resources strings.resX creates the XML resource file strings.resX . We will look at how to work with XML resource files when we look at Localization, later in this chapter.

The resgen utility doesn't support adding pictures. With the .NET Framework SDK Samples, you'll get a ResXGen sample. With ResXGen it's possible to add pictures to a .resX file. Adding pictures to resources can also be easily done using the ResourceWriter class.

ResourceWriter

Instead of using the resgen utility to build resource files, a simple program can be written. ResourceWriter is a class in the System.Resources namespace that also supports pictures, and other resource types.

Here we're creating a ResourceWriter object rw using a constructor with the filename Demo.resources . After creating an instance, a number of resources of up to 2GB in total size can be added using the AddResource() method of the ResourceWriter class. The first argument of AddResource() specifies the key, and the second argument specifies the value. A picture resource can be added using an instance of the Image class. To use the Image class, the System.Drawing assembly must be referenced. We are also opening the namespace System.Drawing with the using directive.

Here I'm creating an Image object by opening the file logo.gif . You'll have to copy the picture to the directory of the executable, or specify the full path to the picture in the method argument of Image.FromFile() . The using statement specifies that the image resource should automatically be disposed of at the end of the using block. Additional simple string resources are added to the ResourceWriter object. The Close() method of the ResourceWriter class automatically calls ResourceWriter.Generate() to finally write the resources to the file Demo.resources :

   using System;     using System.Resources;     using System.Drawing;     class Class1     {     [STAThread]     public static void Main()     {     ResourceWriter rw = new ResourceWriter("Demo.resources");     using (Image image = Image.FromFile("logo.gif"))     {     rw.AddResource("WroxLogo", image);     rw.AddResource("Title", "Professional C#");     rw.AddResource("Chapter", "Assemblies");     rw.AddResource("Author", "Christian Nagel");     rw.AddResource("Publisher", "Wrox Press");     rw.Close();     }     }     }   

Starting this small program creates the resource file Demo.resources . The resources will be used in a Windows application.

Using Resource Files

Resource files can be added to assemblies using the Assembly Generation Tool Al.exe using the /embed option, or directly with Visual Studio .NET. For a demonstration on how to use resource files with Visual Studio .NET, I'm creating a C# Windows Application and calling it ResourceDemo :

In the context menu of the Solution Explorer ( Add Add Existing Item ), the previously created resource file Demo.resources can be added to this project. By default the BuildAction of this resource is set to Embedded Resource , so that this resource gets embedded into the output assembly:

click to expand

After building the project, viewing the assembly using ildasm shows .mresource in the manifest. .mresource declares the name for the resources in the assembly. If .mresource is declared public (as in our example), the resource is exported from the assembly and can be used from classes in other assemblies. .mresource private means the resource is not exported and only available within the assembly.

click to expand

When adding the resource to the assembly using Visual Studio .NET, the resource is always public as can be seen in the screenshot above. If the assembly generation tool is used to create assemblies, we can use command-line options to differentiate between adding public and private resources. The option /embed:demo.resources,Y adds the resource as public , while /embed:demo.resources,N adds the resource as private .

If the assembly was created using Visual Studio .NET it's possible to change the visibility of the resources later. Opening the assembly using ildasm with File Dump generates an MSIL source file. The MSIL code can then be changed using a text editor such as Notepad. Using the text editor, we can change .mresource public to .mresource private . Using the tool ilasm , it's then possible to regenerate the assembly with the MSIL source code: ilasm /exe ResourceDemo.il /out:ResourceDemo.exe .

In our Windows application, we'll add some textboxes and a picture by dropping Windows Forms elements from the Toolbox to the designer where we will display the values from the resources. I'm changing the Text and Name properties of the textboxes and the labels to the values that you see below. The Name property of the PictureBox control is changed to logo . In the screenshot below, the PictureBox can be seen as a rectangle without grid in the upper left hand corner. The final form opened in the Forms Designer looks like this:

click to expand

To use that embedded resource, the ResourceManager class from the System.Resources namespace can be used. We can pass the assembly where the resources are embedded in the constructor. In our case we have the resources embedded in the executing assembly, so we pass the result of Assembly.GetExecutingAssembly() as the second argument. The first argument is the root name of the resources. The root name is made of the namespace, with the name of the resource file, without the resources extension. As you've seen earlier, ildasm shows the name. It's just necessary to remove the file extension resources . We can also get the name programmatically using the GetManifestResourceNames() method of the System.Reflection.Assembly class.

Using the ResourceManager instance rm we can get all the resources by specifying the key:

   using System.Reflection;     using System.Resources;     // ...     private System.Resources.ResourceManager rm;   public Form1()       {          //          // Required for Windows Form Designer support          //          InitializeComponent();   Assembly assembly = Assembly.GetExecutingAssembly();     rm = new ResourceManager("ResourceDemo.Demo", assembly);     logo.Image = (Image)rm.GetObject("WroxLogo");     textBoxTitle.Text = rm.GetString("Title");     textBoxChapter.Text = rm.GetString("Chapter");     textBoxAuthor.Text = rm.GetString("Author");     textBoxPublisher.Text = rm.GetString("Publisher");   } 

When we run the code, we can see the string and picture resources, as the screenshot below shows:

click to expand

Now we will move on to look at internationalization and the use of resource files.

Internationalization and Resources

NASA's Mars Climate Orbiter was lost on September 23rd 1999 at a cost of $125 million because one engineering team used metric units, while another used different units for a key spacecraft operation.

When writing applications for international distribution, different cultures and regions must be kept in mind. A culture defines who you are, whereas a region defines where you are. Together, the culture and the region are called the locale . One example of the challenges with internationalization is the decimal divider: for the US, the decimal divider is ".", but for Germany it's a ",".

Here are some examples of different number formats:

US English

123,456,789.23

German

123.456.789,23

Swiss German

123'456'789.23

French

123 456 789,23

And some examples of different date formats:

US English

2/13/2002

Wednesday, February 13, 2002

UK English

13/02/2002

13 February 2002

German

13.02.2002

Mittwoch, 13. Februar 2002

French

13/02/2002

Mercredi 13 fevrier 2002

Cultures

A culture is a set of preferences based on a user 's language and cultural conventions. The class CultureInfo is used to format dates, times, and numbers , to sort strings, and to determine the language choice for text. The user selects the culture during system installation and can configure it using the Regional Options in the Windows Control Panel.

The culture name defines a 2-letter language and a 2-letter country/region code that follow RFC1766 conventions. Some examples of the culture names are listed here:

Culture name

Culture

en

English

en-GB

English (United Kingdom)

en-US

English (United States)

fr

French

fr-FR

French (France)

de

German

Using the System.Globalization.CultureInfo class it's possible to get the name of the culture and supported cultures, create a new culture using a culture name string, and so on. Particular things to note are some of the static properties of CultureInfo . We have to differentiate between these culture types:

  • CurrentCulture is the culture of the current thread. The default is set to the user settings, but the culture of the thread can be changed. This culture is used for locale-dependent formatting like numbers and dates.

  • CurrentUICulture is the culture that is used for resource lookups. It is also dependent on the current thread. This culture can be used to get strings or pictures from resources. Contrary to the CurrentCulture this culture can only be configured by the user with a multi-language operating system. With a single-language operating system this culture is the same as the language of the operating system. There is a good reason it is done that way: if an English version of the operating system is installed that displays English dialogs, the same language should be used from applications, but the number and date formatting ( CurrentCulture ) should be configurable by the user. However, we also can change the CurrentUICulture programmatically.

  • InstalledUICulture is the system default culture for resource lookups.

  • InvariantCulture is a "neutral" culture. If you build a Windows service where the output and sorting should be independent of the logged on user, this culture should be used; it does not map to any real culture, and so it is not linguistically correct. If you want to store data, this culture is the preferred one as it does not change, and can easily be parsed and displayed in a culture-dependent manner.

Region

The region is different from the culture. The region does not represent the user's preferences, but defines the location. Switzerland is a multi-language country where the cultures de-ch , fr-ch , and it-ch are specified for Swiss people using their own versions of the German, French, and Italian languages. Be assured that the Swiss German language is different from German in Germany. In contrast to three cultures for Swiss people, only a single region, CH, is defined for Switzerland. When the language is defined by the culture, the region defines the currency and the unit of measurement. The currency used in a region is independent of the language. There's even one bigger aspect of culture vs. region: a Swiss person can go to a different country where they can still use their "home-culture", but of course they have to change the region.

The location can also be set using the Regional Options in Control Panel. The System.Globalization.RegionInfo class has methods to get to these values. RegionInfo has a static property CurrentRegion to get the configured region. It's also possible to create a RegionInfo object by passing a 2- or 3-letter ISO3166 name as a string to the constructor. Examples of this string are AT or AUT for Austria, DE or DEU for Germany, FR or FRA for France, GB or GBR for the United Kingdom, and US or USA for the United States of America.

System.Globalization Namespace

The System.Globalization namespace holds all the culture and region classes to support different date formats, different number formats, and even different calendars like GregorianCalendar , HebrewCalendar , JapaneseCalendar , and so on. By using these classes, it's possible to use different representations depending on the locale.

Numbers

The number structures Int16 , Int32 , Int64 , and so on, in the System namespace all have an overloaded ToString() method. This method can be used to create a different representation of the number depending on the locale. For the Int32 structure, ToString() is overloaded with these four versions:

   public string ToString();     public string ToString(IFormatProvider);     public string ToString(string);     public string ToString(string, IFormatProvider);   

ToString() without arguments returns a string without formatting options. We can also pass a string and a class that implements IFormatProvider . The string specifies the format of the representation. The format can be a standard numeric formatting string, or a picture numeric formatting string. For standard numeric formatting, strings are predefined where C specifies a currency notation, D creates a decimal output, E scientific output, F fixed-point output, G general output, N number output, and X hexadecimal output. With a picture numeric format string, it's possible to specify the number of digits, section and group separators, percent notations, and so on. The picture numeric format string ###,### means two 3-digit blocks separated by a group separator.

With the default constructor of NumberFormatInfo in the System.Globalization namespace, a culture-independent or invariant object is created. Using the properties of this class it's possible to change all the formatting options like a positive sign, percent symbol, number group separator, currency symbol, and a lot more. A read-only culture-independent NumberFormatInfo object is returned from the static property InvariantInfo . A NumberFormatInfo object, where the format values are based on the CultureInfo of the current thread, is returned from the static property CurrentInfo .

The IFormatProvider interface is implemented by the NumberFormatInfo , DateTimeFormatInfo , and CultureInfo classes. This interface defines a single method GetFormat() that returns a format object.

In the next examples I'm using a simple Console Project . In this code, the first example shows a number displayed in the format of the culture of the current thread. On my operating system, the setting is en-us . That's the default for the thread. The second example uses the ToString() method with the IFormatProvider argument. CultureInfo implements IFormatProvider , so I'm creating a CultureInfo object with the French culture. The third example changes the culture of the current thread. Using the property CurrentCulture of the Thread instance the culture is changed to German:

 using System;   using System.Globalization;     using System.Threading;   namespace Wrox.ProCSharp.Assemblies.Localization {    class Class1    {       [STAThread]       static void Main(string[] args)       {   int val = 1234567890;     // culture of the current thread     Console.WriteLine(val.ToString("N"));     // use IFormatProvider     Console.WriteLine(val.ToString("N", new CultureInfo("fr-fr")));     // change the culture of the thread     Thread.CurrentThread.CurrentCulture = new CultureInfo("de-de");     Console.WriteLine(val.ToString("N"));     }     }     }   

The output is shown in the screenshot here. You can compare the outputs with the previously listed differences for US English, French, and German:

click to expand
Dates

The same support for numbers is here for dates. The DateTime structure has some methods for date-to-string conversions. The public instance methods ToLongDateString() , ToLongTimeString() , ToShortDateString() , ToShortTimeString() all create string representations using the current culture. Using the ToString() method a different culture can be assigned:

   public string ToString();     public string ToString(IFormatProvider);     public string ToString(string);     public string ToString(string, IFormatProvider);   

With the string argument of the ToString() method, a predefined format character or a custom format string can be specified for converting the date to a string. The DateTimeFormatInfo class specifies the possible values. With the IFormatProvider argument the culture can be specified. Using an overloaded method without the IFormatProvider argument means that the culture of the current thread is used:

   DateTime d = new DateTime(2001, 6, 15);     // current culture     System.Console.WriteLine(d.ToLongDateString());     // use IFormatProvider     System.Console.WriteLine(d.ToString("D", new CultureInfo("fr-fr")));     // use culture of thread     CultureInfo ci = Thread.CurrentThread.CurrentCulture;     Console.WriteLine(ci.ToString() + ": " + d.ToString("D"));     ci = new CultureInfo("de-de");     Thread.CurrentThread.CurrentCulture = ci;     Console.WriteLine(ci.ToString() + ": " + d.ToString("D"));   

The output of our example program shows ToLongDateString() with the current culture of the thread, a French version where a CultureInfo instance is passed to the ToString() method, and a German version where the CurrentCulture of the thread is changed to de-de :

click to expand

Besides having a different formatting and measurement system depending on the locale, strings should have different texts ; perhaps some pictures should also be replaced depending on the locale. This is where satellite assemblies are used.

Satellite Assemblies

Satellite assemblies are used in applications to support language-dependent strings. Satellite assemblies are assemblies that hold only resources and no code. A string table, pictures, videos , and so on can be included. Of course, the AssemblyCulture attribute must be set. A satellite assembly for the de culture must be placed into the de subdirectory of the program. The satellite assembly supporting Austria's version of German is placed into the de-at subdirectory.

de and de-at have many translations in common. There are only a few differences. With the .NET architecture of satellite assemblies, it's not necessary to store all resources in the de-at assembly, just the differences. We just have to create a resource file with the different strings. If the user has a system where the region de-at is configured, and resources cannot be found there, the parent assembly is used; de is the parent assembly for de-at . If a resource can't be found there, then the resources of the neutral assembly are used. The neutral assembly is the assembly where no assembly culture is set. That's the reason why you shouldn't set a culture for the main assembly.

System.Resources Namespace

Before we move onto our example, we shall conclude this section with a review of the classes contained in the System.Resources namespace that deal with resources, some of which we've already met:

  • The ResourceManager can be used to get resources for the current culture from satellite assemblies. Using the ResourceManager it's also possible to get a ResourceSet for a particular culture.

  • A ResourceSet represents the resources for a particular culture. When a ResourceSet instance is created it enumerates over a class implementing the interface IResourceReader , and stores all resources in a Hashtable .

  • The interface IResourceReader is used from the ResourceSet to enumerate resources. The class ResourceReader implements this interface.

  • We already used the ResourceWriter class to create a resource file. ResourceWriter implements the interface IResourceWriter .

  • Additionally there are some classes: ResXResourceSet , ResXResourceReader , and ResXResourceWriter . These classes are similar to ResourceSet , ResourceReader , and ResourceWriter , but create a XML-based resource file .resX instead of a binary file. ResXFileRef can be used to make a link to a resource instead of embedding it inside an XML file.

Localization Example Using Visual Studio .NET

We are going to create a simple Windows application to demonstrate localization using Visual Studio .NET. This application doesn't use complex Windows Forms, and doesn't have any real inner functionality because the main feature that is being demonstrated here is localization. In the sourcecode I'm changing the namespace to Wrox.ProCSharp.Assemblies.Localization , and the class name to BookOfTheDayForm . The namespace is not only changed in the source file BookOfTheDayForm.cs , but also in the project settings, so that all generated resource files will get this namespace, too. This is done within the Common Properties of Project Properties .

To show some issues with localization, this program has a picture, some text, a date, and a number. The picture will also be localized so that the French version is different.

This form is created using the Windows Forms Designer :

click to expand

The values for the Name and Text properties for the Windows Forms elements are listed in this table:

Name

Text

labelBookOfTheDay

Book of the day

labelItemsSold

Books sold

textBoxDate

Date

textBoxTitle

Professional C#

textBoxItemsSold

10000

In addition to this form, we want to display a message box with a greeting, where the greeting message is different depending on the current time. This should demonstrate that the localization for dynamically created dialogs must be done differently. In the method WelcomeMessage() , we display a message box using MessageBox.Show() . We call the method WelcomeMessage() in the constructor of the form class BookOfTheDayForm, before the call to InitializeComponent() . Here's the code for the WelcomeMessage method:

   public void WelcomeMessage()     {     DateTime time = DateTime.Now;     string message;     if (time.Hour <= 12)     {     message = "Good Morning";     }     else if (time.Hour <= 19)     {     message = "Good Afternoon";     }     else     {     message = "Good Evening";     }     MessageBox.Show(message + " \nThis is a localization sample.");     }   

The number and date in the form should be set using formatting options. We add a new method SetDateAndNumber() to set the values with the format option. In a real application these values could be received from a Web Service or a database, but in this example we are just concentrating on localization. The date is formatted using the D option (to display the long date name). The number is displayed using the picture number format string ###,###,### where " # " represents a digit and " , " is the group separator:

   public void SetDateAndNumber()     {     DateTime date = DateTime.Today;     textBoxDate.Text = date.ToString("D");     Int32 itemsSold = 327444;     textBoxItemsSold.Text = itemsSold.ToString("###,###,###");     }   

In the constructor of the BookOfTheDayForm class both the WelcomeMessage and SetDateAndNumber () methods are called.

 public BookOfTheDayForm() {   WelcomeMessage();   //    // Required for Windows Form Designer support    //    InitializeComponent();   SetDateAndNumber();   } 

A magic feature of the Windows Forms designer is started when we set the Localizable property of the form from false to true : this results in the creation of an XML-based resource file for the dialog box that stores all resource strings, properties (including the location of Windows Forms elements), embedded pictures, and so on. In addition, the implementation of the InitializeComponent() method is changed; an instance of the class System.Resources.ResourceManager is created, and to get to the values and positions of the text fields and pictures, the GetObject() method is used instead of writing the values directly in the code. GetObject() uses the CurrentUICulture property of the current thread for finding the correct resources.

Here is part of InitalizeComponent() before the Localizable property is set to true , where all properties of textBoxTitle are set:

   private void InitializeComponent()     {     //...     this.textBoxTitle = new System.Windows.Forms.TextBox();     //...     //     // textBoxTitle     //     this.textBoxTitle.Cursor = System.Windows.Forms.Cursors.Default;     this.textBoxTitle.Location = new System.Drawing.Point(32, 136);     this.textBoxTitle.Name = "textBoxTitle";     this.textBoxTitle.Size = new System.Drawing.Size(432, 20);     this.textBoxTitle.TabIndex = 2;     this.textBoxTitle.Text = "Professional C#";   

This is the automatically changed code for InitalizeComponent() with the Localizable property set to true :

   private void InitializeComponent()     {     System.Resources.ResourceManager resources =     new System.Resources.ResourceManager(typeof(BookOfTheDayForm));     //...     this.textBoxTitle = new System.Windows.Forms.TextBox();     //...     //     // textBoxTitle     //     this.textBoxTitle.AccessibleDescription = ((string)     (resources.GetObject("textBoxTitle.AccessibleDescription")));     this.textBoxTitle.AccessibleName = ((string)     (resources.GetObject("textBoxTitle.AccessibleName")));     this.textBoxTitle.Anchor = ((System.Windows.Forms.AnchorStyles)     (resources.GetObject("textBoxTitle.Anchor")));     this.textBoxTitle.AutoSize = ((bool)     (resources.GetObject("textBoxTitle.AutoSize")));     this.textBoxTitle.BackgroundImage = ((System.Drawing.Image)     (resources.GetObject("textBoxTitle.BackgroundImage")));     this.textBoxTitle.Cursor = ((System.Windows.Forms.Cursor)     (resources.GetObject("textBoxTitle.Cursor")));     this.textBoxTitle.Dock = ((System.Windows.Forms.DockStyle)     (resources.GetObject("textBoxTitle.Dock")));     this.textBoxTitle.Enabled = ((bool)     (resources.GetObject("textBoxTitle.Enabled")));     this.textBoxTitle.Font = ((System.Drawing.Font)     (resources.GetObject("textBoxTitle.Font")));     this.textBoxTitle.ImeMode = ((System.Windows.Forms.ImeMode)     (resources.GetObject("textBoxTitle.ImeMode")));     this.textBoxTitle.Location = ((System.Drawing.Point)     (resources.GetObject("textBoxTitle.Location")));     this.textBoxTitle.MaxLength = ((int)     (resources.GetObject("textBoxTitle.MaxLength")));     this.textBoxTitle.Multiline = ((bool)     (resources.GetObject("textBoxTitle.Multiline")));     this.textBoxTitle.Name = "textBoxTitle";     this.textBoxTitle.PasswordChar = ((char)     (resources.GetObject("textBoxTitle.PasswordChar")));     this.textBoxTitle.RightToLeft = ((System.Windows.Forms.RightToLeft)     (resources.GetObject("textBoxTitle.RightToLeft")));     this.textBoxTitle.ScrollBars = ((System.Windows.Forms.ScrollBars)     (resources.GetObject("textBoxTitle.ScrollBars")));     this.textBoxTitle.Size = ((System.Drawing.Size)     (resources.GetObject("textBoxTitle.Size")));     this.textBoxTitle.TabIndex = ((int)     (resources.GetObject("textBoxTitle.TabIndex")));     this.textBoxTitle.Text = resources.GetString("textBoxTitle.Text");     this.textBoxTitle.TextAlign =     ((System.Windows.Forms.HorizontalAlignment)     (resources.GetObject("textBoxTitle.TextAlign")));     this.textBoxTitle.Visible = ((bool)     (resources.GetObject("textBoxTitle.Visible")));     this.textBoxTitle.WordWrap = ((bool)     (resources.GetObject("textBoxTitle.WordWrap")));   

How does it work - where does the resource manager get the data? When we set the Localizable property to true , a resource file, BookOfTheDay.resX , was generated. In this file, the scheme of the XML resource can be found first, followed by all elements in the form: Type , Text , Location , TabIndex , and so on.

The following example shows a few of the properties of textBoxTitle : the Location property has a value of 36,136 , the TabIndex property has a value of 2 , the Text property is set to Professional C#, etc. For every value the type of the value stored, as well. For example, the Location property is of type System.Drawing.Point , and this class can be found in the assembly System.Drawing .

Why are the locations and sizes also stored in this XML file? With translations, many strings will have completely different sizes and don't any longer fit in to the original positions. When the locations and sizes all are stored inside the resource file, everything that's needed for localizations is in these files, and is separate from the C# code:

   <data name="textBoxTitle.Location" type="System.Drawing.Point, System.Drawing,     Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">     <value>36, 136</value>     </data>     <data name="textBoxTitle.TabIndex" type="System.Int32, mscorlib,     Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">     <value>2</value>     </data>     <data name="textBoxTitle.Text">     <value>Professional C#</value>     </data>     <data name="textBoxTitle.TextAlign"     type="System.Windows.Forms.HorizontalAlignment, System.Windows.Forms,     Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">     <value>Left</value>     </data>     <data name="textBoxTitle.Visible" type="System.Boolean, mscorlib,     Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">     <value>True</value>     </data>   

When changing some of these resource values, it's not necessary to work directly with the XML code. We can change these resources directly in the Visual Studio Designer. Whenever we change the Language property of the form and the properties of some form elements, a new resource file is generated for the specified language.

We create a German version of the form by setting the Language property to German, and a French version by setting the Language property to French. For every language we get a resource file with the changed properties: BookOfTheDayForm.de.resx and BookOfTheDayForm.fr.resx . Here are the changes needed for the German version of the form:

German - Name

Value

$this.Text (title of the form)

Buch des Tages

labelItemsSold.Text

B



Professional C#. 2nd Edition
Performance Consulting: A Practical Guide for HR and Learning Professionals
ISBN: 1576754359
EAN: 2147483647
Year: 2002
Pages: 244

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net