Resources


When you finally get to the point of running your software, usually there are other things needed for it to run besides the executable. For example, you might find that you need images, icons, cursors or, if you are going to globalize the application, a culture's set of strings. You could fill your application directory full of a bunch of files containing these "resources." But if you did, you would run the risk of forgetting something when you deployed your application. I think a better solution is to group common resources into .resources files. Then, optionally, embed the .resources files into the assembly that uses the contents of the .resources files. My thought is that with fewer files floating around, fewer things can get lost.

You have three ways to work with grouped resources in the .NET Framework:

  • You can place the grouped resources in .resources files and then work with them as separate entities. This allows you to switch and swap the .resources files as needed. It also allows you to work with the resources within the .resources files in a dynamic fashion.

  • You can embed the resources directly into the assembly that uses them. This method has the least flexibility, but you can be secure in the knowledge that everything you need to run the assembly is available.

  • You can combine the two previous methods and create what the .NET Framework calls satellite assemblies. These are assemblies containing only resources, but at the same time, they directly link to the assembly that uses the resources within them. You will see this use of resources when you look at globalization and localization later in this chapter.

Creating Resources

The .NET Framework provides you with two text formats for creating .resources files: a text file made up of name/value pairs and an XML-formatted file called a .resx file. Of the two, the name/value-formatted file is much easier to use, but it has the drawback of supporting only string resources. On the other hand, .resx files support almost any kind of resource, but unfortunately they are extremely hard to hand code. Most likely, because .resx files are so complex, you will choose a third way, which is to write a simple program to add nontext-formatted resources to a .resources file. I show you how to write the program later in this section.

Because .resx files are so complex, why are they included? They are what Visual Studio .NET uses to handle resources. In fact, you will use them quite extensively when you look at globalization and localization later in this chapter, but you will probably not even be aware that you are.

Building Text Name/Value Pair Resource Files

The simplest type of resource that you can create is the string table. You will probably want to create this type of resource using name/value pair files, as the format of the name/value pair file maps quite nicely to a string table. Basically, the name/value pair file is made up of many lines of name and value pairs separated by equal signs (=). Here is an example:

 Name = Stephen Fraser Email Address = srgfraser@frasertraining.com Phone Number = (502) 555-1234 Favorite Equation = E=mc2 

As you can see, spaces are allowed for both the name and the value. Also, the equal sign can be used in the value (but not the name), as the first equal sign is used to delimit the name and the value.

Caution

Don't try to line up the equal signs, because the spaces will become part of the name. As you'll see later in the chapter, doing this will make it harder to code the resource accessing method.

ResGen

The text file you created previously is only an intermediate file. You might think of it as a source file just like a .cpp or .h file. You need to convert it to a .resources file so that your program will be able to process it as a resource. (By the way, you could process the file as a standard string file, but then you would lose many of the resources features provided by the .NET Framework.) To convert your text file, use the .NET Framework's ResGen.exe utility. There is not much to running the utility:

 > ResGen filename.txt 

When you run the preceding code, assuming that the text file consists of valid name/value pairs, you get an output file of filename.resources in the directory where you ran the utility. You can work with these files as separate entities or you can embed them into your assembly. You will see how to do that later in this chapter.

One more thing. If you are a glutton for punishment and write resource files using .resx files, then you would use the ResGen utility to convert them into .resources files as well.

ResourceWriter

As I stated previously, adding nontext resources is not possible using name/value pair files, and the .resx file is a bear to work with, so what are you to do if you simply need to create nontext resources (e.g., an image table)?

You can use the System::Resources::ResourceWriter class, because this class has the capability to place almost any type of data within a .resources file, so long as the total combined size of the file does not exceed 2GB. In fact, this class is what ResGen.exe uses to generate its .resources file. Why they didn't make ResGen.exe more robust and allow other types of data types escapes me.

Using the ResourceWriter class requires you to perform only three steps:

  1. Open up a .resources file using the ResourceWriter class's constructor.

  2. Add resources to the .resources file using the AddResources() method.

  3. Close the .resources file using the Close() method.

Listing 17-10 presents all the code you need to add an image to a .resources file from a .jpg file.

Listing 17-10: Adding an Image to a .resources File

start example
 #using <System.Drawing.dll>  // Add the reference as it's not a default using namespace System; using namespace System::Resources; using namespace System::Drawing; Int32 main() {     ResourceWriter *rwriter = new ResourceWriter(S"filename.resources");     rwriter->AddResource(S"ImageName", Images::FromFile(S"Imagefile.jpg"));     rwriter->Close();     return 0; } 
end example

Embedding Resources

One way to make sure that everything that you need to execute an assembly is available is to put everything in the assembly itself. This way, executing an assembly is as easy as double-clicking the assembly's .exe file.

To embed resources from the command line, you use the assembly generation tool, al.exe, passing it the /embed option along with the name of the .resources file.

If you are using Visual Studio .NET, embedding resources is also fairly straightforward. In fact, if you are using .resx files as the source of your resources, you have to do nothing, because Visual Studio .NET will automatically handle everything for you. To embed resources using name/value pair files and prebuilt .resources files is not much more difficult.

I think the easiest way to explain how to embed resources is to actually walk through the process. In the following example, you will embed Animal.resx, Color.exe (name/value pair file), and Fruit.resources into an assembly called EmbeddingResources.exe.

The first step, as with any other .NET application project, is to use the project template wizard to build the basic structure of your project. In this case, you will build a standard Console Application (.NET) project and name it EmbeddingResources. To complete this project, perform the following steps:

  1. Add a new item of type Assembly Resource File (.resx) and name it Animal. Then add some name/value pairs, as shown in Figure 17-10.

    click to expand
    Figure 17-10: The Animal resource file

  2. Add a new item of type Text File (.txt) and name it Color. Then add the following name/value pairs:

     Color1 = Blue Color2 = Red Color3 = Yellow Color4 = Green 

  3. Add an existing item called Fruit.resources. You will need to create this file at the command line using the ResGen tool on the name/value pair file containing the following entries:

     Fruit1 = Apple Fruit2 = Orange Fruit3 = Grape Fruit4 = Lemon 

Now that you have all the resources ready, go ahead and embed them into the assembly. As I said previously, you don't have to do anything to embed a .resx file. Personally, though, I don't like the name that Visual Studio .NET gives the resource when it's embedded, so let's change it:

  1. Right-click Animal.resx in Solution Explorer then select the Properties menu item.

  2. Select All Configurations from the Configuration drop-down list.

  3. Change the Resource File Name entry in Managed Resources General to $(IntDir)/$(RootNamespace).Animal.resources (see Figure 17-11). This will give the resource the name EmbeddingResources.Animal. I think this is better than the default EmbeddingResources.ResourceFiles.

    click to expand
    Figure 17-11: Changing the generated resource name

  4. Click OK.

To embed the name/value pairs file Color.txt requires just one more step than Animal.resx: You have to change the build tool from Custom Build Tool to Managed Resource Compiler. You make this change also in the file's properties, but this time change the Tool entry in the Configuration Properties General page (see Figure 17-12). You might want to also go ahead and change the name of the generated resource file to $(IntDir)/$(RootNamespace).Color.resources.

click to expand
Figure 17-12: Changing the tool to Managed Resource Compiler

To embed an already-created .resources file requires that you add it as an input to the assembly linker. (By the way, you don't have to add it to Solution Explorer to get this to work—it just has to be in the project directory. I put it there so I remember that, in fact, I am embedding it.) The steps this time are a little different:

  1. Right-click the EmbeddingResources project in Solution Explorer and select the Properties menu item. This will bring up a dialog box similar to Figure 17-13.

    click to expand
    Figure 17-13: Adding embedded resources

  2. In the Linker folder select Input.

  3. Enter Fruit.resources in the Embed Managed Resource File text box.

  4. Click OK.

When you compile the project, you will have three resources embedded into the application assembly. If you want proof, look in the assemblies manifest and you will find the following three entries:

 .mresource public fruit.resources { } .mresource public EmbeddingResources.Color.resources { } .mresource public EmbeddingResources.Animal.resources { } 

Accessing Resources

You've looked at creating resources and then embedding resources. Both are kind of neat but by themselves are quite useless unless you have some way of accessing these resources within your application. Obviously, the .NET Framework provides a class to get access to the resources. In fact, depending on where the resource is stored, it may provide two ways: the ResourceReader class and the ResourceManager class.

ResourceReader Class

The ResourceReader class is the counterpart of the ResourceWriter class. It enables you to iterate through a .resources file, treating it as though it were a simple file. Just like the ResourceWriter class, the ResourceReader class is very easy to implement:

  1. Open the .resources file using the ResourceReader constructor.

  2. Get IDictionaryEnumerator from the ResourceReader class's GetEnumerator() method.

  3. Use the MoveNext() method to process all the entries in the .resources file.

  4. Close the ResourceReader class with the Close() method.

Here is all the code you need to implement ResourceReader:

 ResourceReader *rreader = new ResourceReader(S"filename.resources"); IDictionaryEnumerator *denum = rreader->GetEnumerator(); while (denum->MoveNext()) {     Console::WriteLine(S"{0} = {1}", denum->Key, denum->Value ); } rreader->Close(); 

Caution

The order in which the key/value pairs are retrieved from the assembly may not match the order in which they were written.

ResourceManager Class

Although the ResourceReader class is restricted to .resources files, the ResourceManager class gives you access to either .resources files or embedded resources. Another feature of the ResourceManager class that you will cover later in this chapter is that it can access the resources in a culture-specific manner.

To create an instance of a ResourceManager class, you need to pass the name of the resource and the assembly that the resource is embedded into:

 ResourceManager rmgr = new ResourceManager(S"resourceName", assembly); 

Along with embedded resources, it is also possible to open an instance of the ResourceManager from a .resources file using the CreateFileBasedResourceManager() static method. This method takes three parameters: the name of the .resources file without the .resources suffix, the path to the .resources file, and the culture to mask output with. I discuss cultures later in this chapter. The result of this method is a pointer to a ResourceManager:

 ResourceManager rmgr =     ResourceManager::CreateFileBasedResourceManager(S"resourceFilename", "", 0); 

Once you have the instance of the ResourceManager, all you have to do is pass the name of the resource item you want either the GetString() or GetObject() method to return the value of:

 String *Value = rmgr->GetString(S"Name"); Object *Value = rmgr->GetObject(S"Name"); 

Listing 17-11 expands on the previous section's project, EmbeddingResources. This example displays the Fruit.resources file using both the ResourceReader and ResourceManager and then continues on to display the embedded version of the Fruit resource using ResourceManager again.

Listing 17-11: EmbeddedResources Display Function

start example
 using namespace System; using namespace System::Collections; using namespace System::Reflection; using namespace System::Resources; Int32 main() {     Console::WriteLine(S"*** ResourceReader ***");     ResourceReader *rreader = new ResourceReader(S"Fruit.resources");     IDictionaryEnumerator *denum = rreader->GetEnumerator();     while (denum->MoveNext())     {         Console::WriteLine(S"{0} = {1}", denum->Key, denum->Value);     }     rreader->Close();     ResourceManager *rmgr;     Console::WriteLine(S"\n*** ResourceManager From File ***");     rmgr = ResourceManager::CreateFileBasedResourceManager(S"Fruit", "", 0);     Console::WriteLine(rmgr->GetString(S"Fruit1"));     Console::WriteLine(rmgr->GetString(S"Fruit2"));     Console::WriteLine(rmgr->GetObject(S"Fruit3"));     Console::WriteLine(rmgr->GetObject(S"Fruit4"));     Console::WriteLine(S"\n*** ResourceManager From Assembly ***");     Assembly *assembly = Assembly::GetExecutingAssembly();     rmgr = new ResourceManager(S"Fruit", assembly);     Console::WriteLine(rmgr->GetString(S"Fruit1"));     Console::WriteLine(rmgr->GetString(S"Fruit2"));     Console::WriteLine(rmgr->GetObject(S"Fruit3"));     Console::WriteLine(rmgr->GetObject(S"Fruit4"));     return 0; } 
end example

Notice that you can use either GetString() or GetObject() to extract a String resource item. If, on the other hand, you were extracting an Image type resource item, you would need to use the GetObject() method and then typecast it back to an Image:

 Image *img = dynamic_cast<Image*>(rmgr->GetObject(S"ImageName")); 

Figure 17-14 shows EmbeddedResources.exe in action.

click to expand
Figure 17-14: The result of executing the EmbeddedResources program




Managed C++ and. NET Development
Managed C++ and .NET Development: Visual Studio .NET 2003 Edition
ISBN: 1590590333
EAN: 2147483647
Year: 2005
Pages: 169

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