Chapter 38. Resource Files

WHAT'S IN THIS CHAPTER?

  • Understanding what an application resource is

  • Defining and using resources within your application

  • Defining culture-specific resources

  • Extending the default resource types

Developers often overlook the humble XML resource file, because it is often hidden by Visual Studio 2010 so as not to clutter the solution. Because its most common use is as a backing file for forms or web pages, you can write large applications without interacting directly with resource files. However, resource files are an important tool that you need to be able to use in order to write applications that can be easily maintained and translated into other languages.

The first part of this chapter explains why resource files are important and describes the features that enable developers to work with them. The remainder of the chapter explains how you can use resource files to localize an application for different languages and cultures.

WHAT ARE RESOURCES?

A resource is any data required by an application, whether it is a string, an icon, an image, or even an audio clip. Resources are non-executable and support the running of the application through the provision of data such as location, size, and other physical properties of controls. Though most resources are strings, images, audio clips, or icons, there is no reason why a resource could not be a more complex object that supports serialization.

Three types of resource files can be compiled into an application: text, resx (XML resource file), and resources (binary resource file) file formats. Whole files can also be embedded as application resources where needed. Most developers who use Visual Studio 2010 will use resx files and embedded file resources.

Text File Resources

Text files are the most basic sort of resource because they are limited to providing string values. In applications for which a large number of string literals need to be managed, using a simple text file can be the easiest way to do it because that way they are not cluttered among the other resources of the application.

The format of strings defined in a text resource file is a name-value pair, where the name is used to reference the resource in code, as shown in the following example:

Error_Unable_To_Connect = Unable to connect to specified server

Because each name-value pair is delimited by a new line, this character cannot be added to the string. However, C-style escape characters can be used to insert new lines ( ) or tabs ( ) into the text.

You can add comments to the resource file by prefixing a line with a semicolon, as shown here:

;Error message to be displayed when a connection could not be made to the server
Error_Unable_To_Connect = Unable to connect to specified server

Text resource files should be saved with the file extension of .txt or .restext. The latter is useful when you want to distinguish text resource files from regular text files.

Although text resource files are easy to edit and update, it is harder to integrate them into your application. As text files, they cannot be directly compiled into an application; they must instead be converted into either resx or resources files. Do this using the Resource Generator utility, resgen.exe, located in the in folder of the Windows SDK (located at C:Program FilesMicrosoft SDKsWindowsv7.0Ain):

resgen StringResources.txt StringResources.resources

Include the output file — in this case, StringResources.resources — in your application to give yourself access to those resources.

A prebuild event can be used to convert text resource files into a resources file that can be compiled into the main application build. This will ensure that the resources files contained in the application are always up to date. To do this, include the text resource file in the application and set the build action property to None. Navigate to the Project Properties window for the project that contains the text resource file and on the Compile tab select Build Events (VB) or the Build Events tab (C#). In the prebuild events, enter the Resgen command required to compile your text resource file:

"C:Program FilesMicrosoft SDKsWindowsv7.0Ain
esgen.exe"
   "$(ProjectDir)StringResources.txt" "$(ProjectDir)StringResources.resources"

Building the application generates the resources file that needs to be included within your application with the build action property set to Embedded Resource. Figure 38-1 illustrates how both the text file and the resources file are included within an application with appropriate build action properties.

Figure 38-1

Figure 38-1. Figure 38-1

Resx Resource Files

A much more user-friendly format for resources is the XML resource file, commonly referred to as a resx file. This is a simple XML data file that contains name-value pairs of XML nodes. The advantage of this format is that the value is not restricted to just a string; it can be of any type that is serializable or that can be represented as a string.

The following XML snippet shows a resource named HelloWorld, with an associated value and comment. As you can see from the code, no information is available about the type of data contained within the resource, because it is a string resource:

<data name="HelloWorld">
<value>Say Hello</value>
<comment>This is how we say hello</comment>
</data>

The next snippet illustrates how a more complex data type can be stored in a resource file as a string representation. It also shows how an assembly alias can be used to reference an external assembly that contains type information. When this resource is accessed, the type information will be used to convert the string value to an object of this type:

<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0,
Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="Button1.Location" type="System.Drawing.Point, System.Drawing">
<value>71, 43</value>
</data>

Although resx files can be included in an application without your having to use the Resource File Generator (Resgen), they are still converted prior to being compiled into the application. During the build process, resources files are generated for each resx file in the application. These are subsequently linked into the application.

Binary Resources

The third resource format is the binary resource file, indicated by the .resources file extension. Behind the scenes, Visual Studio 2010 converts all resx files into .resources files as an intermediate step during compilation (you can see these files in the objdebug folder for your project), and as you saw earlier in this chapter, you must manually convert text resources into .resources files using Resgen. You can also integrate other binary resources into your project by simply including the .resources file and setting the build action to Embedded Resource.

Adding Resources

Visual Studio 2010 supports a rich user interface for adding and modifying resource files. It is still possible to view the contents of a resource file within the IDE. However, unless the resource is a string, or has a string representation, it is not possible to modify the value within the resource file. The resource editor provides support for strings, images, icons, audio files, and more.

Double-clicking the My Project (VB) or Properties (C#) node for a project in the Solution Explorer opens the project properties editor, from which you can select the Resources tab to open the default, or project, resource file. For C# projects you will then need to click the presented link to create the resource file (VB projects already have a default resource file). When the default resource file opens, you will see that in the top left-hand corner of the resource editor is a drop-down list that navigates among resources of different types, as shown in Figure 38-2. Double-clicking any resx file within the Solution Explorer also brings up this resource editor.

Figure 38-2

Figure 38-2. Figure 38-2

The editor displays the resource in an appropriate format, according to its type. For example, strings are presented in an editable textbox, whereas images are presented as thumbnails that can be opened and edited. Adding new resources is as simple as selecting the Add Resource drop-down, choosing the appropriate resource type, and adding the necessary information. Once you have added a resource, it appears in the resource editor, as shown in Figure 38-3.

Figure 38-3

Figure 38-3. Figure 38-3

Figure 38-3 shows an additional column that gives you the option to specify a comment alongside your resource. Unfortunately, the resource editor is the only place in Visual Studio 2010 where this comment is displayed.

Embedding Files as Resources

It is often necessary to embed an entire file in an application. You can do this by including the file in the application and modifying the build action. Depending on the file type, when the item is included in the application, the build action (click the file and open the Properties window) is normally set to either Compile or None. If this is changed to Embedded Resource, the entire file is added to the application as an embedded resource.

Alternatively, you can use the resource editor shown in Figure 38-2 to add a file resource. When images, icons, and other files are added to an existing resource file by means of the resource editor, they are added as a resxfileref item. The file will appear in the resources directory, but the build action will be None. When the application is built, these files are compiled into the resources file prior to being linked into the application. In the past, the data from these files was pulled out and added to the resx file as a binary block. This meant that, once added, the data couldn't be easily modified. With the file reference item, the data remains in an associated file and can easily be updated.

Naming Resources

Resources are named for the resource file to which they belong and the root namespace. For example, if you have a resource file called Sample.resources in a project called MyProject, the full resource name will be MyProject.Sample.

This is particularly important to remember when you make a file an embedded resource by changing the build action. You can access any file by prefixing the filename with the project name. Unlike with resource files, the name of the file retains the extension. For example, if you have a file called ASimpleDataDocument.doc in a project called MyProject, it will need to be referenced as MyProject.ASimpleDataDocument.doc.

Note

Any directory structure will be ignored for the purpose of naming embedded resources.

Accessing Resources

The method that you use to access resources depends on how they are embedded in the application. You have already seen that you have two ways to embed resources: the first is to add a file to the project and set the build action to Embedded Resource; the second is via the resource editor. To access resources added by a change to the build action, you need to use the GetManifestResourceNames and GetManifestResourceStream methods. The following code retrieves the names of all the resources in the assembly by querying the manifest. It then creates a stream for accessing the relevant resource file. As discussed in the previous section, the name of the embedded resource file returned by the GetManifestResourceNames method and accepted by the GetManifestResourceStream method is in the form Root namespace .Filename .File_extension (for example, MyProject.ASimpleDataDocument.doc).

VB
Dim names = Reflection.Assembly.GetExecutingAssembly.GetManifestResourceNames
Dim resources = From n In names
               Select Assembly.GetExecutingAssembly.GetManifestResourceStream(n)
For Each r In resources
    Using strm As New IO.StreamReader(r)
        MsgBox(strm.ReadToEnd)
    End Using
Next
C#
var names = Assembly.GetExecutingAssembly().GetManifestResourceNames();
var resources = from n in names
               select Assembly.GetExecutingAssembly().GetManifestResourceStream(n);
foreach (var r in resources){
    using (var strm = new StreamReader(r)){
        MessageBox.Show(strm.ReadToEnd());
    }
}

Resources added via the resource editor can be accessed in code by means of a resource manager, which you can easily create from the name of the resource file to which they belong and a reference to the assembly from which the resource should be extracted:

VB
Dim res As New ResourceManager("WorkingWithResources.Resources",
                               Assembly.GetExecutingAssembly)
C#
var res = new ResourceManager("WorkingWithResources.Properties.Resources",
                               Assembly.GetExecutingAssembly());

Once created, resources can be extracted by means of either the GetObject or GetString function:

res.GetObject("StringResource")

For more complex resources, such as files, you may also want to use the GetStream function. All three functions take the name of the resource as the only parameter.

Designer Files

The Resource Generator utility, Resgen, has a number of improvements that enable you to build strongly typed wrapper classes for your resource files. When you add a resx file to your application, Visual Studio 2010 automatically creates a designer file that wraps the process of creating a resource manager and accessing the resources by name. The accessor properties are all strongly typed and are generated by the designer to reduce the chance of invalid type conversions and references. For example, if you have a string resource, StringResource, contained in a resource file, MyResources, you can use the following code to access the string:

VB
My.Resources.MyResources.MyStringResource
C#
MyResources.StringResource

You will notice that the designer-generated code is different for VB and C#. This is because C# uses the generic ResXFileCodeGenerator custom tool whereas VB uses the VbMyResourcesResXFileCodeGenerator custom tool to integrate the resource file into the My namespace.

Unfortunately, Visual Studio 2010 does not automatically generate the designer file for text resource files, because text resource files cannot be explicitly added to the application. The process of generating a resource file from the text file can be extended to include the generation of the designer file.

A new argument has been added to Resgen that facilitates the generation of this designer file:

resgen sample.txt sample.resources /str:vb

Both of the output files need to be added to the application so that the resources are accessible. To ensure that the resources can be correctly accessed, you must ensure that the naming used within the designer file matches the naming of the compiled resources. You can provide additional parameters to control the namespace, class name, and output filename:

resgen sample.txt defaultnamespace.sample.resources
    /str:vb,defaultnamespace,sample,sample.vb

In this case, the fully qualified output class would be defaultnamespace.sample, and the use of this file would allow access to resources without an exception being raised. Once the correct command has been determined, you can update your prebuild event to include the generation of the designer file. This way, every time the file is modified and saved and the application is compiled, the designer file will be re-created.

RESOURCING YOUR APPLICATION

Writing an application often requires data such as images, icons, or sounds (collectively known as resources) to enhance the appearance of the application. Furthermore, best coding practices suggest that the use of constant strings throughout your application be avoided. In either case, you can put together a custom solution that stores these resources in files that need to be shipped with the application.

An alternative is to include them in a resource file that can be compiled into your application. This way you not only have the resources in a format that you can work with, but they are also automatically available within your application.

In Visual Studio 2010, forms are initially represented by two files: the generated designer file (for example, Form1.Designer.vb) and the code-beside file (for example, Form1.vb). When a control, such as a button, is first added to the form, a resource file (for example, Form1.resx) is automatically created for the form. By default, this resource file contains very little data, because most properties are hard-coded into the designer file. This file becomes very important when localization is turned on for the form. When this is done, via the properties grid shown in Figure 38-4, the designer properties for the controls on the form are persisted to the resource file.

Figure 38-4

Figure 38-4. Figure 38-4

The following code snippet shows the designer-generated method InitializeComponent, which creates and sets properties on Button1. This is how the code would appear with the Localizable property on the form set to False:

Private Sub InitializeComponent()
    Me.Button1 = New Button
    '
    'Button1
    '
    Me.Button1.Location = New Point(71, 43)
    Me.Button1.Size = New Size(185, 166)
    Me.Button1.Text = "Button1"
    Me.Button1.TabIndex = 0
    Me.Button1.Name = "Button1"
    Me.Button1.UseVisualStyleBackColor = True
    '
    'Form1
    '
    Me.Controls.Add(Me.Button1)
End Sub

Once the Localizable property of the form has been set to True, the form uses the new ComponentResourceManager class to load and apply properties found in the associated resource file. (This framework class is covered in more detail later in this chapter.)

Private Sub InitializeComponent(    )
    Dim resources As New ComponentResourceManager(GetType(Form1))
    Me.Button1 = New Button
    '
    'Button1
    '
    resources.ApplyResources(Me.Button1, "Button1")
    Me.Button1.Name = "Button1"
    Me.Button1.UseVisualStyleBackColor = True
    '
    'Form1
    '
    Me.Controls.Add(Me.Button1)
End Sub

Although the resource files generated by the forms designer can be manually edited, this is not encouraged because changes may be overwritten the next time the file is regenerated by the designer.

When resource files are used properly, they can provide a number of benefits because they are a convenient place to store strings, icons, images, and other data that might be referenced by an application. The use of resource files, both for tracking form properties and for application data, is a must for any application that needs to be translated for a foreign culture (we use the term "culture" here because more than language can differ among countries and ethnic groups). Resource files enable developers to provide alternative data for different cultures. When the application is run, the .NET Framework uses the current culture information to determine which data to load, based upon the resource fallback process (the fallback process is discussed in the section "Loading Culture Resource Files" later in this chapter). Common examples of information that might need to be varied among cultures are prompts, titles, error messages, and button images.

Control Images

A number of Windows Forms controls have images as properties. For example, the PictureBox control has Image, ErrorImage, and InitialImage properties. If you click the ellipsis in the value column of the Properties window for any of these properties, you see the dialog shown in Figure 38-5, which enables you to select an image for the specified property.

Figure 38-5

Figure 38-5. Figure 38-5

Before selecting an image, you have to decide whether you want to store it in the resource file associated with the current form (that is, a Local resource) or in a project-level resource file. The former option stores the image in a Base64-encoded block within the actual resource file, whereas the latter adds the image to the project and adds an appropriate reference to the selected resource file. Clearly the latter is normally preferable, because it means that you can change the image without having to import it again.

SATELLITE RESOURCES

One of the big advantages of placing data in a resource file is the resulting capability to translate the data for foreign cultures. Instead of all the languages being included in a single resource file, each culture's data is stored in a resource file that has a suffix defined by that culture.

Cultures

Cultures are defined by a combination of two lowercase letters, which represent the language, and two uppercase letters, which represent the country or region of the culture. These two pairs of letters are separated by a hyphen. For example, U.S. English and Australian English are represented as en-US and en-AU, respectively. The corresponding resource files for these cultures would be MyResource.en-US.resx and MyResource.en-AU.resx. You can find a full list of culture identifiers at http://msdn2.microsoft.com/en-us/library/system.globalization.cultureinfo.aspx. If you are curious, you can look over all the available cultures, which are returned by CultureInfo.GetCultures(CultureTypes.AllCultures). About 220 cultures exist, and they can be classified as follows:

  • Invariant culture: No language or country identifier (for example, Form1.resx). Data is not dependent upon culture — for example, this might be the company logo, which will not vary and is not dependent upon culture information.

  • Neutral culture: Language identifier (for example, Form1.en.resx). Data is dependent upon language alone — for example, a simple warning message that merely needs to be translated.

  • Specific culture: Language and country identifier (for example, Form1.en-US.resx). Data is dependent upon both language and country/region — for example, form layout, color, and prompts should all be translated and adjusted for specific regions.

Creating Culture Resources

If you are creating additional resource files for a form, it is important to ensure that the Localizable property is set to True. You have three ways to create culture-specific resource files:

  • If you know the identifier of the culture for which you want to generate a resource file, you can simply save the resx file to filename. culture_identifier.resx. For example, if you were converting the resource file Form1.resx to Australian English, you would save it as Form1.en-AU.resx. You will notice that when you do this, Visual Studio removes the original resx file from the solution and adds the new culture-specific resx file. To get both files to show up nested under the Form1 node, you actually need to exclude the new resx file, refresh the solution view (by closing and reopening the solution), and then put both files back into the project.

  • Visual Studio supports a much better way to create culture-specific resource files for forms. From the Properties window for the form you can select Language. The name of this property is slightly misleading because it adjusts not only the language, but also the country/region of the form in designer mode. This property is initially set to (Default) and should always be returned to this setting after you have finished generating or modifying resource files for specific cultures. To generate the resource file for Australian English, select English (Australia) from the Language drop-down and make the appropriate changes to the form. Once you are comfortable with the new layout, save it and reset the Language property to (Default).

  • The last way to generate culture-dependent resource files is to use WinRes.exe. Although it's not added to the Start menu, it is available under the Windows SDK folder (located at C:Program FilesMicrosoft SDKsWindowsv7.0Ain) and is a graphical utility for generating resource files for forms. This utility can load an existing resource file, allow properties of all controls on the form to be modified, and then save the changes to a particular culture resource file. Before opening a form's resource file using this utility, make sure that the Localizable property is set to True; otherwise the file will not load properly.

Loading Culture Resource Files

At this point you might be wondering how resource files interact, and whether culture-specific resource files have to be created and compiled at the same time as the main application. The answer to both of these questions lies in the resource fallback process, which is the mechanism by which the ResourceManager class loads resources.

The fallback process has three levels, based upon the current user interface culture (UI culture) of the executing thread. This can be accessed in code via the CultureInfo.CurrentUICulture property. Be aware that this is different from CultureInfo.CurrentCulture, which is the current culture used in string comparisons, date formats, and so on. Unlike the current culture, which is based upon the regional settings of the computer (which you can adjust using Control Panel

Loading Culture Resource Files

Although you can't change the default user interface culture, you can adjust this property in code. A word of caution here, however: without the interface pack installed, some cultures may not display correctly.

Thread.CurrentThread.CurrentUICulture = New CultureInfo("en-US")

Using the current user interface culture, the fallback process tries to locate resources based on a culture match. For example, if the UI culture is en-US, the process would start off by looking for specific culture resources that match both language (English) and country (U.S.). When no resource can be located, the process falls back to neutral culture resources that match just the language (English). If the fallback process still can't locate a resource, the process falls back to invariant culture, indicating there is no match for language or country.

Satellite Culture Resources

So far we have mentioned only how a resource can be converted into a new culture and added to an application. Although this method gives you control over which cultures are deployed with your application, it would be better if you didn't have to rebuild your entire application whenever a culture resource needed to be modified, or when you decided to add support for a new culture.

When Visual Studio 2010 compiles culture resources, it splits the resource files into a hub-and-spoke arrangement, using satellite assemblies to contain culture resources. At the hub is the main assembly that would contain the invariant resources. Satellite assemblies are then created for each culture for which a resource has been created. The naming of the satellite assembly is of the form "MyApp.resources.dll" and it is located in a subdirectory named according to the culture under the main output path. Although there is an implicit relationship between specific cultures and neutral cultures (for example, between en-US and en), satellite assemblies for both types should reside in a subdirectory under the main output path.

Another alternative is for the main assembly and/or satellite assemblies to be installed into the Global Assembly Cache (GAC). In this case, each assembly must be strongly named so that it is unique within the cache.

Clearly, the resource fallback process needs to accommodate assemblies both in the GAC and in subdirectories. Hence, for each culture level (specific, neutral, and invariant) the GAC is checked first, followed by the culture subdirectory. Finally, if no resource is found, an exception is raised.

Note that culture resource files do not have to contain all the resources defined in the default resource file. The resource fallback process will load the resource from the default resource file if it is not located in a more specific resource file, so it makes sense to save in the specified culture only those resources that are different.

ACCESSING SPECIFICS

Numerous shortcuts have been built into the .NET Framework to support the most common tasks related to accessing resources. These shortcuts include single-line image loading, cross-assembly referencing, and the use of the ComponentResourceManager class.

Bitmap and Icon Loading

Images and icons are two of the most common data types held in resource files. Therefore, both the Bitmap and Icon classes in the framework support a constructor that can create an instance directly from a resource without the need for a resource manager. For example, if you have an image, MyImage.bmp, that you included in your project by setting the build action to Embedded Resource, you can access the image directly using the following code:

Dim img As New Bitmap(GetType(ThisClass), "MyImage.bmp")

Here the class, ThisClass, can be any class in the root namespace of the project that contains the embedded resource.

Cross-Assembly Referencing

In Visual Studio 2010, you can control the accessibility level for resource files. With the Access Modifier option in the resource editor, as shown in Figure 38-6, you can choose between keeping a resource internal to the assembly it is defined in (Friend [VB] or Internal [C#]) or making it publicly accessible (Public).

Figure 38-6

Figure 38-6. Figure 38-6

If you set the Access Modifier to Public, you can then access this resource from another assembly by prefixing the resource name with the assembly name. For example, in the following code the MyPerson resource is located in the CustomResourceType assembly:

Dim p As Person = CustomResourceType.My.Resources.MyPerson

ComponentResourceManager

In the first example in this chapter, after localization was turned on, a ComponentResourceManager object was used to retrieve resources associated with the form. The ComponentResourceManager extends the base ResourceManager by providing additional functionality for retrieving and applying component properties. Here are the original four lines required to set the properties defined for Button1:

Me.Button1.Location = New Point(71, 43)
Me.Button1.Size = New Size(185, 166)
Me.Button1.Text = "Button1"
Me.Button1.TabIndex = 0

Using the ComponentResourceManager, they can be condensed into just one line:

resources.ApplyResources(Me.Button1, "Button1")

In previous versions of Visual Studio, the code generated when localization was turned on was much more verbose. For each property, a separate call was made to the ResourceManager to retrieve it by name, as shown in this code snippet:

Me.Button1.Location = CType(resources.GetObject("Button1.Location"), Point)
Me.Button1.Size = CType(resources.GetObject("Button1.Size"), Size)
Me.Button1.TabIndex = CType(resources.GetObject("Button1.TabIndex"), Integer)
Me.Button1.Text = resources.GetString("Button1.Text")

It is still possible to write this code because the GetObject method is still available on the ComponentResourceManager. The issue with writing this code is that each property that is going to be localized needs to be known at compile time. Because of this, every property on every control was added to the resource file. This added excess properties (even when they were no different from the default values) to the resource file. It also added huge overhead during the loading up of a form, because each property was set via a resource property.

The ApplyResources method in the ComponentResourceManager class works in reverse. When you specify a control name, which must be unique on a form, all resources that start with that prefix are extracted. The full resource name is then used to determine the property to set on the control. For example, a resource with the name Button1.Location would be extracted for the control called Button1, and the value used to set the Location property on that control.

This process eliminates the need to have all properties specified in a resource file. It also creates the need for culture resource files to specify additional properties that might not have been defined in the default resource file.

You might be wondering whether any additional penalties exist in using the ComponentResourceManager. To set a property on a control using the name of the property, the ComponentResourceManager uses reflection to find the appropriate property. Once it has been retrieved, it can be invoked. Each search that is done in order to set the property is relatively expensive. However, given the reduced number of properties to be set, the tradeoff is definitely worthwhile, because the application can easily be localized without recompilation of the main application.

CODING RESOURCE FILES

In addition to the rich visual tools that Visual Studio 2010 now provides for editing resource files, it is possible to use code to create resource files. The .NET Framework provides support for reading and writing resource files using two interfaces: IResourceReader and IResourceWriter. Once the resource files have been created, they need to be added to the application or manually linked so that they can be referenced within the application.

  • IResource Reader: The reader interface ensures that resource readers have the following methods:

    • GetEnumerator: The GetEnumerator method retrieves an IDictionaryEnumerator object that permits the developer to iterate over each of the resources in the resource file.

    • Close: The Close method is used to close the resource reader and release any associated resources.

  • IResource Writer: The writer interface ensures that resource writers have the following methods:

    • AddResource: Three overloads to the AddResource method support adding resources to the resource file. Both of the framework implementations of this interface have either an additional overload of this method or an alternative method for adding resources. The overloads that are part of this interface support adding resources in a name-value pair. Each method has the resource name as the first parameter and a value, such as a string, byte array, or object, as the second parameter. The final implementation that takes an object as a parameter may need to be serializable or converted to a string via a type converter.

    • Close: The Close method writes resources out to the stream before closing it.

    • Generate: Unlike the Close method, the Generate method simply writes the resources out to the stream without closing it. Once this method is called, any other method will cause an exception to be raised.

ResourceReader and ResourceWriter

ResourceReader and ResourceWriter are an implementation of the IResource interfaces to support reading and writing directly to resources files. Although reading and writing to this format is the most direct approach, because it reduces the need to use Resgen to generate the resources file, it does limit the quality of information that can be retrieved in reading from the file. Each resource is treated as a series of bytes where the type is unknown.

ResxResourceReader and ResxResourceWriter

ResxResourceReader and ResxResourceWriter are more versatile implementations of the IResource interfaces. In addition to supporting the IResource interface, ResxResourceWriter supports an additional overload of the AddResource method, whereby a ResxDataNode can be added. A ResxDataNode is very similar to a dictionary entry, because it has a key (in this case, the Name property) and a value (which you must set when the node is created). However, the difference is that this node can support additional properties such as a comment and, as an alternative to a value, a file reference (for example, one that indicates where an image needs to be added to a resource file).

As mentioned previously, it is possible to add a file reference to a resx file so that the file is still editable, yet has the benefit of being compiled into the resource file by resgen.exe. The supporting class in the framework is ResxFileRef. This can be instantiated and added as a resource via the ResxResourceWriter. This inserts an XML node similar to the following snippet:

<data name="Figure_11_2" type="ResXFileRef, System.Windows.Forms">
<value>.ResourcesCompanyLogo.tif;System.Drawing.Bitmap, System.Drawing,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>

Warning

Resource files are the best means of storing static application data. Although they are linked in to the application as part of the compilation process, their contents can easily be extracted and made human-readable. Because of this, however, resource files are not suitable for storing secure data such as passwords and credit card information.

CUSTOM RESOURCES

Although Visual Studio provides good support for international application development using resource files, at times it is not possible to get the level of control required using the default behavior. This section delves a little deeper into how you can serialize custom objects to the resource file and how you can generate designer files, which give you strongly typed accessor methods for resource files you have created.

Visual Studio 2010 enables you to store strings, images, icons, audio files, and other files within a resource file. You can do all this using the rich user interface provided. To store a more complex data type within a resource file you need to serialize it into a string representation that can be included within the resource file.

The first step in adding any data type to a resource file is to make that data type serializable. You can do this easily by marking the class with the Serializable attribute. Once it is marked as serializable, you can add the object to a resource file using an implementation of the IResourceWriter interface — for example, ResXResourceWriter:

VB
<Serializable()> _
Public Class Person
    Public Property Name As String
    Public Property Height As Integer
    Public Property Weight As Double
End Class
Dim p As New Person
p.Name = "Bob"
p.Height = 167
p.Weight = 69.5
Dim rWriter As New ResXResourceWriter("foo.resx")
rWriter.AddResource("DefaultPerson", p)
rWriter.Close()
C#
[Serializable()]
public class Person{
    public string Name { get; set; }
    public int Height { get; set; }
    public double Weight { get; set; }
}
var p = new Person(){
                Name = "Bob",
                Height = 167,
                Weight = 69.5};
var rWriter = new ResXResourceWriter("foo.resx");
rWriter.AddResource("DefaultPerson", p);
rWriter.Close();

However, serializing an object this way has a couple of drawbacks:

  • You need to use code to write out this resource file before the build process so that the resource file can be included in the application. Clearly this is an administrative nightmare, because it is an additional stage in the build process.

  • Furthermore, the serialized representation of the class is a binary blob and is not human-readable. The assumption here is that what is written in the generating code is correct. Unfortunately, this is seldom the case, and it would be easier if the content could be human-readable within Visual Studio 2010.

A workaround for both of these issues is to define a TypeConverter for the class and use that to represent the class as a string. This way, the resource can be edited within the Visual Studio resource editor. TypeConverters provide a mechanism through which the framework can determine whether it is possible to represent a class (in this case a Person class) as a different type (in this case as a string). The first step is to create a TypeConverter using the ExpandableObjectConverter, as follows:

VB
Imports System.ComponentModel
Imports System.ComponentModel.Design.Serialization
Imports System.Globalization
Public Class PersonConverter
    Inherits ExpandableObjectConverter
    Public Overrides Function CanConvertFrom(ByVal context As _
                                                ITypeDescriptorContext, _
                                                ByVal t As Type) As Boolean
        If t Is GetType(String) Then Return True
        Return MyBase.CanConvertFrom(context, t)
    End Function
    Public Overrides Function ConvertFrom( _
                                        ByVal context As ITypeDescriptorContext, _
                                        ByVal info As CultureInfo, _
                                        ByVal value As Object) As Object
        If (TypeOf (value) Is String) Then
Try
                If value Is Nothing Then Return New Person()
                Dim vals = CStr(value).Split(","c)
                If vals.Length <> 3 Then Return New Person()
                Return New Person With {.Name = vals(0), _
                                        .Height = Integer.Parse(vals(1)), _
                                        .Weight = Double.Parse(vals(2))}
            Catch
                Throw New ArgumentException("Can not convert '" & _
                                                value.ToString & _
                                                "' to type Person")
            End Try
        End If
        Return MyBase.ConvertFrom(context, info, value)
    End Function
    Public Overrides Function ConvertTo(ByVal context As ITypeDescriptorContext, _
                                        ByVal culture As CultureInfo, _
                                        ByVal value As Object, _
                                        ByVal destType As Type) As Object
        If (destType Is GetType(String) And TypeOf (value) Is Person) Then
            Dim c = TryCast(value, Person)
            Return c.Name & "," & c.Height.ToString & "," & c.Weight.ToString
        End If
        Return MyBase.ConvertTo(context, culture, value, destType)
    End Function
End Class
C#
public class PersonConverter : ExpandableObjectConverter{
    public override bool CanConvertFrom(ITypeDescriptorContext context,
                                        Type t){
        if (typeof(string) == t) return true;
        return base.CanConvertFrom(context, t);
    }
    public override object ConvertFrom(ITypeDescriptorContext context,
                                       CultureInfo culture, object value){
        if (value is string){
            try{
                if (value == null) return new Person();
                var vals = (value as string).Split(','),
                if (vals.Length != 3) return new Person();
                return new Person{
                            Name = vals[0],
                            Height = int.Parse(vals[1]),
                            Weight = double.Parse(vals[2])
                        };
            }
            catch (Exception){
                throw new ArgumentException("Can not convert '" +
                                            value.ToString() + "' to type Person");
            }
        }
return null;
    }

    public override object ConvertTo(ITypeDescriptorContext context,
                                 CultureInfo culture, object value, Type destType){
        if (typeof(string) == destType && value is Person){
            var c = value as Person;
            return c.Name + "," + c.Height.ToString() + "," + c.Weight.ToString();
        }
        return base.ConvertTo(context, culture, value, destType);
    }
}

The class being represented also needs to be attributed with the TypeConverter attribute:

VB
<System.ComponentModel.TypeConverter(GetType(PersonConverter))> _
<Serializable()> _
Public Class Person
    Public Property Name As String
    Public Property Height As Integer
    Public Property Weight As Double
End Class
C#
[System.ComponentModel.TypeConverter(typeof(PersonConverter))]
[Serializable()]
public class Person{
    public string Name { get; set; }
    public int Height { get; set; }
    public double Weight { get; set; }
}

Now you can add this item to a resource file using the string representation of the class. For example, an entry in the resx file might look like this:

<assembly alias="CustomResourceType" name="CustomResourceType, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null" />
<data name="Manager" type="CustomResourceType.Person, CustomResourceType">
<value>Joe,175,69.5</value>
</data>

Note

Creating custom resource types is a difficult process, because Visual Studio 2010 doesn't refresh your TypeConverter after it has been loaded the first time. You can either strongly name the assembly in which the TypeConverter is located and increment the version number each time you change it, or you will have to restart Visual Studio in order for the changes to take effect.

SUMMARY

This chapter demonstrated how important XML resource files are in building an application that can both access static data and be readily localized into foreign languages and cultures. The rich user interface provided by Visual Studio 2010 enables you to easily add resources such as images, icons, strings, audio files, and other files to an application.

The built-in support for localizing forms and generating satellite assemblies empowers developers to write applications that can target a global market. You have also seen that the user interface provided within Visual Studio 2010 is extensible, meaning that you can modify it to interact with your own custom resource types.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.163.250