Globalizing Applications with MFC

F. Avery Bishop

May 9, 1995

Two features provided by the Visual C++ development system are designed to reduce a developer's time from concept to product ship: a class library that implements a large part of the Win32 API in C++ classes, and an integrated development environment (IDE) consisting of App Studio, AppWizard, and ClassWizard that practically writes your applications for you.

These tools can save time for developers writing internationalized applications as well.

Let's take a look first at what Microsoft Foundation Class Library (MFC) provides writers of international applications, and then go through an example of how to use MFC in such an application, employing ClassWizard.

Where did the NLS APIs go?

MFC is designed to implement Windows handles as classes. Thus, the device context (DC) class CDC corresponds to HDC, a handle to a device context. Similarly, CWnd corresponds to HWND, CWinApp to HINSTANCE, and so on. In general, those functions in the Win32 API associated with a handle became member functions of the corresponding MFC class. For example, you call SetMenu as a member of a CWnd object, LoadMenu as a member of a CMenu object, and LoadString as a member of a CString object (see below).

Those functions not logically associated with a class remain available as simple C-style functions. For example, you can still use the familiar NLS (National Language Support) API functions—such as GetLocaleInfo, SetThreadLocale, FindResourceEx, WideCharToMultiByte, and MultiByteToWideChar—as you always have.

On the other hand, the encapsulation of those functions that are directly related to an object helps application writers avoid such pitfalls as passing the wrong type of handle or otherwise misusing handles and other data types.

The CString class

MFC also provides utility classes not covered by the Win32 API. The CString class provides an assortment of member functions and overloaded operators for encapsulating textual data that helps speed the process of writing international applications. The CString class operators—such as + and += (concatenation), == (comparison), and so on—have the semantics of an actual string, rather than a pointer to a string, similar to string-handling conventions in Basic.

Where applicable, the member functions are locale or code-page sensitive. For example, the CString::Compare function uses the current locale setting to provide locale-sensitive comparison or sorting. As expected, the OemToAnsi and AnsiToOem member functions use the current OEM and ANSI code page settings to convert between the two.

The other convenient feature of CString objects is that the string buffer is based on the _TCHAR data type, so an application that makes full use of CString can be based on Unicode, single-byte character set (SBCS), or multibyte character set (MBCS). (See "Internationalizing with Visual C++" by Chris Weight in the September 1994 Developer Network News.)

Message handlers

ClassWizard uses message handlers to let you add processing for a specific Windows message or control without paging through a huge switch statement, as was necessary in straight ANSI C.

To implement a message handler using ClassWizard, you first select either the message that you want to process, such as WM_IME_NOTIFY, or a new command that you add yourself, such as ID_SELECTFONT. You also decide which of your application's modules should process it.

ClassWizard then inserts lines in a message map in your implementation of that module's class, indicating that the message or command is to be processed by a member function that you write, called a handler. It also puts a prototype of the handler in the appropriate header file, and gives you the option of editing a skeletal version of the handler in the source file for the module. A message handler is generally given a name of the form On<message name>—for example, OnImeNotify.

Japanese example

Let's look at some examples of how these various classes and member functions can be used together. Suppose you have an application that needs to change the language used in its user interface—that is, in its menu prompts, message text, and so on. One way to do that is to use the LANGUAGE keyword in the resource file. (This keyword is not directly supported by Visual C++ 2.0, but will be supported in a future version. See the "Resource Statements Not Supported by Visual C++" Help file in Books Online for an explanation of how to use this keyword using Visual C++2.0.)

For example, here is a Japanese menu.

The corresponding UK English menu is as follows:

#define LANG_ENGLISH 0x09

#define SUBLANG_ENGLISH_UK 0x02

//

// English menu

//

 

IDR_MAINFRAME MENU PRELOAD DISCARDABLE

LANGUAGE LANG_ENGLISH SUBLANG_ENGLISH_UK

BEGIN

POPUP "&File"

BEGIN

MENUITEM "&New\tCtrl+N", ID_FILE_NEW

MENUITEM "&Open...\tCtrl+O", ID_FILE_OPEN

MENUITEM "&Save...\tCtrl+S", ID_FILE_SAVE

MENUITEM "Save &As...", ID_FILE_SAVE_AS

/* etc. */

MENUITEM "E&xit", ID_APP_EXIT

END

POPUP "&Edit"

/* etc. */

POPUP "&Options"

BEGIN

MENUITEM "Change UI Language (&L)", ID_OPTIONS_LANGUAGE

END

/* etc. */

END

One can then select a locale and change the language used in menus and strings as follows:

void CMainFrame::OnOptionsMenuUI()

{

CMenu *pmnMenu ;

 

CString Error, Format, AppName ;

LCID dwSelected_LCID =

MAKELCID(MAKELANGID(LANG_ENGLISH,

SUBLANG_ENGLISH_UK), SORT_DEFAULT) ;

// Default locale.

// Omitted: Code to select locale, e.g., from

// a list box, and set dwSelected_LCID.

 

if(!SetThreadLocale(dwSelected_LCID))

{

 

VERIFY(Format.LoadString(ID_SETLOCALE_ERROR))

;

VERIFY(AppName.LoadString(ID_APPNAME)) ;

Error.Format(Format, dwSelected_LCID) ;

VERIFY(MessageBox(Error, AppName , MB_OK |

MB_ICONEXCLAMATION)) ;

return ;

}

 

VERIFY(SetMenu(NULL)) ; // Erases the

// current menu

pmnMenu = &mnLocalizedMenu ; // This is a

// global instance of CMenu

ASSERT(pmnMenu != 0) ; // Make sure the

// intialization

// worked

VERIFY(pmnMenu->LoadMenu(IDR_MAINFRAME)) ;

// Load menu of language ID matching

// current locale

VERIFY(SetMenu(pmnMenu)) ; // Set the menu

// in the main

// frame window

VERIFY(NULL!=pmnMenu->Detach()) ; // Local

// object will

// go out of

// scope

}

The VERIFY macro is a debugging device that does nothing in retail (nondebug) mode, but displays a warning message box and lets the user abort or ignore the error when the argument returns FALSE in debug mode. In the case of SetThreadLocale, an error message is displayed explicitly, because this error is likely to occur even in a "bug-free" retail version. To report the error, we have used the LoadString and Format (similar to sprintf) member functions of the CString object, and passed the formatted CString object to MessageBox, a member function of CWnd.

Note that SetThreadLocale is a simple Win32 function, rather than a member function of any class. SetMenu also appears to be a stand-alone function in this example, but it is actually a member function of the current object, CMainFrame (which is derived ultimately from the CWnd class). Thus, no class name is needed. In contrast, LoadMenu and Detach are member functions of the CMenu class, which must be explicitly indicated.

Text in a string table can also be marked with the Language keyword, enabling the language to be switched in the user interface. Any call to CString::LoadString will now load the string of the appropriate language, as defined by the current locale setting.

Anything Win32 can do...

Anything you can do in the Win32 API, you can do in MFC. Some of the Win32 NLS functions have been moved to member functions of classes, while others remain as stand-alone C-style functions.

The easiest way to find out where one of your favorite functions went is to highlight it in the Visual C++ 2.0 editor, and press F1. If the function is a member function of some class, the WinHelp application will give you a choice of the MFC version or the Win32 version of the help for that function. Otherwise, it will go straight to the Win32 description.

F. Avery Bishop, a technical evangelist in Microsoft's Developer Division, brews his own beer, speaks and reads Japanese, and will kill for good sushi.

An MFC Application's Basic Structure

The standard MFC-based application consists of four basic modules:

CWinApp, the main module controlling the initialization and processing of the rest of an application. It corresponds roughly to a WinMain function in C-based Windows source code.

• A CMainFrame object and member functions, which make up the main window of your application.

• Single-document interface (SDI) and multiple-document interface (MDI) CDocument objects and member functions. These objects perform any direct processing of the data in the user's document.

• One CView object (or a derived class such as CEditView or CScrollView) for each CDocument object. This object is for display of the document and for receiving the user's input into the document.

The AppWizard in Visual C++ creates basic versions of these classes that an application writer edits and enhances.