Resource Basics


Imagine setting the background image of a form in your application by loading a bitmap from a file:

 
 Public Sub New()   ...   ' Load a file from the file system   Me.BackgroundImage = _     New Bitmap("C:\WINDOWS\Web\Wallpaper\Azul.jpg") End Sub 

The problem with this code is that not all installations of Windows will have Azul.jpg, and even those that do have it may not have it in the same place. Even if you shipped this picture with your application, a space-conscious user may decide to remove it, causing your application to fault. The only safe way to make sure that the picture, or any file, stays with code is to embed it and load it as a resource , a named piece of data embedded in the assembly itself.

Manifest Resources

Resources are added to an assembly at compile time. To embed a file into an assembly using VS.NET requires you that add the file to your VS.NET project. [2] To add a file to a project, right-click on your project in Solution Explorer, choose Add Existing Item, and choose the file you'd like to add. If it's not already there, it will be copied into your project's directory, but it is not yet embedded as a resource. To embed the file as a resource, right-click on the file and choose Properties, changing Build Action from Content (the default) to Embedded Resource, as shown in Figure 10.1.

[2] The .NET Framework SDK command line compilers, such as csc.exe and vbc.exe, provide options for bundling files into assemblies as resources (for csc.exe and vbc.exe, the switch is /resource). In addition, the /embedresource switch for al.exe will create a new assembly from an existing assembly and a set of files to embed as resources.

Figure 10.1. Setting a File's Build Action to Embedded Resource

When it's marked as an Embedded Resource, a file gets embedded into the assembly's set of manifest resources. The manifest of an assembly is composed of a set of metadata that's part of the assembly. Part of that metadata is the name and data associated with each embedded resource.

Naming Manifest Resources

To check that a file has been embedded properly into your project's output assembly, you can use the .NET Framework SDK tool ildasm.exe. This tool shows all embedded resources in the Manifest view of your assembly, as shown in Figure 10.2.

 
 <defaultNamespace>.<folderName>.<fileName> 
Figure 10.2. ildasm Showing an Embedded Manifest Resource

The default namespace portion of the resource name is the default namespace of the project itself, as set via Solution Explorer <projectName>(right-click) Properties Common Properties General Default Namespace, as shown in Figure 10.3.

Figure 10.3. A VS.NET Project's Default Namespace

If the file happens to be in a subfolder of your project, the folder name of the resource will include a version of that folder name, replacing the back slashes with dots. For example, Figure 10.4 shows the Azul.jpg file in the foo\bar project subfolder, and Figure 10.5 shows the resulting name of the resource in ildasm.

Figure 10.4. The Azul.jpg Resource File in the foo\bar Project Subfolder

Figure 10.5. How VS.NET Composes the Name of a Resource in a Project Subfolder

Loading Manifest Resources

To discover the resources embedded in an assembly, you can enumerate the list of manifest resources, as ildasm is doing, by using the GetManifestResourceNames method of the System.Reflection.Assembly [3] class:

[3] A type's assembly can be retrieved from the associated Type object's Assembly property. Similarly, the Assembly class itself provides several methods for retrieving assemblies of interest: GetAssembly, GetCallingAssembly, GetEntryAssembly, and GetExecutingAssembly.

 
 Imports System.Reflection ... ' Get this type's assembly Dim assem As Assembly = Me.GetType().Assembly ' Enumerate the assembly's manifest resources Dim resourceName As String For Each resourceName In assem.GetManifestResourceNames()   MessageBox.Show(resourceName) Next 

When you know the name of a manifest resource, either by enumerating them or by hard-coding the one you want, you can load it as a raw stream of bytes via the Assembly class's GetManifestResourceStream method:

 
 Imports System.IO Namespace ResourceApp   Public Sub New()       ...       ' Get this type's assembly       Dim assem As Assembly = Me.GetType().Assembly       ' Get the stream that holds the resource       ' from the "ResourcesApp.Azul.jpg" resource       ' NOTE1: Make sure not to close this stream,       '        or the Bitmap object will lose access to it       ' NOTE2: Also be very careful to match the case       '        on the resource name itself       Dim mystream As Stream = _           assem.GetManifestResourceStream("ResourcesApp.Azul.jpg")       ' Load the bitmap from the stream       Me.BackgroundImage = New Bitmap(mystream)   End Sub End Namespace 

Notice that the resource name passed to GetManifestResourceStream is the full, case-sensitive name of the resource, including the namespace and the file name. If the resource is embedded from a subfolder of the project, remember to include the "dottified" version of the folder name as well:

 
 Dim mystream As Stream = _   assem.GetManifestResourceStream("ResourcesApp.  foo.bar.  Azul.jpg") 
Manifest Resource Namespaces

If you pass a System.Type object to the GetManifestResourceStream method, it will use the type's namespace as the namespace prefix portion of the embedded resource. This is especially useful because, by default, a newly generated class in VS.NET is contained in the project's default namespace, allowing for an easy match between a type's namespace and the project's default namespace:

 
 Namespace ResourcesApp    Public Class Form1       Inherits Form     ...     ' Load the stream for resource "ResourcesApp.Azul.jpg"     Dim mystream As Stream = _       assem.GetManifestResourceStream(Me.GetType(), "Azul.jpg")     ...   End Class End Namespace 

This namespace-specification shortcut also works for some types that can directly load files that are embedded as resources. For example, the Bitmap class can load an image from a resource, eliminating the need to get the manifest stream manually:

 
 Namespace ResourcesApp   Public Class Form1       Inherits Form       ...       Public Sub New()           ...           ' Get this form's assembly           Dim assem As Assembly = Me.GetType().Assembly           ' Load image from "ResourcesApp.Azul.jpg"           Me.BackgroundIMage = _             New Bitmap(Me.GetType(), "Azul.jpg")       End Sub End Namespace 

To help you keep track of where all the parts of a manifest resource come from and how they're specified, Figure 10.6 shows a summary.

Figure 10.6. A Summary of Manifest Resource Naming and Name Resolution

Although manifest resources are useful, their degree of integration with VS.NET and the type system is limited. However, manifest resources serve as the needed foundation for typed resources, which address both of these issues.

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 type ”for example, JPEG, PNG, GIF, and so on. It's up to you to properly map the type of each resource to the type of the object needed to load it.

However, you can tag your resources with a type if you're willing to take an extra step. .NET supports an extended set of metadata for a resource that includes Multipurpose Internet Mail Extensions (MIME) type information in two formats: one text and one binary. Both formats have readers so that you can pull out the properly typed resources at run time.

Text-Based Typed Resources

The text-based format is a .NET-specific XML format called ResX (.resx files). In spite of its text basis, this format is not meant to be read by humans (as few XML formats are). However, VS.NET provides a rudimentary editor for .resx files. To add a new .resx file to your VS.NET project, choose Add New Item from the Project menu and pick the Assembly Resource File template, as shown in Figure 10.7.

Figure 10.7. Adding a .resx File to a Project

As of this writing, even an empty .resx file is 42 lines of 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 MIME type. Figure 10.8 shows a .resx file with two entries: a string named MyString and an image named MyImage.

Figure 10.8. A Simple .resx File in the Data View of the Designer

The corresponding XML data for the entries is shown here:

 
 <?xml version="1.0" encoding="utf-8" ?> <root>  ...   <data name="MyString">   <value>a string value</value>   <comment>a comment</comment>   </data>   <data name="MyImage"   type="System.Drawing.Bitmap, ..."   mimetype="application/x-microsoft.net.object.bytearray.base64">   <value>   ... base64-encoded image data ...   </value>   </data>  </root> 

Of the two entries, only the string entry can actually be edited in the Data view of the .resx editor. The image entry was added by hand in the XML view (the Base64-encoded data being particularly challenging). For this reason, the direct use of .resx files in VS.NET is useful only for string resources (although indirect usage makes .resx files very useful for any kind of data, as you'll see later in this chapter).

After you've got a .resx file, you can load it and enumerate it using the ResXResourceReader class from the System.Resources namespace:

 
 Imports System.Collections Imports System.Resources ... Dim reader As ResXResourceReader = _       New ResXResourceReader("C:\Resource1.resx") Dim entry As DictionaryEntry For Each entry in reader   Dim s As String = String.Format("{0} ({1})= '{2}'", _       entry.Key, entry.Value.GetType(), entry.Value)   MessageBox.Show(s) Next reader.Dispose() 

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:

 
 Public Sub New()   ...   Dim reader As ResXResourceReader = _       New ResXResourceReader("Resource1.resx")   Dim entry As DictionaryEntry   For Each entry In reader       If entry.Key.ToString() = "MyString" Then           ' Set form caption from string resource           Me.Text = entry.Value.ToString()       End If   Next End Sub 

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.

Binary Typed Resources

To add a .resx file to your project as an embedded resource, you use the Add New Item dialog. 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 VS.NET 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 ResourcesApp and a .resx file named Resources1.resx, the container of nested resources is named ResourcesApp.Resources1.resources, as shown in ildasm in Figure 10.9.

Figure 10.9. An Embedded .resources File

The .resources extension comes from the resgen .exe tool, which VS.NET 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 Resource1.resources in this case):

 
 C:\> resgen.exe Resource1.resx 

After you've compiled a .resx file into a .resources file in the file system, you can load it and enumerate it using ResourceReader from the System.Resources namespace. Other than 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:

 
 Dim reader As ResourceReader = New ResourceReader("C:\Resource1.resources") Dim entry As DictionaryEntry For Each entry In reader   Dim s As String = String.Format("{0} ({1})= '{2}'", _       entry.Key, entry.Value.GetType(), entry.Value)   MessageBox.Show(s) Next reader.Dispose() 

You can read a .resources file from the file system, but because VS.NET 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:

 
 ' Load embedded .resources file Dim mystream As Stream = assem.GetManifestResourceStream( _       Me.GetType(), "Resource1.resources") ' Find resource in .resources file Dim reader As ResourceReader = New ResourceReader(mystream) Dim entry As DictionaryEntry For Each entry In reader   If entry.Key.ToString() = "MyString" Then       ' Set form caption from string resources       Me.Text = entry.Value.ToString()   End If Next mystream.Dispose() 

This two-step process ”loading the .resx file or the .resources nested resource and then enumerating all values looking for the one you want ”is an inconvenience, so .NET provides the ResourceManager class, which provides random access to resources.

Resource Manager

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

 
 Public Sub New()   ...   ' Get this type's assembly   Dim assem As Assembly = Me.GetType().Assembly   ' Load the .resources file into the ResourceManager   ' Assumes a file name "Resource1.resx" as part of the project   Dim resman As ResourceManager = _       New ResourceManager("ResourcesApp.Resource1", assem)   ... End Sub 

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

As a further convenience, if you name a .resx file with the name of a type, such as MyCustomType.resx, the name of the .resources file and the assembly can be determined from the type:

 
 Namespace ResourcesApp   Class MyCustomType       Public Sub New()            ' Load "ResourcesApp.MyCustomType.resources"           ' from the MyCustomType class's assembly           Dim resman As ResourceManager = _ New ResourceManager(Me.GetType())           ...       End Sub   End Class End Namespace 

Using a type's namespace and name to name a .resx file is a useful way to keep per-type resources. This is how WinForms Designer associates resources with custom forms and other types with design surfaces, as you'll see soon.

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. But if you're using the .resx file for string resources, you'll want to use the GetString method instead. This method performs the cast to the System.String type for you:

 
 Namespace ResourcesApp   Class MyCustomType       Public Sub New()           ' Load "ResourcesApp.MyCustomType.resources"           Dim resman As ResourceManager = _             New ResourceManager(Me.GetType())           ' Access the MyString string from the ResourceManager           ' (both of these techniques are equivalent for strings)           Dim s1 As String = CStr(resman.GetObject("MyString"))           Dim s2 As String = resman.GetString("MyString")       End Sub   End Class End Namespace 

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

Figure 10.10. Logical View of the Way ResourceManager Uses ResourceReader

Again, because the naming scheme for embedded resources is somewhat obscured, Figure 10.11 shows a summary of how VS.NET settings influence the names used with the ResourceManager.

Figure 10.11. Resource Naming and ResourceManager

Using a resource manager directly, especially one associated with a specific type, is a useful thing to do, but somewhat labor- intensive . WinForms Designer often makes such manual coding unnecessary.

Designer Resources

Although the resource manager code is lots friendlier to write than resource reader code, the lack of a decent editor for .resx files makes it difficult to use them for anything but string resources. Not only do you have to write the code manually to bring the data in at run time, but also you don't get to see what your resources will look like at design time. That's a problem for resources such as a form's background image.

In addition to all its other duties , WinForms Designer provides support for associating resource data with objects hosted on your custom forms and other component types. [4] If you open the VS.NET Solution Explorer and press the Show All Files button, you'll see that each component type, whether it's a form, a control, or a simple component, has a corresponding .resx file. This keeps resources associated with properties of the component, as set in the Property Browser. For example, if you set the BackgroundImage property of a form, not only will the form show the background image in the Designer, but also the form's .resx file will contain an entry for that image. Similarly, if you set the Image property of a PictureBox control on the same form, the .resx file will have grown to include that resource as well. Figure 10.12 shows both of these entries.

[4] What makes a component type is discussed in detail in Chapter 9: Design-Time Integration.

Figure 10.12. A Component's .resx File

Each component's .resx file is compiled and embedded as a .resources file, just as if you'd added your own .resx file to your project, thereby making the resources available to the component at run time. In addition to the entries in the component's .resx file, the Designer adds the code to InitializeComponent to load a resource manager for the component. The Designer-added code populates the component's properties using the objects pulled from the resources:

 
 Namespace ResourcesApp   Class Form1       Inherits Form       ...       Sub InitializeComponent()           Dim resources As ResourceManager = _             New ResourceManager(GetType(Form1))           ...           Me.pictureBox1.Image = _               CType(resources.GetObject( _               "pictureBox1.Image"), _               Bitmap)           ...           Me.BackgroundImage = _Type(resources.GetObject("$this.Back               groundImage") Bitmap)           ...       End Sub End Namespace 

Notice that the ResourceManager object is constructed using the type of the component, which is used to construct the .resources resource name for the component. Notice also the naming convention used by the Designer to name resources. For properties on fields of the component, the format of the name is as follows :

 
 <fieldName>.<propertyName> 

For properties on the component itself, the format of the name looks like this:

 
 $Me.<propertyName> 

If you'd like to add custom string properties for use in an existing Designer-managed .resx file, you can, but make sure to stay away from the format of the Designer-generated names:

 
 $mine.<resourceName> 

By using a leading dollar sign or another character that's illegal for use as a field name, and by avoiding the "$Me" prefix (and the ">>" prefix, which is used by localized resources), you're likely to stay out of the way of the current Designer implementation while still allowing yourself per-component typed resources. However, because the implementation of the Designer could change, adding your own .resx file to the project is the surest way of maintaining custom resources outside the influence of the Designer.

Designer Resource Issues

As of this writing there are two main problems with resources as supported in VS.NET. The first is that when you set a property from a file, the data from the file is copied directly into the .resx file, so changes to the original file are not automatically replicated in the resource. This is exacerbated by the fact that after data from a file has been added to a .resx file, there is no way in VS.NET to edit it. Instead, to get updated data into a .resx file, you must use VS.NET to remove the entry from VS.NET, after which you must add it again manually.

The second problem is that, except for a single icon, C# projects don't support the addition of unmanaged resources. All files marked as Embedded Resource or pulled into a .resx file are managed resources. This makes them unavailable for use by unmanaged components , such as those used in the COM-based DirectX API. To support some interoperability scenarios, you'll need to use the /win32resource compiler command line switch, which can't be used from within VS.NET builds, or you must use a third-party tool that will add unmanaged resources after a build. [5] Microsoft provides no tools, as of this writing, that provide this functionality.

[5] This chapter's source code 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.



Windows Forms Programming in Visual Basic .NET
Windows Forms Programming in Visual Basic .NET
ISBN: 0321125193
EAN: 2147483647
Year: 2003
Pages: 139

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