To minimize the amount of rewriting needed for applications to support mirroring, system components such as the GDI and User modules have been adapted to enable control over the mirroring state of the UI objects. Almost no additional code changes are necessary, with the exception of a few things you will need to consider when working with owner-drawn controls and bitmaps. As you will see, new APIs and extended styles for windows let you turn mirroring on and off at the process level, the window level, the dialog-resources level, or even the device-context level. However, before examining the capabilities of system components and APIs that are available when you enable mirroring in code, this chapter will take a quick look at how and when to enable mirroring in resources.
With changes that the Windows team has made to the system's resource loader, you can mirror your applications by simply editing your resources. This is a good approach to use when you want to mirror old components for which the source is not available or is no longer used; it is also beneficial when you want to conduct primary testing of your applications to see if they support mirroring.
You can mirror applications by adding two left-to-right marks (LRMs)-represented by Unicode code point U+200E-in front of the FileDescription value of the version stamping. Figure 8-3 shows an English Notepad.exe file that has been mirrored with this approach.
Figure 8.3 - A Notepad.exe file that has been mirrored by adding two LRMs.
Although easy, enabling mirroring in resources offers little flexibility when it comes to just mirroring a specific window or control. In fact, all windows and controls within those windows that belong to the affected process will be automatically mirrored. For that reason, enabling mirroring in code is more flexible than mirroring in resources, since it allows you the freedom to decide what you want mirrored. Keep in mind that a child process created by the mirrored process does not inherit the mirroring style.
Important
Glossary
Although you can activate mirroring through the localization process (with non- compile mirroring), doing so will often reveal some issues that require a code fix. Also, as stated, you might want full control over when to enable mirroring-such as per process, per window, for dialog resources, or per device context, as shown in the following sections-and over which elements you want mirrored. This is only possible through coding and through new mirroring APIs.
By activating the mirroring layout on a per-process basis, all windows that are created after activation will be mirrored; but this activation will not affect the existing windows. This approach is very similar to noncompile mirroring, with the exception that the mirroring style within an application can be turned on and off at run time rather than at a binary level, as in the case of noncompile mirroring.
You set the default direction to RTL for a process by calling SetProcessDefaultLayout(LAYOUT_RTL). You can also turn off default mirroring by calling SetProcessDefaultLayout(0). In addition, you can query the current default layout direction as follows:
BOOL WINAPI GetProcessDefaultLayout(DWORD *pdwDefaultLayout);
After returning successfully, pdwDefaultLayout contains either LAYOUT_RTL or 0.
Although you can activate mirroring at the process level, there are times you might wish to activate it at a lower level-such as per window. The following section explains when this method is a good option and shows you what extended styles to use.
Once the mirroring style is set at a process level, all newly created windows are automatically mirrored. An alternative approach is to activate mirroring on a per-window basis (if not all windows in your application are meant to be mirrored). With this approach, a window can be mirrored upon its creation by calling CreateWindowEx, and set by using the extended style WS_EX_LAY- OUTRTL.
By default, all child windows of a parent window inherit the mirroring attribute. However, once again, not all windows are necessarily meant to be mirrored; thus you might want to prevent child windows from being mirrored, for example. To remove the mirroring inheritance for child windows, you need to specify the new extended style WS_EX_NOINHERITLAYOUT in your call to CreateWindowEx.
A good example of a case where you should prevent the mirroring of child windows would be when you have windows that host or import external controls, such as Microsoft ActiveX, Java applets, Component Object Model (COM) objects, and so on. The problem with controls such as these is that you have little ability to manipulate their behavior, and you cannot readily impose mirroring requirements on them.
The following code shows how to toggle the layout of a window between RTL and LTR:
LONG lExStyles; // Retrieve the extended style of the window. lExStyles = GetWindowLong(hWnd, GWL_EXSTYLE); // Toggle layout. lExStyles ^= WS_EX_LAYOUTRTL; // Set extended styles to new value. SetWindowLong(hWnd, GWL_EXSTYLE, lExStyles); // Update client area. InvalidateRect(hWnd, NULL, TRUE);
Important
Stepping down yet another level, you can activate mirroring at the level of a window's dialog resources. The following section shows you how to do this and identifies which extended styles to use.
It's important to distinguish between a window and a dialog box (which does not receive WM_CREATE, but rather WM_INITDIALOG). To activate mirroring for dialog boxes, you need to set the mirroring flags in your resource editor and add the extended styles WS_EX_LAYOUTRTL and WS_EX_NOINHERITLAYOUT. (The second style is only if you want to prevent the inheritance from taking place.)
The only way to switch between mirrored and nonmirrored dialog resources at run time is to have two sets of dialog resources: one mirrored and one nonmirrored. The Dialog Properties property sheet within the resource editor of Microsoft Visual Studio 6 allows you to set the mirroring style of the dialog resources. (See Figure 8-4.)
Figure 8.4 - Setting the mirroring style of the dialog resources within the resource editor of Visual Studio 6.
Another level at which you can enable mirroring is at the level of the device context. The following pinpoints when and how to do this.
The mirroring technology described up to now applies to all objects in a standard window. These objects, as well as bitmaps and icons in a mirrored window, are all mirrored by default. Obviously, there are some graphics objects, particularly those that include text, that shouldn't be mirrored. Therefore, Microsoft provides several GDI functions in platforms that support mirroring (which, again, includes all versions of Windows 2000 and Windows XP). These functions are shown in the following code:
BOOL WINAPI SetLayout(HDC hDc, DWORD dwLayout); DWORD WINAPI GetLayout(HDC hDc);
The device context of a mirrored window is mirrored by default. Nevertheless, you can disable mirroring in a device context by calling SetLayout (hdc, 0), and activate it by calling SetLayout (hdc, LAYOUT_RTL). You might need control over the mirroring properties of a device context in cases when some drawing operation performed by a legacy code is broken by mirroring.
Drawing images that may be sensitive to directionality of the output presents a separate problem. To prevent mirroring of bitmaps, call SetLayout with the LAYOUT_BITMAPORIENTATIONPRESERVED bit set in dwLayout :
SetLayout(hdc, LAYOUT_RTL | LAYOUT_BITMAPORIENTATIONPRESERVED);
Upon successful completion of GetLayout, GetLayout returns the following:
Yet another way to prevent a bitmap from being mirrored is to define NOMIR-RORBITMAP in calls to BitBlt and StretchBlt.
Now that you know how and when to activate mirroring for processes, windows, dialog resources, and device contexts, what are some other factors to consider when mirroring? For instance, how can you use mirroring for property sheets?
A tricky situation when enabling mirroring is when you are dealing with property sheets (and wizards). Imagine the case where a property-sheet container has several pages, and only a few of them are localized into an RTL language. Individual pages are made out of dialog templates that can be mirrored separately in the resources. But what about the container? Should the container be mirrored? To avoid confusion and inconsistent behavior with property sheets, the following approaches are among many steps that the system takes in order to decide whether to create a mirrored container:
The GlobalDev sample application shows how to create a property-sheet client that is mirrored. (To view this application, see the Samples subdirectory on the companion CD.) Figure 8-5 illustrates a GlobalDev application created in a fully mirrored container when the UI language is set to Hebrew.
Figure 8.5 - GlobalDev application created in a fully mirrored container when the UI language is set to Hebrew.
Another area to consider when mirroring Win32 applications involves mapping coordinates. The following shows you how to handle what can sometimes be another tricky matter.
Glossary
Perhaps one of the biggest issues when it comes to dealing with mirroring is mapping between screen coordinates and client coordinates, or failing to do so. For example, developers often use code such as the following to position a control in a window:
GetWindowRect(hControl, (LPRECT) &rControlRect); // Gets coordinates of window in screen coordinates. ScreenToClient(hDialog, (LPPOINT) &rControlRect.left); // Maps screen coordinates to client coordinates // in dialog box. ScreenToClient(hDialog, (LPPOINT) &rControlRect.right);
In this code, the left edge of a rectangle becomes the right edge in a mirrored window, and vice versa, causing unintended results. The solution is to replace the ScreenToClient calls as shown in the following code:
GetWindowRect(hControl, (LPRECT) &rControlRect); // Gets coordinates of window in screen coordinates. MapWindowPoints(NULL, hDialog, (LPPOINT) &rControlRect, 2);
This code works because the MapWindowPoints API has been modified on platforms that support mirroring. As a result of this modification, Map-WindowPoints is able to swap the left- and right-point coordinates of the client window that is being mirrored, whenever two points at a time are passed to this API.
Another common practice that can cause problems in mirrored windows is using offsets in screen coordinates to position objects in a client window, instead of using client coordinates. For example, to position a control in a dialog box, the following code uses the difference in the left edges of the control and the dialog box-in screen coordinates-as the x position in client coordinates:
RECT rcDialog; RECT rcControl; HWND hControl = GetDlgItem(hDlg, IDD_CONTROL); GetWindowRect(hDlg, &rcDialog); // Gets rect in screen coordinates GetWindowRect(hControl, &rcControl); // Takes x position in client coordinates MoveWindow(hControl, rcControl.left - rcDialog.left, rcControl.top - rcDialog.top, nWidth, nHeight, FALSE);
This works fine when the dialog window has an LTR layout, and when the mapping mode of the client is MM_TEXT. The new x position in client coordinates corresponds to the difference in the left edges of the control and the dialog box in screen coordinates. In a mirrored dialog box, however, the roles of left and right are reversed. You can remove the assumption from the previous code that near is left and far is right by using MapWindowPoints to go into client coordinates, as shown in the following code sample:
RECT rcDialog; RECT rcControl; HWND hControl = GetDlgItem(hDlg, IDD_CONTROL); GetWindowRect(hControl, &rcControl); // MapWindowPoints will work correctly in mirrored // windows and in non-mirrored windows. MapWindowPoints(NULL, hDlg, (LPPOINT)&rcControl, 2); //Now rcControl is in client coordinates. MoveWindow(hControl, rcControl.left, rcControl.top, nWidth, nHeight, FALSE);
There is a common theme in these examples. Instead of thinking in terms of the concrete notions of left and right, you should substitute the more abstract concepts of near and far. The following are some specific guidelines on developing applications that can be mirrored by using the automatic mirroring interfaces:
Another area to consider when mirroring is how to deal with direction-sensitive graphics. Suppose you have an application that contains a relatively large number of graphics. How these graphics are mirrored (or whether they are at all) will vary according to circumstances.
Direction-sensitive graphics (such as bitmaps and icons) present another challenge with regard to mirroring. Some direction-sensitive graphics can have a different meaning when mirrored. For example, within an LTR layout in a browser, an arrow that points to the left represents the concept of going back to the previous page; an arrow that points to the right would signify going forward to the next page. When these arrows are mirrored for an RTL layout, the meaning will be just the opposite. (See Figure 8-6.)
Figure 8.6 - Example of a direction-sensitive arrow that changes meaning when mirrored.
Other graphics should not be mirrored at all, such as legal trademarks or logos because they will make no sense when laid out from right to left. (See Figure 8-7.)
Figure 8.7 - Example of a direction-sensitive graphic that should not be mirrored.
There are several solutions available for dealing with cases such as these. Deciding which of the following four approaches to use depends on the particular situation at hand.
One solution is to temporarily turn off RTL layout of the device context when rendering direction-sensitive bitmaps or icons; you can then turn it back on once mirroring is completed. The problem with this approach is that sometimes you don't have access to the device context where the graphics will be rendered. (An example of this is when passing your icon or bitmap handle to a third-party DLL that will display the image for you.)
Another solution is to have two different sets of graphics in your resources-one set to be used when the drawing destination is LTR-oriented, and the other set to be used when the drawing destination is RTL-oriented. The first set will contain the original, LTR graphics. The second set will contain a copy of the first set of graphics with modifications made only to those graphics needing to reflect the RTL orientation. You can then choose which set to load and render based on whether the set will be rendered in a mirrored or nonmirrored situation. In other words, for nonmirrored device contexts you would load the original set of nonmirrored graphics, and for mirrored device contexts you would load the set that contained the assortment of mirrored and nonmirrored graphics. (In the latter case, when mirrored graphics are rendered on a mirrored device context, they will be mirrored again and thus revert back to the way you want them to be displayed.) Depending on the number of direction-sensitive graphics that you have in your project, this approach might not be your best choice if you have a lot of graphics that should not be mirrored, since duplicating those will increase the size of your resources.
As a third solution, if you ship a separate set of binaries based on the target localized languages, you can let your localization team mirror those graphics elements using a resource editor. (Most resource editors let you flip images in both x and y directions nowadays.) A final solution-especially when you don't have direct access to the device context where the graphics will be rendered-is to create a mirrored copy of the resource. You then pass this mirrored copy instead of the original, nonmirrored one. (Again, when the mirrored graphics are rendered on a mirrored device context, they will revert back to their original, intended look.) The following sample code illustrates how to mirror both a bitmap and an icon when each's handle is passed:
HBITMAP CreateMirroredBitmap( HBITMAP hbmOrig) { HDC hdc, hdcMem1, hdcMem2; HBITMAP hbm = NULL, hOld_bm1, hOld_bm2; BITMAP bm; if (!hbmOrig) return NULL; if (!GetObject(hbmOrig, sizeof(BITMAP), &bm)) return NULL; // Grab the screen DC. hdc = GetDC(NULL); if (hdc) { hdcMem1 = CreateCompatibleDC(hdc); if (!hdcMem1) { ReleaseDC(NULL, hdc); return NULL; } hdcMem2 = CreateCompatibleDC(hdc); if (!hdcMem2) { DeleteDC(hdcMem1); ReleaseDC(NULL, hdc); return NULL; } hbm = CreateCompatibleBitmap(hdc, bm.bmWidth, bm.bmHeight); if (!hbm) { ReleaseDC(NULL, hdc); DeleteDC(hdcMem1); DeleteDC(hdcMem2); return NULL; } /* * Flip the bitmap. */ hOld_bm1 = (HBITMAP)SelectObject(hdcMem1, hbmOrig); hOld_bm2 = (HBITMAP)SelectObject(hdcMem2 , hbm ); SetLayout(hdcMem2, LAYOUT_RTL); BitBlt(hdcMem2, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem1, 0, 0, SRCCOPY); SelectObject(hdcMem1, hOld_bm1 ); SelectObject(hdcMem1, hOld_bm2 ); DeleteDC(hdcMem1); DeleteDC(hdcMem2); ReleaseDC(NULL, hdc); } return hbm; } HICON CreateMirroredIcon(HICON hiconOrg) { HDC hdcScreen, hdcBitmap, hdcMask = NULL; HBITMAP hbm, hbmMask, hbmOld,hbmOldMask; BITMAP bm; ICONINFO ii; HICON hicon = NULL; hdcBitmap = CreateCompatibleDC(NULL); if (hdcBitmap) { hdcMask = CreateCompatibleDC(NULL); if( hdcMask ) { SetLayout(hdcBitmap, LAYOUT_RTL); SetLayout(hdcMask, LAYOUT_RTL); } else { DeleteDC( hdcBitmap ); hdcBitmap = NULL; } } hdcScreen = GetDC(NULL); if (hdcScreen) { if (hdcBitmap && hdcMask) { if (hiconOrg) { if (GetIconInfo(hiconOrg, &ii) && GetObject(ii.hbmColor, sizeof(BITMAP), &bm) { /* * Do the cleanup for the bitmaps. */ DeleteObject( ii.hbmMask ); DeleteObject( ii.hbmColor ); ii.hbmMask = ii.hbmColor = NULL; hbm = CreateCompatibleBitmap(hdcScreen, bm.bmWidth, bm.bmHeight); hbmMask = CreateBitmap(bm.bmWidth, bm.bmHeight, 1, 1, NULL); hbmOld = (HBITMAP)SelectObject(hdcBitmap, hbm); hbmOldMask = (HBITMAP)SelectObject(hdcMask, hbmMask); DrawIconEx(hdcBitmap, 0, 0, hiconOrg, bm.bmWidth, bm.bmHeight, 0, NULL, DI_IMAGE); DrawIconEx(hdcMask, 0, 0, hiconOrg, bm.bmWidth, bm.bmHeight, 0, NULL, DI_MASK); SelectObject(hdcBitmap, hbmOld); SelectObject(hdcMask, hbmOldMask); // // Create the new mirrored icon and delete // bitmaps // ii.hbmMask = hbmMask; ii.hbmColor = hbm; hicon = CreateIconIndirect(&ii); DeleteObject(hbm); DeleteObject(hbmMask); } } } ReleaseDC(NULL, hdcScreen); } if (hdcBitmap) DeleteDC(hdcBitmap); if (hdcMask) DeleteDC(hdcMask); return hicon; }
A final area to consider when mirroring in Win32 applications pertains to graphics once again. The following will examine what you need to think of when working with image-list controls.
A good way of dealing with graphics is to use image-list controls because of the wide flexibility these controls offer. Image-list controls are also used to manage graphics for other controls like tree-view and list-view controls. However, there is one potential issue that can arise. In the case of direction-sensitive bitmaps, if you are using an image list whose contents will be rendered on a mirrored device context, the image list will not have direct access to the image-list device context. On Windows XP, you can use the image-list flags ILC_MIRROR and ILC_PERITEMMIRROR to enable mirroring. If your code needs to run on Windows 2000, Windows 98, or Windows Me with mirroring support, you can use the previous code samples to mirror the direction-sensitive bitmap or icon before adding it to the list. Note, however, that the previous code will allow you to mirror such bitmaps and icons if you are adding these images to the list one by one. If you are adding images to the list more than one at a time, you can tweak the previous code to mirror each element inside the image strip that you are adding to the list. For example, you can modify CreateMirroredBitmap(), so that instead of this line:
BitBlt(hdcMem2, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem1, 0, 0, SRCCOPY);
you can have the following code, where Width is the width of each individual image in the image strip that will be added to the image list:
int n = bm.bmWidth / Width, i; for(i =0; i < n; i++) { BitBlt(hdcMem2, i * Width + 0, 0, Width, bm.bmHeight, hdcMem1, (n - i - 1) * Width, 0, SRCCOPY); }
Specifying the width of each individual image will achieve the same effect on Windows 2000, Windows 98, or Windows Me as if you were to specify ILC_PERITEMMIRROR on Windows XP.
Thus far in this chapter, you have seen ways to leverage APIs, extended styles for windows, and good coding practices for mirroring within a Win32 environment. The following discusses the best ways to go about mirroring Web content. Included are tips regarding directional images, left alignment of text, absolute positioning, and tables.