Strongly Typed Resources


Despite the file's extension, manifest resources are embedded with no type information. For example, if the name of the Azul.jpg file were Azul.quux, that would make no difference to the Bitmap class, which is looking at the data itself for the typeJPEG, PNG, GIF, and so on. It's up to you to properly map the type of each resource to the type of the object that's needed to load it. Fortunately, VS05 can do most of the heavy lifting to assist you in this endeavor.

Application Resources (.resx) Files

Because resources do not come with their own type, you need a place where you can tag your resources with appropriate type information. This is the primary job of application resources files, or .resx files, so called because they employ a .NET-specific XML schema called ResX to persist resource type information.

By default, a standard VS05 wizard-generated Windows Application project comes with a .resx file, Resources.resx, located in the Properties project folder. If you need to add .resx files to your project, perhaps as a mechanism for segregating subsets of resource data, you choose Add New Item from the Project menu and pick the Resources File template, as illustrated in Figure 13.7.

Figure 13.7. Adding a New Resources (.resx) File to a Project


As of this writing, even an empty .resx file is 42 lines of noncomment Extensible Markup Language (XML), most of which is the schema information. The schema allows for any number of entries in the .resx file, each of which has a name, value, comment, type, and Multipurpose Internet Mail Extensions (MIME) type. The following shows the XML for a .resx file with a single string resource, MyString:

<?xml version="1.0" encoding="utf-8"?> <root>   <xsd:schema          xmlns=""     xmlns:xsd="http://www.w3.org/2001/XMLSchema"     xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">        ...   </xsd:schema>   <resheader name="resmimetype">     <value>text/microsoft-resx</value>   </resheader>   <resheader name="version">     <value>2.0</value>   </resheader>   <resheader name="reader">     <value>       System.Resources.ResXResourceReader,       System.Windows.Forms,       Version=2.0.0.0,       Culture=neutral,       PublicKeyToken=b77a5c561934e089</value>   </resheader>   <resheader name="writer">     <value>       System.Resources.ResXResourceWriter,       System.Windows.Forms,       Version=2.0.0.0,       Culture=neutral,       PublicKeyToken=b77a5c561934e089</value>   </resheader>   <data name="MyString" xml:space="preserve">     <value>MyStringValue</value>     <comment>MyStringComment</comment>   </data> </root>


In spite of its text basis, the ResX schema is not meant to be read or edited by humans (as few XML formats are). If you are more visually inclined, you can take advantage of VS05's Resources Editor UI to edit .resx files and turn the overall process of managing resources into a relatively painless experience.

Managing Resources

The Resources Editor, shown in Figure 13.8 with the MyResources.resx file open, is the UI that appears in VS05 when you open a .resx file for editing.

Figure 13.8. The Resources Editor


As you can see in Figure 13.8, the Resources Editor supports categorization of the resources it manages into strings, images, icons, audio (.wavs), and files (either text or binary, including text files, Word documents, or .wmv files). Another category, Other, exists to store extra resource data such as component-defined serialization of design-time data.

Adding Resources

The first way you'll likely use the Resources Editor is to add the desired resources to the .resx file. The Resources Editor offers several ways to add resources from a variety of locations. First, you can use the Resources Editor's Add Resource menu, shown in Figure 13.9.

Figure 13.9. Adding Resources to a .resx File Using the Add Resource Menu Button


Depending on whether the resource exists as a file, you can either import existing resources (excluding string resources) by clicking Add Existing File, or create new resources by clicking any of the other Add New Xxx menu items.[4] Either way, the Resources Editor determines the type and again categorizes it appropriately.

[4] For some reason, the menu item for adding a new image does not contain the word Add, unlike its counterparts, but it still allows you to add a new image.

You can also drag resources onto the Resources Editor from the current project and other applications such as File Explorer. Interestingly, you can even drag selected document text from an application like Word onto the string resources category.

All resource data files added to a .resx file using the Resources Editor are automatically added to a Resources folder in your project. If the folder doesn't already exist, the Resources Editor creates it. As shown in Figure 13.10, this provides a basic, useful segregation of resource data files from the rest of the files in your project.

Figure 13.10. Resources Editor-Managed Project Resources Folder


If a resource already within a project is added to a .resx file, it is neither moved nor copied to the Resources folder, but it still works the same. The reason is that each resource data file managed by a .resx through the Resources Editor references a file in the file system, whether each is included in the VS05 project in which it is used. It's important to consider this indirection because, as we discussed earlier, .resx files merely layer type information over actual manifest resources.

Deleting Resources

Another example of resource indirection derives from resource deletion. A resource can only be cut or removed from a .resx using the Resources Editor, but not actually deleted. This is because you are cutting or removing only resource metadata rather than the resource file (excluding strings, which can only be embedded). Also, the file remains after being cut or removed from the .resx file. If you want to remove all traces of a file resource, you must remove it from the .resx file and then delete it from the project.

Similarly, if you delete the file from your project, its .resx metadata remains. Upon recompilation, a compile-time exception is raised indicating that the actual file referenced from the .resx file is missing and needs to be rectified, as shown in Figure 13.11.

Figure 13.11. Compilation Error Caused by a Named Resource without a Resource File


Consequently, you must make sure that you properly remove all traces of a file resource. The Resources Editor helps out by raising useful exceptions as necessary to help ensure that your project is in a consistent state.

Editing Resources

In some situations, you add resources to your project that are ready for production and require no further editing. In other situations, you may create resources via the Resources Editor, or you may add resources that are not yet complete. Either way, these kinds of resources still need to be edited.

You can edit strings using the Resources Editor. If you are editing your project in VS05, you'll find that VS05 has extra smarts for editing icons, images, and text files. When you double-click resources of these types, VS05 opens an appropriate editor. For icons, it is VS05's own icon editor, for images, it is the Windows Paint application, and for text files, it is a VS05 text editor. By default, double-clicking a sound file opens Windows Media Player to play the .wav file.

However, you aren't limited to VS05's default editors to create and manage resource files; in all cases, you can edit these files with the tools of your choice. In fact, resource support in VS05 is geared toward supporting resource editing in this fashion throughout the development cycle, whether by you, by other developers, or by nontechnical people, such as graphic designers. Support for this hinges on how resources are associated with a project.

Resource Persistence

After an icon, image, or audio resource is added, you can specify the way it is associated with the project either by linking or embedding. You make this choice by setting a resource's Persistence property via the Properties window, as shown in Figure 13.12.

Figure 13.12. Specifying Project Persistence of a File Resource


You'll find that the Persistence property is set to Linked at compile time by default for all resources other than strings (which can only be embedded). This means that the resource's data is stored in a separate file and referenced from the .resx file using a relative file path (the file path you add via the Resources Editor). Keeping this separation makes the resource available for editing by anyone, and is incorporated into the executable only when the project is built. The following excerpt from the .resx file shows how a linked resource is persisted:

<?xml version="1.0" encoding="utf-8"?> <root>   ...   <data     name="MyImage"     type="System.Resources.ResXFileRef, System.Windows.Forms">     <value>       Resources\MyImage.png;       System.Drawing.Bitmap,       System.Drawing,       Version=2.0.0.0,       Culture=neutral,       PublicKeyToken=b03f5f7f11d50a3a     </value>   </data>   ... </root>


If you prefer, you can specify the persistence of your file resource as Embedded, which causes the resource to be sucked into your project and stored in the .resx file for the duration of development:

<?xml version="1.0" encoding="utf-8"?> <root>   ...   <assembly     alias="System.Drawing"     name="System.Drawing,          Version=2.0.0.0,          Culture=neutral,          PublicKeyToken=b03f5f7f11d50a3a" />   <data     name="MyImage"     type="System.Drawing.Bitmap, System.Drawing"     mimetype="application/x-microsoft.net.object.bytearray.base64">     <value>       iVBORw0KGgoAAAANSUhEUgAAADAAAA ...     </value>   </data>   ... </root>


Consequently, you can be assured that the resource exists and thereby avoid compilation errors, unless you delete it yourself. When you switch to Embedded persistence, you can delete the resource file from your project and the application still compiles safely. If you switch back to Linked persistence, a file is re-created for your resource, if it doesn't already exist, and is added to the Resources folder. Note that embedding a resource makes it impossible to edit. Instead, to affect an embedded resource, you have to remove the resource and add an updated file as a new resource.

An interesting side effect of adding or creating resources via the Resources Editor is that all resources, including icons, images, and audio files, are given a Build Action of None. But as you saw when we discussed manifest resources, the Build Action must be set to Embedded Resource for the resource to be compiled into the assembly. Yet, if we compile and execute an app whose resources were created with the Resources Editor, they are there in the assembly. This is possible because the .resx file itself has a Build Action that, by default, is set to Embedded Resource. As the visual Resources Editor suggests, a .resx file is a container for one or more resources to be compiled into an application when built. It also reinforces the fact that whether your resources are linked or embedded, the persistence property is only for the design time; either way, both types of resources are ultimately compiled into an assembly.

Using Typed Resources

As interesting and rich as the resource management experience may be, the proof of the pudding is in the eating. There is a variety of things that you can do with a .resx file both directly and indirectly.

Using the .resx File Directly

When a .resx file has been configured, you might like to access it directlyfor example, to load and enumeratesomething you can do with a little help from the ResXResourceReader class (from the System.Resources namespace):

using System.Collections; using System.Resources; ... using( ResXResourceReader reader =   new ResXResourceReader(@"C:\MyResources.resx") ) {   foreach( DictionaryEntry entry in reader ) {     string s = string.Format("{0} ({1})= '{2}'",       entry.Key, entry.Value.GetType(), entry.Value);     MessageBox.Show(s);   } }


The ResXResourceReader class parses the XML file and exposes a set of named, typed values, but it provides no random access to them. Pulling out a specific entry requires first finding it:

using( ResXResourceReader reader =   new ResXResourceReader(@"C:\MyResources.resx") ) {   foreach( DictionaryEntry entry in reader ) {     if( entry.Key.ToString() == "MyString" ) {       // Display string resource value and stop searching further       MessageBox.Show("MyString = " + (string)entry.Value);       break;     }   } }


The benefit of the .resx file is that type information is embedded along with the data itself, requiring a simple cast to get to a typed version of the data. For linked resources, the resource returned by ResXResourceReader is pulled from the relative file path stored as the resource's value.

Using Compiled .resx Resources

Building the project causes the .resx data to be embedded as nested resources, which are resources grouped into a named container. When a .resx file is embedded as a resource in a VS05 project, it becomes the container for the nested resources it holds.

As part of that process, the .resx file is compiled from the text format to the .resources binary format. Assuming a project's default namespace of ResourcesSample and a .resx file, MyResources.resx, the container of nested resources is named ResourcesSample.MyResources.resources, as shown in ildasm in Figure 13.13.

Figure 13.13. An Embedded .resources File


The .resources extension comes from the resgen.exe tool, which VS05 uses on the .resx file before embedding it as a resource. You can compile a .resx file into a .resources file yourself by using the following command line (which produces MyResources.resources in this case):

C:\> resgen.exe MyResources.resx


After you've compiled a .resx file into a .resources file in the file system, you can load it from the relative path and enumerate it using ResourceReader (from the System.Resources namespace). Except for the name of the class and the input format, usage of the ResourceReader class is identical to that of ResXResourceReader, including the lack of random access for named entries:

using( ResourceReader reader =   new ResourceReader("MyResources.resources") ) {   foreach( DictionaryEntry entry in reader ) {     string s = string.Format("{0} ({1})= '{2}'",       entry.Key, entry.Value.GetType(), entry.Value);     MessageBox.Show(s);   } }


You can read a .resources file from the file system, but because VS05 compiles a .resx file and embeds the resulting .resources file for you, it's easier to access a .resources file directly from its manifest resource stream:

Assembly asm = Assembly.GetExecutingAssembly(); // Load embedded .resources file using(   Stream stream = asm.GetManifestResourceStream(     this.GetType(),     "MyResources.resources") ) {   // Find resource in .resources stream   using( ResourceReader reader = new ResourceReader(stream) ) {     foreach( DictionaryEntry entry in reader ) {       if( entry.Key.ToString() == "MyString" ) {         // Display string resource value         MessageBox.Show("MyString = " + (string)entry.Value);         break;       }     }   } }


This two-step processfirst loading either the .resx or the .resources file and then enumerating all values looking for the one you wantis an inconvenience, so .NET provides the ResourceManager class, which supports random access to resources.

The Resource Manager

The ResourceManager class (from the System.Resources namespace) is initialized with an embedded .resources file:

// Get this type's assembly Assembly asm = this.GetType().Assembly; // Load the .resources file into the ResourceManager // Assumes a file named MyResources.resx within the current // project ("ResourcesSample") ResourceManager resman =   new ResourceManager("ResourcesSample.MyResources", asm);


Notice the use of the project's default namespace appended to the MyResources.resources file. You name your .resources files in exactly the same way you name any other kind of resource, except that the .resources extension is assumed and cannot be included in the name.

Accessing Resources from a Resource Manager

After you've created a resource manager, you can pull out nested resources by name using the GetObject method, casting to the appropriate type. However, if you're using the .resx file for string resources, you use the GetString method instead. This method performs the cast to the System.String type for you:

// MainForm.cs namespace ResourcesSample {   partial class MainForm : Form {     public MainForm() {       ...       // Load ResourcesSample.MainForm.resources from MainForm.resx       ResourceManager resman = new ResourceManager(this.GetType());       // Access the MyString string resource from the ResourceManager       // (these two techniques are equivalent for strings)       string s1 = (string)resman.GetObject("MyString");       string s2 = resman.GetString("MyString");     }   } }


The resource manager acts as a logical wrapper around a resource reader, exposing the nested resources by name, as shown in Figure 13.14.

Figure 13.14. Logical View of the Way ResourceManager Uses ResourceReader


Again, because the naming scheme for embedded resources is somewhat obscured, Figure 13.15 summarizes how VS05 settings influence the names used with ResourceManager.

Figure 13.15. Resource Naming and ResourceManager


Using a resource manager directly, especially one associated with a specific type, is a useful thing to do although somewhat labor-intensive. Fortunately, VS05 incorporates special support that can alleviate the need for such coding.

Strongly Typed Resource Classes

The resource manager simplifies life in the resources world somewhat by providing the weakly typed GetObject method, which returns any resource, although the onus is on you to cast the data to the correct type. Even more simplified is the GetString method, which returns a strongly typed resource value, albeit only for strings.

Neither technique, however, provides the simple and complete solution that developers require: the ability to access any resource in a strongly typed fashion. The answer to this problem is provided by VS05 and a custom tool, ResXFileCodeGenerator, shown in Figure 13.16.

Figure 13.16. The ResXFileCodeGenerator Custom Tool


When a .resx file is saved, VS05 applies the custom tool to the .resx file, generating a corresponding .Designer.cs file, as shown in Figure 13.17.

Figure 13.17. A .Designer.cs Code File Associated with a .resx File After Project Compilation


The .Designer.cs file exposes a class with the same name as the .resx file; the class is located within a namespace that corresponds to defaultNamespace.projectPath. For example, the following code shows a slightly abridged version of what is generated for MyResources.resx when resources are absent:

namespace ResourcesSample {    /// <summary>    ///      A strongly typed resource class, for looking up localized    ///       strings, etc.    /// </summary>    // This class was autogenerated by the StronglyTypedResourceBuilder    // class via a tool like ResGen or Visual Studio.    // To add or remove a member, edit your .resx file and then rerun ResGen    // with the /str option, or rebuild your VS project.    internal class MyResources {      static global::System.Resources.ResourceManager resourceMan;      static global::System.Globalization.CultureInfo resourceCulture;      internal MyResources() {}      /// <summary>      ///   Returns the cached ResourceManager instance used by this      ///   class.      /// </summary>      internal static global::        System.Resources.ResourceManager ResourceManager {        get {          if( (resourceMan == null) ) {            global::System.Resources.ResourceManager temp =              new global::System.Resources.ResourceManager(                "ResourcesSample.MyResources",                typeof(MyResources).Assembly);            resourceMan = temp;          }          return resourceMan;        }      }      /// <summary>      ///   Overrides the current thread's CurrentUICulture property for      ///   all resource lookups using this strongly typed resource class.      /// </summary>      internal static global::System.Globalization.CultureInfo Culture {        get { return resourceCulture; }        set { resourceCulture = value; }      }    } }


There are two key features of the MyResources type. First, it provides static access to a ResourceManager via the like-named ResourceManager property, which relieves you of the need to write the creation logic we saw earlier. Second, static access to localization information is exposed from a CultureInfo object via the Culture property (localization is discussed extensively later in this chapter).

Although these helper properties are useful in their own right, the generated .Designer.cs file becomes much more interesting when resources are added to it. The following shows how MyResources.Designer.cs exposes a string, an icon, an image, a sound, and a text file resource:

using System.Drawing; using System.IO; ... namespace ResourcesSample {   internal class MyResources {     ...     internal static Icon MyIcon { get { ... } }     internal static Bitmap MyImage { get { ... } }     internal static UnmanagedMemoryStream MySound { get { ... } }     internal static string MyString { get { ... } }     internal static string MyTextFile { get { ... }   } }


Each resource is exposed as a strongly typed, static, read-only property. The beauty of this implementation is that developers now need only write a single line of code to access any single resource:[5]

[5] A great benefit of writing code against strongly typed implementations is, of course, that such code can be checked for errors at compile time.

// MainForm.cs namespace ResourcesSample {   partial class MainForm : Form {     public MainForm() {       ...       // Access strongly typed resources from MyResources.resx       string myString = MyResources.MyString;       Icon myIcon = MyResources.MyIcon;       Image myImage = MyResources.MyImage;       UnmanagedMemoryStream mySound = MyResources.MySound;       string myTextFile = MyResources.MyTextFile;     }     ...   } }


Internally, each property exposed by the designer-generated resources class uses its internally managed ResourceManager object in much the same fashion as you would:

using System.Drawing; ... namespace ResourcesSample {   internal class MyResources {     ...     static global::System.Globalization.CultureInfo resourceCulture;     ...     internal static Icon MyIcon {       get {         return ((Icon)           (ResourceManager.GetObject("MyIcon", resourceCulture)));       }     }   } }


Each call to ResourceManager passes information about the current UI culture, a topic that we cover shortly. For now, however, it's enough to know that this means your resources are geared for internationalization support.

Designer Resources

So far, you've seen how to manually create and manage .resx files for VS05 projects. VS05 and the Windows Forms Designer also do a variety of additional things with .resx files, something we look at now.

Default Project Resources

First and foremost, VS05 manages projectwide resources for you. You can view and manage these resources from the Resources Editor, which is embedded in the Resources tab of your project's property pages, as shown in Figure 13.18.

Figure 13.18. Editing Projectwide Resources with the Resources Editor


Because Resources.resx is really managed from your project's property pages, VS05 stores it in your project's Properties folder. And, as with the .resx files you add to your project, the custom ResXFileCodeGenerator tool is automatically applied to generate Resources.Designer.cs, a strongly typed class abstraction of the Resources.resx file:

namespace ResourcesSample.Properties {   ...   internal class Resources {     ...     internal static global::       System.Resources.ResourceManager ResourceManager {       get { ... }     }     internal static global::System.Globalization.CultureInfo Culture {             get { ... }             set { ... }     }   } }


Given the location of the Resource.resx file, the generated Resources class resides in the ResourcesSample.Properties namespace. Any resources you add to Resource.resx are, of course, accessible through a strongly typed and static property, so you can use the following code with them:

MessageBox.Show(Properties.Resources.MyString);


As a rule of thumb, resources that are shared across more than one of an assembly's types should be placed in Resources.resx.

Automatic Resource Association

If your project has a form and if you open the VS05 Solution Explorer before pressing the Show All Files button, you'll see that your form has a corresponding .resx file without your having to do anything. This keeps resources associated with certain properties of the form, such as BackgroundImage and Icon. To assist with the setting of either of these properties, the Properties window opens the Select Resource UITypeEditor, which allows you to choose an appropriate image resource from one of several locations, as shown in Figure 13.19.[6]

[6] UITypeEditors are covered in Chapter 11: Design-Time Integration: The Properties Window.

Figure 13.19. Adding Resources with the Select Resource UITypeEditor


The Select Resource UITypeEditor allows you to import and store your image resources in one of two ways: as a local resource or as a project .resx. For a form, the local resource is embedded into the .resx file that's automatically created by the Windows Forms Designer and associated with the form. If you choose this option, you can import an image resource straight into the form's .resx file, or you can use an image resource that's already located in any other .resx files in the project not associated with forms. If the image resource you want hasn't been imported and if you want to share it among more than one type, you can also import the desired image resource straight into Resources.resx before selecting it.

If you import an image resource straight into a form's .resx file, you get what is shown in Figure 13.20.

Figure 13.20. Image Resource Imported as a Local Resource into a Form's .resx


In particular, the Windows Forms Designer uses a special naming convention to distinguish all the resources that it's managing:

$this.PropertyName


If you'd like to add your own per-component typed resources to a .resx, use a leading dollar sign, or some other character that's illegal for use as a field name, and avoid the "$this" prefix (and the ">>" prefix, as you'll see shortly). For example, the following is suitable:

$mine.ResourceName


However, because the implementation of the Windows Forms Designer could change, adding your own .resx to the project is the surest way of maintaining custom resources outside the influence of the Designer.

If an image resource is added to a Windows Forms Designer-managed .resx, the Windows Forms Designer generates code into InitializeComponent to load a resource manager and populate the form's property from the Windows Forms Designer-managed .resx:

// MainForm.Designer.cs using System.ComponentModel; using System.Windows.Forms; ... partial class MainForm {   ...   void InitializeComponent() {     ...     ComponentResourceManager resources =       new ComponentResourceManager(typeof(MainForm));     ...     // MainForm     this.BackgroundImage =       ((System.Drawing.Image)     (resources.GetObject("$this.BackgroundImage")));     ...    }    ... }


The reason that a resource manager is used, rather than a strongly typed class, is that the latter hasn't been generated. By default, Windows Forms Designer-managed .resx files associated with forms are not set with the custom ResXFileCodeGenerator tool, presumably to avoid name collisions, as you saw earlier. However, if an image resource you assigned to a property comes from a non-Windows Forms Designer-managed .resx file, such as Properties Resources.resx, a strongly typed class is generated, and the Windows Forms Designer generates the following, more compact, alternative code to InitializeComponent:

// MainForm.Designer.cs using System.ComponentModel; using System.Windows.Forms; ... partial class MainForm {   ...   void InitializeComponent() {     ...     // MainForm     this.BackgroundImage = ResourcesSample.Properties.Resources.Azul;     ...   }   ... }


Embedding Native Resources

All the resources we've talked about so far are managed, and VS05 provides great support for them. Unfortunately, except for a single icon and version information, VS05 projects don't support the addition of native resources. All files marked as Embedded Resource or pulled into a .resx file are managed resources. This makes them unavailable for use by native components, such as the COM-based DirectX API. To support some interop scenarios, you need to use the /win32resource compiler command line switch, which can't be used from within VS05 builds, or you need to use a third-party tool that adds unmanaged resources after a compile.[7] At the time of this writing, Microsoft supplies no tools that provide this functionality.[8]

[7] This chapter's EmbeddingUnmanagedResourcesSample includes a tool from Peter Chiu called ntcopyres.exe that adds unmanaged resources to a managed assembly. It was obtained from http://www.codeguru.com/ cpp_mfc/rsrc-simple.html (http://tinysells.com/23) and is executed from a post-build step on the C# project.

[8] One problem with adding the native resources after a build is that if you've already signed it with a strong name key, the additional resources will screw up the hash of the file.




Windows Forms 2.0 Programming
Windows Forms 2.0 Programming (Microsoft .NET Development Series)
ISBN: 0321267966
EAN: 2147483647
Year: 2006
Pages: 216

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