循序渐进全球化
镜像识别
本页内容
概述和说明
对 于从右向左 (RTL) 的语言,不但文本对齐和文本阅读顺序沿着从右向左的方向,而且 UI 元素布局也应沿着从右向左这一自然方向。当然,这种布局更改仅适用于本地化的 RTL 语言(在 Windows 2000 之前,只有阿拉伯语和希伯来语)。在 Windows 95 项目期间,"Espresso" 中引入了一种翻转方法,它可以替换对话框的控制元素。这种局部解决方案并没有解决标题栏、树视图、组合框等的布局问题。它只是在翻转后的对话框中为它们指 定了一个新位置。
为了解决翻转方法遗留的问题,在阿拉伯语和希伯来语版本的 Win98 中引入了镜像方法,并在 Win2000 中得到了应用。它为 UI 提供了完美的从右向左 (RTL) 的外观和感觉。对于 Win98,此方法仅适用于本地化的阿拉伯语和希伯来语操作系统,但在 Windows 2000 中,操作系统的所有语言版本和类型都已经可以识别镜像,并且镜像的应用程序可以在所有 Win2000 平台上创建和执行。下面的图 1 显示的是一个阿拉伯语 Win98 的镜像桌面,其中从“开始”按钮到树视图控件,所有 UI 元素都已被镜像。
图 1: 阿拉伯语 Windows XP 桌面 |
镜像实际上只不过是一种坐标转换:
-
原点 (0,0) 位于窗口的右上角
- X 缩放系数 = -1(即 x 值从右向左递增)
图 2: 坐标转换 |
为了避免混淆坐标范围,可尝试将左 和右 的概念替换为近 和远 的概念。
为最大程度减少使应用程序支持镜像所需的重写工作量,系统组件(如 "GDI" 和 "User")已经过了修改,除了几个有关自绘控件和位图的注意事项外,几乎不再需要任何额外的代码更改即可开启和关闭镜像功能(请参见示例)。
开启和关闭镜像功能有两种不同的方法:
- 已编译应用程序方法 :如果对应用程序源代码具有完全访问权限,则使用此方法。
- 未编译应用程序方法 :如果对旧有应用程序的任何代码都不具有访问权限,则使用此方法。这通常由一些资源本地化工具来完成。
有关这两种方法的详细信息,请参阅代码示例 部分。
示例
正如我们在前面的概述部分所提到的,除了几个有关自绘控件的注意事项以外,要想完全识别镜像并不需要太多的代码更改。
大多数与镜像相关的错误实际上都与自绘控件有关。以下是几个示例:
- 颠倒的位图:
在镜像的 DC 中绘制位图时,就会发生这种情况。对于已编译的应用程序,可以通过调用 SetLayout API 来关闭 DC 的镜像。对于未编译的应用程序,解决方法是在本地化资源中翻转 Windows 徽标。位图在水平方向翻转两次后即正常显示!有关详细信息,请参阅解决方案与代码示例 部分。
- 偏离屏幕位图:
自 绘命令或位图被绘制在所需区域外,因为其坐标认为原点位于窗口的左上方而不是右上方。对于已编译的应用程序,可使用 MapWindowPoints 来获取目标矩形的坐标。对于未编译的应用程序,可尝试关闭对话框的继承标记 - 仅当矩形不是父对话框的子项时,此解决方案才有效!
Win32 中的镜像
为 最大程度减少使应用程序支持镜像所需的重写工作量,系统组件(如 GDI 和 User 模块)已经过了修改,可对 UI 对象的镜像状态进行控制。除了在使用自绘控件和位图时需要考虑几点注意事项以外,几乎不再需要进行任何额外的代码更改。稍后您会看到,新的窗口 API 和扩展样式允许您在进程级、窗口级、对话框资源级甚至设备上下文级开启和关闭镜像功能。但是,在检查系统组件和 API 的功能(这些功能是当您在代码中启用镜像后可用的)之前,本章先简要谈一谈如何以及何时在资源中启用镜像。
在资源中启用镜像
在 Windows 团队对系统的资源加载程序进行了更改之后,您只需编辑一下资源便可镜像应用程序。当您想要镜像其来源已不再存在或已不再使用的旧组件时,这是一种不错的方法;当您想要对应用程序进行主要测试以查看其是否支持镜像时,这种方法也有好处。
可以通过在版本戳记的 FileDescription 值前添加两个从左到右的标记 (LRM)(由 Unicode 码位 U+200E 表示)来镜像应用程序。下图显示了通过此方法镜像的英语版 Notepad.exe 文件。
图 5: 通过添加两个 LRM 来镜像的 Notepad.exe 文件 |
虽 然相对比较简单,但是如果仅镜像特定的窗口或控件,则在资源中启用镜像会显得不够灵活。实际上,对于那些属于受影响进程的窗口,其中的所有窗口和控件都将 被自动镜像。为此,在代码中启用镜像要比在资源中镜像更灵活,因为前者允许您自由决定要镜像的内容。要记住,由镜像进程创建的子进程不会继承镜像样式。
在代码中启用镜像
尽 管可以在本地化进程中激活镜像(使用未编译镜像),但这样做往往会暴露出一些需要进行代码修复的问题。如前所述,您可能想要完全控制何时启用镜像(例如按 进程、按窗口、对话框资源或按设备上下文,如以下部分所示)以及要镜像的元素。这只能通过编码或新的镜像 API 实现。
按进程激活镜像
如果以每进程为基础激活镜像布局,则在激活后创建的所有窗口都将被镜像;但这种激活方法并不会影响现有窗口。这种方法与未编译镜像非常相似,不同之处在于应用程序中的镜像样式可在运行时而非二进制级别开启和关闭,就如同未编译镜像的情况一样。
通过调用 SetProcessDefaultLayout (LAYOUT_RTL ) 将进程的默认方向设置为 RTL。也可以通过调用 SetProcessDefaultLayout(0) 关闭默认镜像。另外,您可以按如下所示查询当前的默认布局方向:
BOOL WINAPI
GetProcessDefaultLayout
(DWORD *pdwDefaultLayout);
在成功返回后,pdwDefaultLayout 包含 LAYOUT_RTL 或 0。
虽然可以在进程级激活镜像,但有时您可能希望在较低级别(如按窗口)激活它。以下部分将说明此方法在何种情况下适用并向您展示要使用的扩展样式。
按窗口激活镜像
在进程级设置完镜像样式后,所有新创建的窗口将自动被镜像。另一种方法是以每个窗口为基础激活镜像(如果应用程序中并非所有窗口都需要镜像)。使用此方法,可通过调用 CreateWindowEx 在窗口创建时进行镜像,并通过使用扩展样式 WS_EX_LAY- OUTRTL 对窗口进行设置。
默认情况下,父窗口的所有子窗口均继承镜像属性。但是需要重申的是,并非所有窗口都需要进行镜像;因此,有时您可能希望阻止子窗口被镜像。要删除子窗口的镜像继承,需要在调用 CreateWindowEx 时指定新扩展样式 WS_EX_NOINHERITLAYOUT。
下 面举个例子对此加以说明,比如当您的窗口托管或导入外部控件(如 Microsoft ActiveX、Java applet、组件对象模型 (COM) 对象等)时,就应当阻止子窗口的镜像。此类控件也存在一些问题,例如其行为难于操控,致使无法轻松地对其施加镜像要求。
以下代码显示了如何在 RTL 与 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);
重要事项: 动态更改镜像样式并不总是实现窗口镜像的最有效方法。许多控件可能无法按预期发挥作用,所以也就无法正确镜像。因此,在窗口创建之初启用窗口镜像仍是比较好的方法。
激活对话框资源的镜像
区 分窗口和对话框(它不接收 WM_CREATE,而是接收 WM_INITDIALOG)十分重要。要激活对话框的镜像,需要在资源编辑器中设置镜像标记并添加扩展样式 WS_EX_LAYOUTRTL 和 WS_EX_NOINHERITLAYOUT。(仅当需要阻止继承发生时才使用第二个样式。)
在运行时切换已镜像和未镜像对话框资源的唯一方法是准备两组对话框资源:一个已镜像的和一个未镜像的。利用 Microsoft Visual Studio 6 资源编辑器中的“对话框属性”属性表,可以设置对话框资源的镜像样式。
图 6: 在 Visual Studio 6 资源编辑器中设置对话框资源的镜像样式 |
按设备上下文激活镜像
在 此之前介绍的镜像方法适用于标准窗口中的所有对象。默认情况下,这些对象以及镜像窗口中的位图和图标都将被镜像。但很明显,有些图形对象(尤其是那些包括 文本内容的)不应被镜像。因此,Microsoft 在支持镜像的平台(再次强调,这包括所有版本的 Windows 2000 和 Windows XP)中提供了多种 GDI 函数。这些函数如下列代码所示:
BOOL WINAPI
SetLayout(HDC hDc, DWORD dwLayout);
DWORD WINAPI
GetLayout(HDC hDc);
默认情况下,镜像窗口的设备上下文会被镜像。不过,您可以通过调用 SetLayout (hdc, 0) 在设备上下文中禁用镜像,也可以通过调用 SetLayout(hdc, LAYOUT_RTL ) 将其激活。如果某些由旧有代码执行的绘图操作被镜像中断,这时可能需要对设备上下文的镜像属性进行控制。
可能会受输出内容的方向性影响的绘图图像会呈现出分隔问题。要防止位图被镜像,请调用 SetLayout ,并在 dwLayout 中设置 LAYOUT_BITMAPORIENTATIONPRESERVED 位:
SetLayout(hdc, LAYOUT_RTL | LAYOUT_BITMAPORIENTATIONPRESERVED);
GetLayout 成功完成后,GetLayout 返回以下值:
-
0(如果设备上下文未被镜像)
-
LAYOUT_RTL (如果设备上下文被镜像)
- LAYOUT_RTL 和 LAYOUT_BITMAP(如果设备上下文被镜像以及如果编程人员不希望设备上下文具有镜像的位图)
另一种防止位图被镜像的方法是在对 BitBlt 和 StretchBlt 的调用中定义 NOMIR-RORBITMAP。
镜像和属性表
启 用镜像时会让您感到比较棘手的情况是处理属性表(以及向导)。可以设想一下这种情况:属性表容器包含多个页面,但其中只有几个页面被本地化为 RTL 语言。各个页面是由对话框模板制作而成的,它们可以在资源中单独镜像。但容器该怎么办呢?应该镜像容器吗?为避免属性表的混淆和不一致的行为,系统在决定 是否创建镜像容器时会采取许多步骤,以下是其中的部分方法:
-
如果第一个页面不包含镜像窗口样式且客户端进程未被镜像,则客户端不会被视为镜像的客户端。当应用程序从不同的二进制文件加载第一个页面时,会发生这种情况。
-
如果第一个页面的主要资源语言不是阿拉伯语或希伯来语,则客户端不会被视为镜像的客户端。当应用程序从另一个二进制文件(可能是针对其他语言本地化的二进制文件)加载第一个页面时,会发生这种情况。
映射坐标
处理镜像时面临的其中一个最大问题可能是屏幕坐标与客户端坐标之间的映射,或者无法执行此操作。例如,开发人员通常使用下列代码在窗口中定位控件:
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);
在此代码中,矩形的左边缘变成镜像窗口的右边缘(反之亦然),致使引发意外结果。解决方案是替换 ScreenToClient 调用,如下列代码所示:
GetWindowRect(hControl, (LPRECT) &rControlRect);
// Gets coordinates of window in screen coordinates.
MapWindowPoints (NULL, hDialog, (LPPOINT) &rControlRect, 2);
此代码之所以可行,是因为已在支持镜像的平台上对 MapWindowPoints API 进行了修改。在进行此修改后,Map-WindowPoints 可以交换要镜像的客户端窗口的左右点坐标(只要这两个点同时被传递给此 API)。
另一种可能在镜像窗口中引发问题的常见做法是使用屏幕坐标的偏移量在客户端窗口中定位对象,而不是使用客户端坐标。例如,为了定位对话框中的某个控件,下列代码使用控件与对话框的左边缘(屏幕坐标)的差值作为客户端坐标的 x 位置:
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);
当对话框窗口具有 LTR 布局且客户端的映射模式为 MM_TEXT 时,这种方法非常管用。客户端坐标的新 x 位置与控件和对话框左边缘屏幕坐标的差值相对应。但在镜像的对话框中,左右角色将颠倒。您可以通过使用 MapWindowPoints 进入客户端坐标来删除先前代码中的假设(即近为左、远为右),如以下代码示例所示:
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);
这些示例有一个共同的主题。您应考虑近和远这样相对比较抽象的概念,而不是左和右这种具体概念。以下是一些有关开发应用程序的具体指导原则,这些应用程序可通过使用自动镜像接口进行镜像:
-
尽量使用客户端坐标而不是屏幕坐标。
-
如有必要,可从屏幕坐标映射到客户端坐标。
-
要映射点,可使用 MapWindowPoints ,而不是使用 ClientToScreen 和 ScreenToClient 。
处理区分方向的图形
区 分方向的图形(如位图和图标)带来了另一个有关镜像的难题。一些区分方向的图形在镜像后可能具有不同的意义。例如,在浏览器的 LTR 布局中,指向左侧的箭头表示返回到前一页;而指向右侧的箭头表示进入下一页。当 RTL 布局的这些箭头被镜像后,所表达的含义会恰恰相反。
图 7: 镜像后含义会发生改变的区分方向箭头示例 |
还有些图形根本就不能镜像(例如法律商标或徽标),因为采用从左向右布局后这些图形将失去其意义。
图 8: 不应该镜像的区分方向图形示例 |
有多种解决方案可用来处理上述这些情况。可以根据实际情况从以下四种解决方案中选择一种。
一 种解决方案是在呈现对区分方向的位图或图标时暂时关闭设备上下文的 RTL 布局;然后在镜像完成后再重新开启。此方法存在的问题是有时您可能无权访问呈现这些图形的设备上下文。(关于此方案的一个示例是当您将图标或位图句柄传递 给要为您显示图像的第三方 DLL 时。)
另一种解决方案是在资源中准备两组不同的图形,一组用于绘制目标是 LTR 方向的情况,另一组用于绘制目标是 RTL 方向的情况。第一组将包含原始的 LTR 图形。第二组将包含第一组图形的副本,其中仅对需要反映 RTL 方向的图形进行了修改。然后您可以根据是在镜像情况下呈现还是在未镜像情况下呈现来选择要加载和呈现的组。换言之,对于未镜像的设备上下文,应加载原始的 未镜像图形组,而对于镜像的设备上下文,则应加载包含镜像和未镜像图形的图形组。(在后一情况中,当镜像图形在镜像设备上下文中呈现时,它们将被再次镜 像,从而还原为您希望的显示方式。)根据项目中包含的区分方向图形的数量,如果许多图形都不应该被镜像,则此方法可能并非是最佳选择,因为复制这些图形会 增加资源的大小。
在第三种解决方案中,如果根据目标本地化语言单独提供一组二进制文件,则可以使您的本地化团队能够使用资源编辑器来镜像这些图形元素。(现在大多数资源编辑器都允许沿 x 和 y 方向翻转图像。)最后一种解决方案是创建资源的镜像副本,此方案非常适合于对要呈现图形的设备上下文不具有直接访问权限的情况。您然后需要传递此镜像副 本,而非原始的未镜像副本。(再次强调,当镜像图形在镜像设备上下文中呈现时,他们将还原为其原始的本来外观。)下列示例代码说明了在位图和图标的句柄被 传递后要如何对二者进行镜像:
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;
// 获取屏幕 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;
}
镜像和图像列表控件
处 理图形的一个好方法是使用图像列表控件,因为这些控件可以提供很大的灵活性。图像列表控件还可以用来管理其他控件(如树视图和列表视图控件)的图形。但是 这可能会引发一个潜在的问题。对于区分方向的位图,如果您所使用的图像列表的内容将会呈现在镜像设备上下文中,则图像列表将无权直接访问图像列表设备上下 文。在 Windows XP 中,您可以使用图像列表标记 ILC_MIRROR 和 ILC_PERITEMMIRROR 来启用镜像。如果您的代码需要在支持镜像的 Windows 2000、Windows 98 或 Windows Me 上运行,则可以使用前述代码示例来镜像区分方向的位图或图标,然后再将其添加到列表中。但请注意,如果将这些图像逐一添加到列表中,先前的代码将允许您镜 像此类位图和图像。如果一次向列表添加多个图像,则可对先前的代码进行调整,以镜像图像带内要添加到列表中的每个元素。例如,您可以修改 CreateMirroredBitmap() 以代替以下行:
BitBlt(hdcMem2, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem1, 0, 0, SRCCOPY);
您可以使用以下代码,其中 Width 是图像带内要添加到图像列表中的各个图像的宽度:
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);
}
指定各个单独图像的宽度可在 Windows 2000、Windows 98 或 Windows Me 中得到同样的效果,就像您在 Windows XP 中指定了 ILC_PERITEMMIRROR 一样。
镜像 .NET Framework
与 Web 内容一样,DIR 属性可用于镜像 Web 窗体中的文本。RightToLeft 属性(来自 Control 类)对于为 Windows 窗体提供 RTL 支持非常重要,而 MessageBox 类则支持消息框的 RTL 和镜像。在后续部分中,您将了解如何利用此项支持。
镜像后的 Web 窗体
在 Microsoft Visual Studio .NET 中,您可以通过使用 Microsoft Visual Basic .NET 或 Microsoft Visual C# .NET 来创建 ASP.NET 应用程序。您应遵循的准则与您刚刚看到的用于镜像 Web 内容的准则基本相同。设计阿拉伯语 Web 窗体页面时,使文本流从右向左的最好方法是使用 DIR 属性。和 Web 内容一样,此属性通常放在 <HTML> 标记或 <BODY> 标记中。页面中的控件和 HTML 元素然后会继承指定的方向。
您可以在 DOCUMENT 对象级设置 DIR 属性。窗体中的所有控件都将继承相同的设置。但是,DIR 属性可单独与其他标记(如 <TABLE>)一起使用并可用在 Web 窗体控件中(如以下示例所示),以允许从右向左显示项目:
<TABLE dir="rtl" ...>
<asp:TextBox dir="rtl" ...>
镜像后的 Windows 窗体
在 .NET Framework 中创建的 Windows 应用程序允许您访问操作系统服务并充分利用用户的计算环境提供的其他优点。此外您也可以使用 ADO.NET 访问数据。另外,GDI+ 还允许您在窗体中进行高级绘图和绘画工作。最后,您的 Windows 应用程序可对 XML Web 服务所提供的方法进行调用。
Windows 窗体可以是标准窗口、多文档界面 (MDI) 窗口、对话框或用于图形例程的显示画面。定义窗体 UI 的最简单方法是将控件放在其表面上。窗体是指具有多个功能的对象。这些对象提供定义窗体外观的属性、定义其行为的方法以及定义它们与用户交互的事件。 Windows 窗体是一组控件,因为它们是从 Control 类继承而来的。该类具有用于 RTL 支持的一个重要属性(RightToLeft 属性),此属性指定文本是否从右向左显示。与所有其他控件一样,窗体本身也支持 RightToLeft 属性。
表 1:RightToLeft 属性的值 | |
值 | 说明 |
Inherit | 文本的呈现方向是从父控件继承而来的。 |
No | 文本从左向右呈现。此为默认值。 |
Yes | 文本从右向左呈现。 |
设计时间会怎样呢?Windows 窗体设计器会考虑 RightToLeft 属性对控件的影响。例如,如果将窗体的 RightToLeft 属性设置为 Yes ,此属性会自动更新设计器中的窗体显示,以使窗体的标题在运行时以右对齐的方式出现在标题栏中。当 RightToLeft 属性值被设置为 RightToLeft.Yes 时,水平对齐的控件元素将被颠倒,但元素的对齐值保持不变。例如,在 TextAlign 属性值为 HorizontalAlignment.Left 的 TextBox 控件中,文本以右对齐方式显示,但属性值仍为 HorizontalAlignment.Left 。不过,如果 RightToLeft 属性值被设置为 RightToLeft.Yes ,且 TextAlign 属性被设置为 HorizontalAlignment.Right ,文本将以左对齐方式显示。
Windows 窗体并不像支持 RightToLeft 属性一样直接支持镜像。相反,您要根据需要创建自己的显示控件。例如,您可以开发自己的 RTLTreeview 控件(可以从 Treeview 控件继承而来)并更改其要镜像的样式。
有 时,您可能希望创建带有设置和属性(如水印或某个特定的控件布局)的基本窗体,并可以在今后的项目中重新使用它们。每个新窗体都可以包含对原始窗体模板的 修改。此解决方案使用的是 Windows 窗体继承,这是一个由 .NET Framework 类库提供的功能。例如,使用继承功能,您可以通过从 System.Windows.Forms.Form 类继承并更改 System.-Windows.Forms.Form 的窗体样式来构建自己的镜像窗体。
消息框
顾名思义,MessageBox 类代表消息框;该类的 Show 方法会显示一个消息框,其中可以包含用来通知和指示用户的文本、按钮和符号等信息。MessageBox 类完全支持 RTL 阅读顺序和镜像。MessageBox 类的 Show 方法使用在 MessageBoxOptions 枚举中定义的常量作为参数。这些常量包括 RtlReading 和 RightAlign ,它们都可以启用消息框来显示双向文本。下列代码将说明如何显示阿拉伯语消息框:
[Visual Basic]
MessageBox.Show([put here your Arabic text],
MessageBoxButtons.OK,
MessageBoxIcon.Question,
MessageBoxDefaultButton.Button1,
MessageBoxOptions.RtlReading Or MessageBoxOptions.RightAlign)
RtlReading 负责确保消息框中的文本按 RTL 阅读顺序显示并应用镜像。RightAlign 只是使文本以右对齐方式显示,而不对消息框中的文本强制实施 RTL 阅读顺序或镜像。
网页中的镜像
与 Win32 应用程序相比,在 Web 内容中应用从右向左布局要简单得多。Microsoft Internet Explorer 本身就支持以 RTL 方式呈现 HTML 内容。在浏览器中切换 LTR 与 RTL 显示就像在 HTML 元素中添加一个属性(方向 (DIR))一样简单。<HTML DIR=RTL> 将使整个页面从右向左显示。这意味着页面在呈现时好像是以右上角为原点,沿 x- 轴从右向左延伸,如前文所述。将 DIR 属性设置为 RTL 或 LTR 后,经过精心组织的页面将正确呈现出来。(如果未使用 DIR 属性,布局将默认为 LTR。)DIR 属性放在 <HTML> 标记中与放在 <BODY> 标记中存在一些重要的差异。<BODY> 标记中的 DIR 将影响文档中的所有可见元素,但不会影响环境属性(固定元素)。对于 ActiveX 控件,放在 HTML 标记中的 DIR 会将文档的环境属性设置为 AMBIENT_RIGHTTOLEFT。此外,如果在 HTML 标记中设置 RTL 阅读顺序,则可在运行时更改文档的阅读顺序(通过编写脚本来设置文档对象的 DIR 属性)。
要体验 RTL 呈现,请尝试在 Internet Explorer 中运行以下页面:
<HTML DIR=RTL>
<HEAD>
<TITLE>Playing with document.dir</TITLE>
<SCRIPT LANGUAGE=javascript>
<!--
function myfunction()
{
if (document.dir = "rtl")
document.dir = "ltr";
else document.dir = "rtl";
}
//-->
</SCRIPT></HEAD>
<BODY>
<P>This is some text...</P>
<P>
<INPUT type="button" value="Switch" οnclick=myfunction()>
</P>
<Table border=1>
<TBODY>
<TR>
<TD width=100>Cell 1</TD>
<TD width=100>Cell 2</TD>
</TR>
<TR>
<TD width=100>Cell 1</TD>
<TD width=100>Cell 2</TD>
</TR>
</TBODY>
</Table>
</BODY>
</HTML>
也可以通过右键单击页面内容,然后在出现的上下文相关菜单中选择“编码”子项目(例如从右向左文档)来先行修改 HTML 文件的 DIR 属性。
可通过以下方式来使用 DIR 属性:
- 如果在 <HTML> 标记中方向被指定为 RTL,则系统将使用正确的扩展样式对页面进行设置,以使其在双向系统中按 RTL 显示页面,因此竖直滚动条显示在左侧。
- 如果在 <BODY> 标记中方向被指定为 RTL,则框架和标题将不继承 RTL 方向。
下列 HTML 代码将 DIR 属性设置为 RTL:
<HTML dir="rtl">
<!-- Or -->
<body dir="rtl" ...>
尽 管镜像 Web 内容相对简单一些(与镜像 Win32 应用程序相比),但对 HTML 元素应用复杂定位也可能会引发许多问题,在针对 RTL 语言设计 Web 页面时对此要加以注意。其中一个应该考虑的注意事项是如何正确处理具有方向性的图像。还应注意与强制左对齐文本和 CSS 绝对定位有关的一些问题。最后,您应利用表格提供的功能来呈现 RTL 文本。
具有方向性的图像
Internet Explorer 的呈现引擎 (Mshtml.dll) 可用来处理 Web 内容的 RTL 布局。应用 DIR=RTL 属性可解决许多问题。它可以将文本的阅读顺序设置为 RTL、使文本靠右对齐,还可以镜像 Web 内容(如动态元素、表格和单元格)。但是,应用 DIR=RTL 属性不会改变固定元素(如图像)的方向。这主要是为了防止那些不应翻转的图像发生翻转。相反,那些具有方向性的图像则需要进行翻转,并应与文本和内容遵循 同样的布局。
例如,图 8-11 显示了在 LTR 文档中带有前置箭头的示例文本。文本被设置为 RTL 后,箭头的原始方向保持不变,但图像位置应颠倒。
图 8: LTR 文档中文本左侧的箭头在 RTL 文档中需要颠倒 |
要 想提供沿左右任一方向都能正确呈现的单一代码段,仅为双向版本的站点提供替代图像是不够的。还必须将脚本加入到 Web 页面中,以根据使用的呈现模式动态更改图像。要使此更改生效,可以对站点上的图像使用 CSS 筛选器。在 Microsoft Internet Explorer 4 及更高版本中都已安装了筛选器(称为 "flipH"),它可以水平反向任何元素,使元素显示为一个镜像图像。以下是用来确定图像方向的内嵌脚本:
<Script Language=VBScript>
Option Explicit
Dim strFilter
If document.dir = "RTL" Then
strFilter = "filter:flipH;"
Else
strFilter = ""
End Ifdocument.write "<IMG SRC='images/arrow.gif' Style='" &
_strFilter & "'>"
</Script>
另外,在 HTML 文件中,代码会如下所示:
<IMG Style=filter:flipH?SRC=C'images/arrow.gif'>
强制左对齐文本
默 认情况下,在基于拉丁语的脚本中,所有表格、单元格和文本都靠左对齐。显式添加 ALIGN=LEFT 标记将覆盖 DIR 属性,会使 RTL 文本无法正确地设置为右对齐。因此在本例中,ALIGN 参数是多余的。由于在任何情况下 LTR 的显示都默认为左对齐,而 RTL 默认为右对齐(因为已应用了 DIR=RTL 属性),因此彻底删除 ALIGN 属性可使页面按预期方式显示。即对于 LTR 语言它将左对齐显示;对于 RTL 语言它将右对齐显示。一般来讲,简单就是美。如果在代码中只加入得到预期结果所必需的那些元素和属性,就可以避免在以后出现不必要的麻烦。
绝对 CSS 定位
绝 对 CSS 定位给双向 Web 站点带来了特殊的问题。图 8-12 显示的是一个翻译为阿拉伯语的对话框和 RTL 属性的应用。此对话框中的按钮是通过绝对 CSS 定位放置的。按钮不但与文本重叠,而且其顺序也是固定的;Yes 按钮固定在左侧,No 按钮固定在其右侧。
图 9: 按钮(使用绝对 CSS 定位放置的)与文本重叠且顺序固定的对话框。 |
以下是绝对定位时使用的代码:
<BUTTON ACCESSKEY="Y"
style="font-family:Arabic Transparent, arial; font-size:10pt; position:absolute;
width:58pt; height:19pt; left:146pt; top:230pt"
οnclick="doOK()" >
<u>Y</u>es</BUTTON>
在 本例中,绝对定位是多余的。假如撇开按钮的 position 属性,按钮本身会自然对齐。从按钮样式中删除 position、left 和 top 属性(在上一代码示例中以粗体显示的属性)会使按钮在文本下方更自然地对齐。另外,允许 Internet Explorer 来控制文本流可颠倒按钮位置,这就是在本例中希望得到的结果。
表格提供的可逆性
从 LTR 变为 RTL 阅读顺序时,表格是确保内容能够正确颠倒的最好也是最可靠的方式(假定您未指定表格单元格对齐方式)。例如,让我们看一看下列 HTML 代码:
<Table width=100% border=0>
<TR>
<TD bgcolor="#C0F000"><NOBR>Item 1 </NOBR></TD>
<TD width=30 bgcolor="#C0F000"></TD>
<TD width=30 bgcolor="#C0F000"></TD>
<TD bgcolor="#C0F000"><NOBR>Item 3 </NOBR></TD>
<TD width=700 bgcolor="#C0F000"></TD>
<TD bgcolor="#C0F000"><NOBR>Item 4 </NOBR></TD>
</TR>
</Table>
总而言之,下面是根据先前部分中的信息提取出来的一些有关站点设计的一般规则,可以帮助简化翻译和文本颠倒的流程:
- 避免不必要地指定 ALIGN=LEFT。
- 避免 CSS 绝对定位。
- 使用表格来提供稳定的可逆性。