Get a site

Icons, Cursors, Strings, and Custom Resources

One of the benefits of using resources is that many components of a program can be bound into the program's .EXE file. Without the concept of resources, a binary file such as an icon image would probably have to reside in a separate file that the .EXE would read into memory to use. Or the icon would have to be defined in the program as an array of bytes (which might make it tough to visualize the actual icon image). As a resource, the icon is stored in a separate editable file on the developer's computer but is bound into the .EXE file during the build process.

Adding an Icon to a Program

Adding resources to a program involves using some additional features of Visual C++ Developer Studio. In the case of icons, you use the Image Editor (also called the Graphics Editor) to draw a picture of your icon. This image is stored in an icon file with an extension .ICO. Developer Studio also generates a resource script (that is, a file with the extension .RC, sometimes also called a resource definition file) that lists all the program's resources and a header file (RESOURCE.H) that lets your program reference the resources.

So that you can see how these new files fit together, let's begin by creating a new project, called ICONDEMO. As usual, in Developer Studio you pick New from the File menu, select the Projects tab, and choose Win32 Application. In the Project Name field, type ICONDEMO and click OK. At this point, Developer Studio creates five files that it uses to maintain the workspace and the project. These include the text files ICONDEMO.DSW, ICONDEMO.DSP, and ICONDEMO.MAK (assuming you've selected "Export makefile when saving project file" from the Build tab of the Options dialog box displayed when you select Options from the Tools menu).

Now let's create a C source code file as usual. Select New from the File menu, select the Files tab, and click C++ Source File. In the File Name field, type ICONDEMO.C and click OK. At this point, Developer Studio has created an empty ICONDEMO.C file. Type in the program shown in Figure 10-1, or pick the Insert menu and then the File As Text option to copy in the source code from this book's companion CD-ROM.

Figure 10-1. The ICONDEMO program.

ICONDEMO.C

/*------------------------------------------
   ICONDEMO.C -- Icon Demonstration Program
                 (c) Charles Petzold, 1998
  ------------------------------------------*/

#include <windows.h>
#include "resource.h"

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     TCHAR    szAppName[] = TEXT ("IconDemo") ;
     HWND     hwnd ;
     MSG      msg ;
     WNDCLASS wndclass ;

     wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
     wndclass.lpfnWndProc   = WndProc ;
     wndclass.cbClsExtra    = 0 ;
     wndclass.cbWndExtra    = 0 ;
     wndclass.hInstance     = hInstance ;
     wndclass.hIcon         = LoadIcon (hInstance, MAKEINTRESOURCE (IDI_ICON)) ;
     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
     wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
     wndclass.lpszMenuName  = NULL ;
     wndclass.lpszClassName = szAppName ;
     if (!RegisterClass (&wndclass))
     {
          MessageBox (NULL, TEXT ("This program requires Windows NT!"),
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }
     
     hwnd = CreateWindow (szAppName, TEXT ("Icon Demo"),
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;
     
     ShowWindow (hwnd, iCmdShow) ;
     UpdateWindow (hwnd) ;
     
     while (GetMessage (&msg, NULL, 0, 0))
     {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
     }
     return msg.wParam ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     static HICON hIcon ;
     static int   cxIcon, cyIcon, cxClient, cyClient ;
     HDC          hdc ;
     HINSTANCE    hInstance ;
     PAINTSTRUCT  ps ;
     int          x, y ;
     
     switch (message)
     {
     case WM_CREATE :
          hInstance = ((LPCREATESTRUCT) lParam)->hInstance ;
          hIcon = LoadIcon (hInstance, MAKEINTRESOURCE (IDI_ICON)) ;
          cxIcon = GetSystemMetrics (SM_CXICON) ;
          cyIcon = GetSystemMetrics (SM_CYICON) ;
          return 0 ;
          
     case WM_SIZE :
          cxClient = LOWORD (lParam) ;
          cyClient = HIWORD (lParam) ;
          return 0 ;

     case WM_PAINT :
          hdc = BeginPaint (hwnd, &ps) ;
          
          for (y = 0 ; y < cyClient ; y += cyIcon)
               for (x = 0 ; x < cxClient ; x += cxIcon)
                    DrawIcon (hdc, x, y, hIcon) ;
               
               EndPaint (hwnd, &ps) ;
               return 0 ;
               
     case WM_DESTROY :
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}

If you try compiling this program, you'll get an error because the RESOURCE.H file referenced at the top of the program does not yet exist. However, you do not create this RESOURCE.H file directly. Instead, you let Developer Studio create it for you.

You do this by adding a resource script to the project. Select New from the File menu, select the Files tab, click Resource Script, and type ICONDEMO in the File Name field. Click OK. At this time, Developer Studio creates two new text files: ICONDEMO.RC, the resource script, and RESOURCE.H, a header file that will allow the C source code file and the resource script to refer to the same defined identifiers. Don't try to edit these two files directly; let Developer Studio maintain them for you. If you want to take a look at the resource script and RESOURCE.H without any interference from Developer Studio, try loading them into Notepad. Don't change them there unless you really know what you're doing. Also, keep in mind that Developer Studio will save new versions of these files only when you explicitly direct it to or when it rebuilds the project.

The resource script is a text file. It contains text representations of those resources that can be expressed in text, such as menus and dialog boxes. The resource script also contains references to binary files that contain nontext resources, such as icons and customized mouse cursors.

Now that RESOURCE.H exists, you can try compiling ICONDEMO again. Now you get an error message indicating that IDI_ICON is not defined. This identifier occurs first in the statement

wndclass.hIcon  = LoadIcon (hInstance, MAKEINTRESOURCE (IDI_ICON)) ;

That statement in ICONDEMO has replaced this statement found in previous programs in this book:

wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;

It makes sense that we're changing this statement because we've been using a standard icon for our applications and our goal here is to use a customized icon.

So let's create an icon. In the File View window of Developer Studio, you'll see two files listed now—ICONDEMO.C and ICONDEMO.RC. When you click ICONDEMO.C, you can edit the source code. When you click ICONDEMO.RC, you can add resources to that file or edit an existing resource. To add an icon, select Resource from the Insert menu. Click the resource you want to add, which is Icon, and then click the New button.

You are now presented with a blank 32-pixel-by-32-pixel icon that is ready to be colored. You'll see a floating toolbar with a collection of painting tools and a bunch of available colors. Be aware that the color toolbar includes two options that are not exactly colors. These are sometimes referred to as "screen" and "inverse screen." When a pixel is colored with "screen," it is actually transparent. Whatever surface the icon is displayed against will show through. This allows you to create icons that appear to be nonrectangular.

Before you get too far, double-click the area surrounding the icon. You'll get an Icon Properties dialog box that allows you to change the ID of the icon and its filename. Developer Studio will probably have set the ID to IDI_ICON1. Change that to IDI_ICON since that's how ICONDEMO refers to the icon. (The IDI prefix stands for "id for an icon.") Also, change the filename to ICONDEMO.ICO.

For now, I want you to select a distinctive color (such as red) and draw a large B (standing for "big") on this icon. It doesn't have to be as neat as Figure 10-2.

Figure 10-2. The standard (32×32) ICONDEMO file as displayed in Developer Studio.

The program should now compile and run fine. Developer Studio has put a line in the ICONDEMO.RC resource script that equates the icon file (ICONDEMO.ICO) with an identifier (IDI_ICON). The RESOURCE.H header file contains a definition of the IDI_ICON identifier. (We'll take a look at this in more detail shortly.)

Developer Studio compiles resources by using the resource compiler RC.EXE. The text resource script is converted into a binary form, which is a file with the extension .RES. This compiled resource file is then specified along with .OBJ and .LIB files in the LINK step. This is how the resources are added to the final .EXE file.

When you run ICONDEMO, the program's icon is displayed in the upper left corner of the title bar and in the taskbar. If you add the program to the Start Menu, or if you add a shortcut on your desktop, you'll see the icon there as well.

ICONDEMO also displays the icon in its client area, repeated horizontally and vertically. Using the statement

hIcon = LoadIcon (hInstance, MAKEINTRESOURCE (IDI_ICON)) ;

the program obtains a handle to the icon. Using the statements

cxIcon = GetSystemMetrics (SM_CXICON) ;
cyIcon = GetSystemMetrics (SM_CYICON) ;

it obtains the size of the icon. The program can then display the icon with multiple calls to

DrawIcon (hdc, x, y, hIcon) ;

where x and y are the coordinates of where the upper left corner of the displayed icon is positioned.

With most video display adapters in current use, GetSystemMetrics with the SM_ CXICON and SM_CYICON indices will report that the size of an icon is 32 by 32 pixels. This is the size of the icon that we created in the Developer Studio. It is also the size of the icon as it appears on the desktop and the size of the icon displayed in the client area of the ICONDEMO program. It is not, however, the size of the icon displayed in the program's title bar or in the taskbar. That smaller icon size can be obtained from GetSystemMetrics with the SM_CXSMSIZE and SM_CYSMSIZE indices. (The first "SM" means "system metrics"; the embedded "SM" means "small.") For most display adapters in current use, the small icon size is 16 by 16 pixels.

This can be a problem. When Windows reduces a 32-by-32 icon to a 16-by-16 size, it must eliminate every other row and column of pixels. For some complex icon images, this might cause distortions. For this reason, you should probably create special 16-by-16 icons for images where shrinkage is undesirable. Above the icon image in Developer Studio is a combo box labeled Device. To the right of that is a button. Pushing the button invokes a New Icon Image dialog box. Select Small (16x16). Now you can draw another icon. For now, use an S (for "small") as shown in Figure 10-3.

Figure 10-3. The small (16×16) ICONDEMO file as displayed in Developer Studio.

There's nothing else you need to do in the program. The second icon image is stored in the same ICONDEMO.ICO file and referenced with the same INI_ICON identifier. Windows will now automatically use the smaller icon when it's more appropriate, such as in the title bar and the taskbar. Windows uses the large image when displaying a shortcut on the desktop and when the program calls DrawIcon to adorn its client area.

Now that we've mastered the practical stuff, let's take a closer look at what's going on under the hood.

Getting a Handle on Icons

If you take a look ICONDEMO.RC and RESOURCE.H, you'll see a bunch of stuff that Developer Studio generates to help it maintain the files. However, when the resource script is compiled, only a few lines are important. These critical excerpts from the ICONDEMO.RC and RESOURCE.H files are shown in Figure 10-4.

ICONDEMO.RC (excerpts)
//Microsoft Developer Studio generated resource script.

#include "resource.h"
#include "afxres.h"

/////////////////////////////////////////////////////////////////////////////
// Icon

IDI_ICON                ICON    DISCARDABLE     "icondemo.ico"

RESOURCE.H (excerpts) 
// Microsoft Developer Studio generated include file.
// Used by IconDemo.rc

#define IDI_ICON                        101

Figure 10-4. Excerpts from the ICONDEMO.RC and RESOURCE.H files.

Figure 10-4 shows ICONDEMO.RC and RESOURCE.H files that look much like they would look if you were creating them manually in a normal text editor, just as Windows programmers did in the old days way back in the 1980s. The only real difference is the presence of AFXRES.H, which is a header file that includes many common identifiers used by Developer Studio when creating machine-generated MFC projects. I will not make use of AFXRES.H in this book.

This line in ICONDEMO.RC,

IDI_ICON ICON DISCARDABLE "icondemo.ico"

is a resource script ICON statement. The icon has a numeric identifier of IDI_ICON, which equals 101. The DISCARDABLE keyword that Developer Studio adds indicates that Windows can discard the icon from memory, if necessary, to obtain some additional space. The icon can always be reloaded later by Windows without any special action by the program. The DISCARDABLE attribute is the default and doesn't need to be specified. Developer Studio puts the filename in quotes just in case the name or a directory path contains spaces.

When the resource compiler stores the compiled resource in ICONDEMO.RES and the linker adds the resource to ICONDEMO.EXE, the resource is identified by just a resource type, which is RT_ICON, and an identifier, which is IDI_ICON or 101. A program can obtain a handle to this icon by calling the LoadIcon function:

hIcon = LoadIcon (hInstance, MAKEINTRESOURCE (IDI_ICON)) ;

Notice that ICONDEMO calls this function in two places—once when defining the window class and again in the window procedure to obtain a handle to the icon for drawing. LoadIcon returns a value of type HICON, a handle to an icon.

The first argument to LoadIcon is the instance handle that indicates what file the resource comes from. Using hInstance means it comes from the program's own .EXE file. The second argument to LoadIcon is actually defined as a pointer to a character string. As we'll see shortly, you can identify resources by character strings instead of numeric identifiers. The macro MAKEINTRESOURCE ("make an integer into a resource string") makes a pointer out of the number like so:

#define MAKEINTRESOURCE(i)  (LPTSTR) ((DWORD) ((WORD) (i)))

The LoadIcon function knows that if the high word of the second argument is 0, then the low word is a numeric identifier for the icon. The icon identifier must be a 16-bit value.

Sample programs presented earlier in this book use predefined icons:

LoadIcon (NULL, IDI_APPLICATION) ;

Windows knows that this is a predefined icon because the hInstance parameter is set to NULL. And IDI_APPLICATION happens also to be defined in WINUSER.H in terms of MAKEINTRESOURCE:

#define IDI_APPLICATION MAKEINTRESOURCE(32512)

The second argument to LoadIcon raises an intriguing question: can the icon identifier be a character string? Yes, and here's how: In the Developer Studio list of files for the ICONDEMO project, select IDONDEMO.RC. You'll see a tree structure beginning at the top with IconDemo Resources, then the resource type Icon, and then the icon IDI_ICON. If you right-click the icon identifier and select Properties from the menu, you can change the ID. In fact, you can change it to a string by enclosing a name in quotation marks. This is the method I prefer for specifying the names of resources and that I will use in general for the rest of this book.

I prefer using text names for icons (and some other resources) because the name can be the name of the program. For example, suppose the program is named MYPROG. If you use the Icon Properties dialog box to specify the ID of the icon as "MyProg" (with quotation marks), the resource script would contain the following statement:

MYPROG ICON DISCARDABLE myprog.ico

However, there will be no #define statement in RESOURCE.H that will indicate MYPROG as a numeric identifier. The resource script will instead assume that MYPROG is a string identifier.

In your C program, you use the LoadIcon function to obtain a handle to the icon. Recall that you already probably have a string variable indicating the name of the program:

static TCHAR szAppName [] = TEXT ("MyProg") ;

This means that the program can load the icon using the statement

hIcon = LoadIcon (hInstance, szAppName) ;

which looks a whole lot cleaner than the MAKEINTRESOURCE macro.

But if you really prefer numbers to names, you can use them instead of identifiers or strings. In the Icon Properties dialog, enter a number in the ID field. The resource script will have an ICON statement that looks something like this:

125 ICON DISCARDABLE myprog.ico

You can reference the icon using one of two methods. The obvious one is this:

hIcon = LoadIcon (hInstance, MAKEINTRESOURCE (125)) ;

The obscure method is this:

hIcon = LoadIcon (hInstance, TEXT ("#125")) ;

Windows recognizes the initial # character as prefacing a number in ASCII form.

Using Icons in Your Program

Although Windows uses icons in several ways to denote a program, many Windows programs specify an icon only when defining the window class with the WNDCLASS structure and RegisterClass. As we've seen, this works well, particularly when the icon file contains both standard and small image sizes. Windows will choose the best image size in the icon file whenever it needs to display the icon image.

There is an enhanced version of RegisterClass named RegisterClassEx that uses a structure named WNDCLASSEX. WNDCLASSEX has two additional fields: cbSize and hIconSm. The cbSize field indicates the size of the WNDCLASSEX structure, and hIconSm is supposed to be set to the icon handle of the small icon. Thus, in the WNDCLASSEX structure you set two icon handles associated with two icon files—one for a standard icon and one for the small icon.

Is this necessary? Well, no. As we've seen, Windows already extracts the correctly sized icon images from a single icon file. And RegisterClassEx seems to have lost the intelligence of RegisterClass. If the hIconSm field references an icon file that contains multiple images, only the first image will be used. This might be a standard size icon that is then reduced in size. RegisterClassEx seems to have been designed for using multiple icon images, each of which contains only one icon size. Because we can now include multiple icon sizes in the same file, my advice is to use WNDCLASS and RegisterClass.

If you later want to dynamically change the program's icon while the program is running, you can do so using SetClassLong. For example, if you have a second icon file associated with the identifier IDI_ALTICON, you can switch to that icon using the statement

SetClassLong (hwnd, GCL_HICON, 
     LoadIcon (hInstance, MAKEINTRESOURCE (IDI_ALTICON))) ;

If you don't want to save the handle to your program's icon but instead use the DrawIcon function to display it someplace, you can obtain the handle by using GetClassLong. For example:

DrawIcon (hdc, x, y, GetClassLong (hwnd, GCL_HICON)) ;

At some places in the Windows documentation, LoadIcon is said to be "obsolete" and LoadImage is recommended instead. (LoadIcon is documented in /Platform SDK/User Interface Services/Resources/Icons, and LoadImage in /Platform SDK/User Interface Services/Resources/Resources.) LoadImage is certainly more flexible, but it hasn't replaced the simplicity of LoadIcon just yet. You'll notice that LoadIcon is called twice in ICONDEMO for the same icon. This presents no problem and doesn't involve extra memory being used. LoadIcon is one of the few functions that obtain a handle but do not require the handle to be destroyed. There actually is a DestroyIcon function, but it is used in conjunction with the CreateIcon, CreateIconIndirect, and CreateIconFromResource functions. These functions allow your program to dynamically create an icon image algorithmically.

Using Customized Cursors

Using customized mouse cursors in your program is similar to using customized icons, except that most programmers seem to find the cursors that Windows supplies to be quite adequate. Customized cursors are generally monochrome with a dimension of 32 by 32 pixels. You create a cursor in the Developer Studio in the same way as an icon (that is, select Resource from the Insert menu, and pick Cursor), but don't forget to define the hotspot.

You can set a customized cursor in your class definition with a statement such as

wndclass.hCursor = LoadCursor (hInstance, MAKEINTRESOURCE (IDC_CURSOR)) ;

or, if the cursor is defined with a text name,

wndclass.hCursor = LoadCursor (hInstance, szCursor) ;

Whenever the mouse is positioned over a window created based on this class, the customized cursor associated with IDC_CURSOR or szCursor will be displayed.

If you use child windows, you may want the cursor to appear differently, depending on the child window below the cursor. If your program defines the window class for these child windows, you can use different cursors for each class by appropriately setting the hCursor field in each window class. And if you use predefined child window controls, you can alter the hCursor field of the window class by using

SetClassLong (hwndChild, GCL_HCURSOR,
              LoadCursor (hInstance, TEXT ("childcursor")) ;

If you separate your client area into smaller logical areas without using child windows, you can use SetCursor to change the mouse cursor:

SetCursor (hCursor) ;

You should call SetCursor during processing of the WM_MOUSEMOVE message. Otherwise, Windows uses the cursor specified in the window class to redraw the cursor when it is moved. The documentation indicates that SetCursor is fast if the cursor doesn't have to be changed.

Character String Resources

Having a resource for character strings may seem odd at first. Certainly we haven't had any problems using regular old character strings defined as variables right in our source code.

Character string resources are primarily for easing the translation of your program to other languages. As you'll discover later in this chapter and in the next chapter, menus and dialog boxes are also part of the resource script. If you use character string resources rather than putting strings directly into your source code, all the text that your program uses will be in one file—the resource script. If the text in this resource script is translated into another language, all you need to do to create a foreign-language version of your program is relink the program. This method is much safer than messing around with your source code. (However, aside from the next sample program, I will not be using string tables for any other programs in this book. The reason is that string tables tend to make code look more obscure and complicated rather than clarifying it.)

You create a string table by selecting Resource from the Insert menu and then selecting String Table. The strings will be shown in a list at the right of the screen. Select a string by double-clicking it. For each string, you specify an identifier and the string itself.

In the resource script, the strings show up in a multiline statement that looks something like this:

STRINGTABLE DISCARDABLE
BEGIN
     IDS_STRING1, "character string 1"
     IDS_STRING2, "character string 2"
     [other string definitions]
END

If you were programming for Windows back in the old days and creating this string table manually in a text editor (which you might correctly guess was easier than creating the string table in Developer Studio), you could substitute left and right curly brackets for the BEGIN and END statements.

The resource script can have multiple string tables, but each ID must uniquely identify only a single string. Each string can be only one line long with a maximum of 4097 characters. Use \t and \n for tabs and ends of lines. These control characters are recognized by the DrawText and MessageBox functions.

Your program can use the LoadString call to copy a string resource into a buffer in the program's data segment:

LoadString (hInstance, id, szBuffer, iMaxLength) ;

The id argument refers to the ID number that precedes each string in the resource script; szBuffer is a pointer to a character array that receives the character string; and iMaxLength is the maximum number of characters to transfer into the szBuffer. The function returns the number of characters in the string.

The string ID numbers that precede each string are generally macro identifiers defined in a header file. Many Windows programmers use the prefix IDS_ to denote an ID number for a string. Sometimes a filename or other information must be embedded in the string when the string is displayed. In this case, you can put C formatting characters in the string and use it as a formatting string in wsprintf.

All resource text—including the text in the string table—is stored in the .RES compiled resource file and in the final .EXE file in Unicode format. The LoadStringW function loads the Unicode text directly. The LoadStringA function (the only function available under Windows 98) performs a conversion of the text from Unicode to the local code page.

Let's look at an example of a function that uses three character strings to display three error messages in a message box. As you can see below, the RESOURCE.H header file contains three identifiers for these messages.

#define IDS_FILENOTFOUND 1
#define IDS_FILETOOBIG   2
#define IDS_FILEREADONLY 3

The resource script has this string table:

STRINGTABLE
BEGIN
     IDS_FILENOTFOUND,   "File %s not found."
     IDS_FILETOOBIG,     "File %s too large to edit."
     IDS_FILEREADONLY,   "File %s is read-only."
END

The C source code file also includes this header file and defines a function to display a message box. (I'll also assume that szAppName is a global variable that contains the program name.)

OkMessage (HWND hwnd, int iErrorNumber, TCHAR *szFileName)
{
     TCHAR szFormat [40] ;
     TCHAR szBuffer [60] ;

     LoadString (hInst, iErrorNumber, szFormat, 40) ;
     wsprintf (szBuffer, szFormat, szFilename) ;

     return MessageBox (hwnd, szBuffer, szAppName,
                        MB_OK ¦ MB_ICONEXCLAMATION) ;
}

To display a message box containing the "file not found" message, the program calls

OkMessage (hwnd, IDS_FILENOTFOUND, szFileName) ;

Custom Resources

Windows also defines a "custom resource," also called the "user-defined resource" (where the user is you, the programmer, rather than the lucky person who gets to use your program). The custom resource is convenient for attaching miscellaneous data to your .EXE file and obtaining access to that data within the program. The data can be in absolutely any format you want. The Windows functions that a program uses to access the custom resource cause Windows to load the data into memory and return a pointer to it. You can do whatever you want with that data. You'll probably find this to be a more convenient way to store and access miscellaneous private data than storing it in external files and accessing it with file input functions.

For instance, suppose you have a file called BINDATA.BIN that contains a bunch of data that your program needs for display purposes. This file can be in any format you choose. If you have a MYPROG.RC resource script in your MYPROG project, you can create a custom resource in Developer Studio by selecting Resource from the Insert menu and pressing the Custom button. Type in a type name by which the resource is to be known: for example, BINTYPE. Developer Studio will then make up a resource name (in this case, IDR_BINTYPE1) and display a window that lets you enter binary data. But you don't need to do that. Click the IDR_BINTYPE1 name with the right mouse button, and select Properties. Then you can enter a filename: for example, BINDATA.BIN.

The resource script will then contain a statement like this:

IDR_BINTYPE1 BINTYPE BINDATA.BIN

This statement looks just like the ICON statement in ICONDEMO, except that the resource type BINTYPE is something we've just made up. As with icons, you can use text names rather than numeric identifiers for the resource name.

When you compile and link the program, the entire BINDATA.BIN file will be bound into the MYPROG.EXE file.

During program initialization (for example, while processing the WM_CREATE message), you can obtain a handle to this resource:

hResource = LoadResource (hInstance,
            FindResource (hInstance, TEXT ("BINTYPE"), 
                          MAKEINTRESOURCE (IDR_BINTYPE1))) ;

The variable hResource is defined with type HGLOBAL, which is a handle to a memory block. Despite its name, LoadResource does not actually load the resource into memory. The LoadResource and FindResource functions used together like this are essentially equivalent to the LoadIcon and LoadCursor functions. In fact, LoadIcon and LoadCursor use the LoadResource and FindResource functions.

When you need access to the text, call LockResource:

pData = LockResource (hResource) ;

LockResource loads the resource into memory (if it has not already been loaded) and returns a pointer to it. When you're finished with the resource, you can free it from memory:

FreeResource (hResource) ;

The resource will also be freed when your program terminates, even if you don't call FreeResource.

Let's look at a sample program that uses three resources—an icon, a string table, and a custom resource. The POEPOEM program, shown in Figure 10-5 beginning below, displays the text of Edgar Allan Poe's "Annabel Lee" in its client area. The custom resource is the file POEPOEM.TXT, which contains the text of the poem. The text file is terminated with a backslash (\).

Figure 10-5. The POEPOEM program, including an icon and a user-defined resource.

POEPOEM.C

/*-------------------------------------------
   POEPOEM.C -- Demonstrates Custom Resource
                (c) Charles Petzold, 1998
  -------------------------------------------*/

#include <windows.h>
#include "resource.h"

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

HINSTANCE hInst ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     TCHAR    szAppName [16], szCaption [64], szErrMsg [64] ;
     HWND     hwnd ;
     MSG      msg ;
     WNDCLASS wndclass ;
     
     LoadString (hInstance, IDS_APPNAME, szAppName, 
                            sizeof (szAppName) / sizeof (TCHAR)) ;

     LoadString (hInstance, IDS_CAPTION, szCaption, 
                            sizeof (szCaption) / sizeof (TCHAR)) ;
        
     wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
     wndclass.lpfnWndProc   = WndProc ;
     wndclass.cbClsExtra    = 0 ;
     wndclass.cbWndExtra    = 0 ;
     wndclass.hInstance     = hInstance ;
     wndclass.hIcon         = LoadIcon (hInstance, szAppName) ;
     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
     wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
     wndclass.lpszMenuName  = NULL ;
     wndclass.lpszClassName = szAppName ;
     
     if (!RegisterClass (&wndclass))
     {
          LoadStringA (hInstance, IDS_APPNAME, (char *) szAppName, 
                                  sizeof (szAppName)) ;

          LoadStringA (hInstance, IDS_ERRMSG, (char *) szErrMsg, 
                                  sizeof (szErrMsg)) ;


          MessageBoxA (NULL, (char *) szErrMsg, 
                             (char *) szAppName, MB_ICONERROR) ;
          return 0 ;
     }
     
     hwnd = CreateWindow (szAppName, szCaption,
                          WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;
     
     ShowWindow (hwnd, iCmdShow) ;
     UpdateWindow (hwnd) ;
     
     while (GetMessage (&msg, NULL, 0, 0))
     {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
     }
     return msg.wParam ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     static char  * pText ;
     static HGLOBAL hResource ;
     static HWND    hScroll ;
     static int     iPosition, cxChar, cyChar, cyClient, iNumLines, xScroll ;
     HDC            hdc ;
     PAINTSTRUCT    ps ;
     RECT           rect ;
     TEXTMETRIC     tm ;

     switch (message)
     {
     case WM_CREATE :
          hdc = GetDC (hwnd) ;
          GetTextMetrics (hdc, &tm) ;
          cxChar = tm.tmAveCharWidth ;
          cyChar = tm.tmHeight + tm.tmExternalLeading ;
          ReleaseDC (hwnd, hdc) ;
          
          xScroll = GetSystemMetrics (SM_CXVSCROLL) ;

          hScroll = CreateWindow (TEXT ("scrollbar"), NULL,
                                  WS_CHILD | WS_VISIBLE | SBS_VERT,
                                  0, 0, 0, 0,
                                  hwnd, (HMENU) 1, hInst, NULL) ;
          
          hResource = LoadResource (hInst, 
                      FindResource (hInst, TEXT ("AnnabelLee"),
                                           TEXT ("TEXT"))) ;
          
          pText = (char *) LockResource (hResource) ;
          iNumLines = 0 ;
          
          while (*pText != `\\' && *pText != `\0')
          {
               if (*pText == `\n')
                    iNumLines ++ ;
               pText = AnsiNext (pText) ;
          }
          *pText = `\0' ;
          
          SetScrollRange (hScroll, SB_CTL, 0, iNumLines, FALSE) ;
          SetScrollPos   (hScroll, SB_CTL, 0, FALSE) ;
          return 0 ;
          
     case WM_SIZE :
          MoveWindow (hScroll, LOWORD (lParam) - xScroll, 0,
                      xScroll, cyClient = HIWORD (lParam), TRUE) ;
          SetFocus (hwnd) ;
          return 0 ;
          
     case WM_SETFOCUS :
          SetFocus (hScroll) ;
          return 0 ;
          
     case WM_VSCROLL :
          switch (wParam)
          {
          case SB_TOP :
               iPosition = 0 ;
               break ;
          case SB_BOTTOM :
               iPosition = iNumLines ;
               break ;
          case SB_LINEUP :
               iPosition -= 1 ;
               break ;
          case SB_LINEDOWN :
               iPosition += 1 ;
               break ;
          case SB_PAGEUP :
               iPosition -= cyClient / cyChar ;
               break ;
          case SB_PAGEDOWN :
               iPosition += cyClient / cyChar ;
               break ;
          case SB_THUMBPOSITION :
               iPosition = LOWORD (lParam) ;
               break ;
          }
          iPosition = max (0, min (iPosition, iNumLines)) ;
          
          if (iPosition != GetScrollPos (hScroll, SB_CTL))
          {
               SetScrollPos (hScroll, SB_CTL, iPosition, TRUE) ;
               InvalidateRect (hwnd, NULL, TRUE) ;
          }
          return 0 ;
          
     case WM_PAINT :
          hdc = BeginPaint (hwnd, &ps) ;
               
          pText = (char *) LockResource (hResource) ;
               
          GetClientRect (hwnd, &rect) ;
          rect.left += cxChar ;
          rect.top  += cyChar * (1 - iPosition) ;
          DrawTextA (hdc, pText, -1, &rect, DT_EXTERNALLEADING) ;

          EndPaint (hwnd, &ps) ;
          return 0 ;
               
     case WM_DESTROY :
          FreeResource (hResource) ;
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}

POEPOEM.RC (excerpts)

//Microsoft Developer Studio generated resource script.

#include "resource.h"
#include "afxres.h"

/////////////////////////////////////////////////////////////////////////////
// TEXT

ANNABELLEE              TEXT    DISCARDABLE     "poepoem.txt"

/////////////////////////////////////////////////////////////////////////////
// Icon

POEPOEM                 ICON    DISCARDABLE     "poepoem.ico"

/////////////////////////////////////////////////////////////////////////////
// String Table

STRINGTABLE DISCARDABLE 
BEGIN
    IDS_APPNAME             "PoePoem"
    IDS_CAPTION             """Annabel Lee"" by Edgar Allan Poe"
    IDS_ERRMSG              "This program requires Windows NT!"
END

RESOURCE.H (excerpts)

// Microsoft Developer Studio generated include file.
// Used by PoePoem.rc

#define IDS_APPNAME                     1
#define IDS_CAPTION                     2
#define IDS_ERRMSG                      3

POEPOEM.TXT

It was many and many a year ago,
   In a kingdom by the sea,
That a maiden there lived whom you may know
   By the name of Annabel Lee;
And this maiden she lived with no other thought
   Than to love and be loved by me.

I was a child and she was a child
   In this kingdom by the sea,
But we loved with a love that was more than love --
   I and my Annabel Lee --
With a love that the winged seraphs of Heaven
   Coveted her and me.
 
And this was the reason that, long ago,
   In this kingdom by the sea,
A wind blew out of a cloud, chilling
   My beautiful Annabel Lee;
So that her highborn kinsmen came
   And bore her away from me,
To shut her up in a sepulchre
   In this kingdom by the sea.
 
The angels, not half so happy in Heaven,
   Went envying her and me --
Yes! that was the reason (as all men know,
   In this kingdom by the sea)
That the wind came out of the cloud by night,
   Chilling and killing my Annabel Lee.
 
But our love it was stronger by far than the love
   Of those who were older than we --
   Of many far wiser than we --
And neither the angels in Heaven above
   Nor the demons down under the sea
Can ever dissever my soul from the soul
   Of the beautiful Annabel Lee:
 
For the moon never beams, without bringing me dreams
   Of the beautiful Annabel Lee;
And the stars never rise, but I feel the bright eyes
   Of the beautiful Annabel Lee:
And so, all the night-tide, I lie down by the side
Of my darling -- my darling -- my life and my bride,
   In her sepulchre there by the sea --
   In her tomb by the sounding sea.
 
                                       [May, 1849]
\

POEPOEM.ICO

In the POEPOEM.RC resource script, the user-defined resource is given the type TEXT and the text name "AnnabelLee":

ANNABELLEE  TEXT  POEPOEM.TXT

During WM_CREATE processing in WndProc, a handle to the resource is obtained using FindResource and LoadResource. The resource is locked using LockResource, and a small routine replaces the backslash (\) at the end of the file with a 0. This is for the benefit of the DrawText function used later during the WM_PAINT message.

Note the use of a child window scroll bar control rather than a window scroll bar. The child window scroll bar control has an automatic keyboard interface, so no WM_KEYDOWN processing is required in POEPOEM.

POEPOEM also uses three character strings, the IDs of which are defined in the RESOURCE.H header file. At the outset of the program, the IDS_APPNAME and IDS_ CAPTION strings are loaded into memory using LoadString:

LoadString (hInstance, IDS_APPNAME, szAppName, sizeof (szAppName) / 
                                               sizeof (TCHAR)) ;

LoadString (hInstance, IDS_CAPTION, szCaption, sizeof (szCaption) /
                                               sizeof (TCHAR)) ;

Notice that these two calls precede RegisterClass. If you run the Unicode version of POEPOEM under Windows 98, these two function calls will fail. Despite the fact that LoadStringA is more complex than LoadStringW (because LoadStringA must convert the resource string from Unicode to ANSI, while LoadStringW just loads it directly), LoadStringW is not one of the few string functions that is supported under Windows 98. This means that when the RegisterClassW function fails under Windows 98, the MessageBoxW function (which is supported in Windows 98) cannot use strings loaded into the program using LoadStringW. For this reason, the program loads the IDS_APPNAME and IDS_ERRMSG strings using LoadStringA and then displays the customary message box using MessageBoxA:

if (!RegisterClass (&wndclass))
{
     LoadStringA (hInstance, IDS_APPNAME, (char *) szAppName, 
                             sizeof (szAppName)) ;

     LoadStringA (hInstance, IDS_ERRMSG, (char *) szErrMsg, 
                             sizeof (szErrMsg)) ;

     MessageBoxA (NULL, (char *) szErrMsg, 
                        (char *) szAppName, MB_ICONERROR) ;
     return 0 ;
}

Notice the casting of the TCHAR string variables into char pointers. With all character strings used in POEPOEM defined as resources, the program is now easier for translators to convert to a foreign-language version. Of course, they'd also have to translate the text of "Annabel Lee"—which would be, I suspect, a more difficult task.