You are here: Home
Text Style (Fonts), Image, Interface Layout Solution under High Resolution. PDF Print
Written by steven.liu   
Monday, 19 June 2006

  为什么要保证我们的软件产品或应用程序中的文本、图像和字体、布局等问题呢,因为我们用户的终端显示设备通常型号和设置各异,如最近出现的16x9,低于8”的等最新款式移动手提电脑,我们的应用程序和软件产品通常会在这样的终端变得面目全非,而这显然给用户的使用带来了严重的问题,直接导致的问题如:操作易用性Usability、功能可接近性Accessibility、文本可读性Readability 等,而这样的问题并非不可逾越,要解决如何让我们的应用程序在高分辨率的显示下仍然保持正常可视,重点需要解决四个方面的问题文本和字体、图像(图形、图标和鼠标指针)、版面设置和重绘等。

  所有的应用程序都可以工作在高分辨率下显示吗?答案当然是否定的。现在比较标准的计算机显示器都已经可以支持显示大概96像素点/英寸的分辨率了,而且越来越多的应用程序都可以运行在这种分辨率下,但是却仍然面临分辨率日益增长的带来的危险。现在,我们可以轻松的买到一台133-DPI显示分辨率的笔记本电脑,甚至还有170DPI的,也许几年以后200-DPI的显示分辨率已经随处可见了,著名的工业杂志DisplaySearch曾经预言在2002年底有40%的膝上电脑已经超过100-DPI的屏幕分辨率了,而且这个数字还在增长。

 

Image
  例图 1.各种常见分辨率下字体外观

  现在大多数的应用程序要想显示正常都依赖于分辨率,我们有些应用程序如果没有高分辨率的支持将会变得非常丑陋并且导致用户易用性降低,与此同时越来越多的用户使用了大字体。但是遗憾的是当分辨率在130-DPI和200-DPI的时候是不成比例的,在96-DPI下的同一个应用程序在这种分辨率下会变得无法使用,有的时候这些应用程序的字体或控件会一律变得很小,但是更多的情况是一部分界面元素的尺寸正确的(例如,应用程序使用了缺省的字体,那么将会在这个基础上比原来大一些)而另外一部分不正确,如下图所示:
 

Image
  图 2. 改变分辨率带来的影响

  由此可见,增强和改善我们应用程序在高分辨率下的显示支持是非常有必要的,那么重要的标准应该是:图片看起来更好,文本也应该看起来更清晰。比如文本在200-DPI分辨率显示器上清晰的像激光打印机输出的一样(因为计算机显示有更多的颜色像素和灰度缩放支持,200-DPI的显示器的质量相当于600-DPI的打印机)所以PDA和Smartphone的厂商相对于纸介更看重高分辨率下的显示。
开发一个适应多分辨率的应用程序不是很简单,尤其对于一个已经成形的应用程序和系统来说,动静可能不小,但是它的好处是可以使我们不必再假设分辨率的各种情况,避免不能缩放带来的质量下降(比如说位图和位图字体),而且开发支持高分辨率的应用程序有时候会觉得有些单调和乏味,但是如果我们的产品或应用程序是为了服务于特定人群的(比如说视力不好、和需要长时间工作的人以及视弱人群),那么我们的工作就会变得非常有必要(在高对比度下和使用扩展大字体的情况都和高分辨率有关)。

 

  系统韵律的利用

 

  Windows平台本身提供了帮助解决用户系统高分辨率问题的解决途径,我们可以通过一个小函数GetDeviceCaps()先获得当前的显示分辨率,然后通过GetSystemMetrics()这个系统韵律函数和读取系统信息和参数的SystemParametersInfo()函数提供的方法来改变windows中的图形以及控件元素、和字体的尺寸,从一个3d的边框效果乃至到一个小图标的尺寸,都可以随心所欲的改变。
 

Image 

  大概原理是首先利用GetDeviceCaps这个函数获得当前分辨下的X、和Y轴的数值作为基准;然后再确定要缩放到多少。

 

// 如何机遇用户定义改变不同的分辨率大小,自动进行计算
Image


关键问题


  在设计高分辨率的应用程序过程中,我们要特别注意四个重要的方面:文本和字体、图像(图形、图标和鼠标指针),版面设置以及重绘。

 

文本和字体
  

  这里有两种字体:位图(光栅)字体和TrueType字体,而我们要想实现高分辨率的应用程序就只能使用Truetype字体,因为位图(光栅)字体只能在96-DPI的屏幕分辨率下正常,而且不能够缩放,Windows已经支持TrueType字体很长时间了,所以找到一个很好的TrueType字体并且定义到我们的应用系统中并不是什么大的问题,另外一个原因只能使用Truetype字体,因为一些最新的技术,比如说GDI+,但是它只支持Truetype字体的操作。
 
Image

  缺省的字体可以通过windows句柄(HWNDs)和得到,而图形设备(HDCs)获得的是位图(光栅)字体,所以有时候在改变字体的时候,不管缺省字体不是HWNDs和HDC字体,只要它是TrueType字体,我们就可以改变它:
 

Image

HFONT font = (HFONT) GetStockObject (DEFAULT_GUI_FONT);
SendMessage (hwnd, WM_SETFONT, (WPARAM) font, 0);
SelectObject (hdc, font);

  当我们在窗口上创建字体的时候,可以使用像素指定字体尺寸,然后调整分辨率。
 
//下面的函数可帮助你来调用系统韵律
void DATA::UpdateSysColors() 
{
 XTPColorManager()->RefreshColors(TRUE);
 // Initialize standard color for windows components.
 clr3DFace                   = GetSystemColor( COLOR_3DFACE );
 clr3DShadow     = GetSystemColor( COLOR_3DSHADOW );
 clr3DDkShadow    = GetSystemColor( COLOR_3DDKSHADOW );
 clr3DHilight    = GetSystemColor( COLOR_3DHILIGHT );
 clrBtnText     = GetSystemColor( COLOR_BTNTEXT );
 clrGrayText     = GetSystemColor( COLOR_GRAYTEXT );
 clrHighlight    = GetSystemColor( COLOR_HIGHLIGHT );
 clrHighlightText   = GetSystemColor( COLOR_HIGHLIGHTTEXT );
 clrMenu      = GetSystemColor( COLOR_MENU );
 clrMenuText     = GetSystemColor( COLOR_MENUTEXT );
 clrWindow     = GetSystemColor( COLOR_WINDOW );
 clrWindowFrame    = GetSystemColor( COLOR_WINDOWFRAME );
 clrWindowText    = GetSystemColor( COLOR_WINDOWTEXT );
 clrActiveCaption      = GetSystemColor( COLOR_ACTIVECAPTION );
 clrInActiveCaption          = GetSystemColor( COLOR_INACTIVECAPTION );
 clrGradActiveCapt           = GetSystemColor( COLOR_GRADIENTACTIVECAPTION );
 clrGradInActiveCapt         = GetSystemColor( COLOR_GRADIENTINACTIVECAPTION );
 clrActiveCaptText           = GetSystemColor( COLOR_CAPTIONTEXT );
 clrInactiveCaptText         = GetSystemColor( COLOR_INACTIVECAPTIONTEXT );

 // For some strange reason Windows 2000/NT does not return
 // the expected RGB value for COLOR_3DLIGHT...
 clr3DLight     = CXTColorRef(clr3DFace) + 15;

 // Initialize special colors for XP style interfaces.
 clrXPBarFace    = GetSystemColor( XPCOLOR_TOOLBAR_FACE );
 clrXPHighlight    = GetSystemColor( XPCOLOR_HIGHLIGHT );
 clrXPHighlightBorder  = GetSystemColor( XPCOLOR_HIGHLIGHT_BORDER );
 clrXPHighlightPushed  = GetSystemColor( XPCOLOR_HIGHLIGHT_PUSHED );
 clrXPIconShadow    = GetSystemColor( XPCOLOR_ICONSHADDOW );
 clrXPGrayText    = GetSystemColor( XPCOLOR_GRAYTEXT );
 clrXPHighlightChecked  = GetSystemColor( XPCOLOR_HIGHLIGHT_CHECKED );
 clrXPHighlightCheckedBorder = GetSystemColor( XPCOLOR_HIGHLIGHT_CHECKED_BORDER );
 clrXPGripper    = GetSystemColor( XPCOLOR_TOOLBAR_GRIPPER );
 clrXPSeparator    = GetSystemColor( XPCOLOR_SEPARATOR );
 clrXPDisabled    = GetSystemColor( XPCOLOR_DISABLED );
 clrXPMenuTextBack   = GetSystemColor( XPCOLOR_MENUBAR_FACE );
 clrXPMenuExpanded           = GetSystemColor( XPCOLOR_MENUBAR_EXPANDED );
 clrXPMenuBorder    = GetSystemColor( XPCOLOR_MENUBAR_BORDER );
 clrXPMenuText    = GetSystemColor( XPCOLOR_MENUBAR_TEXT );
 clrXPHighlightText   = GetSystemColor( XPCOLOR_HIGHLIGHT_TEXT );
 clrXPBarText    = GetSystemColor( XPCOLOR_TOOLBAR_TEXT );
 clrXPBarTextPushed   = GetSystemColor( XPCOLOR_PUSHED_TEXT );
 clrXPTabInactiveBack  = GetSystemColor( XPCOLOR_TAB_INACTIVE_BACK );
 clrXPTabInactiveText  = GetSystemColor( XPCOLOR_TAB_INACTIVE_TEXT );
}

void DATA::UpdateSysMetrics()
{
 cxSmIcon   = ::GetSystemMetrics(SM_CXSMICON);
 cySmIcon   = ::GetSystemMetrics(SM_CYSMICON);
 cxSize    = 18/*::GetSystemMetrics(SM_CXSIZE)*/;
 cySize     = 18/*::GetSystemMetrics(SM_CYSIZE)*/;
 cxHThumb   = ::GetSystemMetrics(SM_CXHTHUMB);
 cyVThumb   = ::GetSystemMetrics(SM_CYVTHUMB);
 cyMenuItem = ::GetSystemMetrics(SM_CYMENU);
}

LOGFONT lf;
Memset (&lf, 0, sizeof (lf));
lf.lfHeight = SCALEY (13);
HFONT font = CreateFontIndirect (&lf);

 

  或者可以使用Windows API提供的选择文字通用对话框,允许使用更准确的像素点来指定字体尺寸,然后经过一些算法后转化字体尺寸为像素,可以指定只使用TrueType字体来显示。
 
//下面的程式可以帮助您来处理不同分辨率下字体的可读性
_XTP_EXT_CLASS void  AFXAPI _xtAfxChangeWindowFont(CWnd* pWnd, CFont* pFont)
{
 CRect windowRect;
 
 // grab old and new text metrics
 TEXTMETRIC tmOld, tmNew;
 CDC * pDC = pWnd->GetDC();
 CFont * pSavedFont = pDC->SelectObject(pWnd->GetFont());
 pDC->GetTextMetrics(&tmOld);
 pDC->SelectObject(pFont);
 pDC->GetTextMetrics(&tmNew);
 pDC->SelectObject(pSavedFont);
 pWnd->ReleaseDC(pDC);
 
 long oldHeight = tmOld.tmHeight+tmOld.tmExternalLeading;
 long newHeight = tmNew.tmHeight+tmNew.tmExternalLeading;
 
 // calculate new dialog window rectangle
 CRect clientRect, newClientRect, newWindowRect;
 
 pWnd->GetWindowRect(windowRect);
 pWnd->GetClientRect(clientRect);
 long xDiff = windowRect.Width() - clientRect.Width();
 long yDiff = windowRect.Height() - clientRect.Height();
 
 newClientRect.left = newClientRect.top = 0;
 newClientRect.right = clientRect.right * tmNew.tmAveCharWidth / tmOld.tmAveCharWidth;
 newClientRect.bottom = clientRect.bottom * newHeight / oldHeight;
 
 newWindowRect.left = windowRect.left -
  (newClientRect.right - clientRect.right)/2;
 newWindowRect.top = windowRect.top -
  (newClientRect.bottom - clientRect.bottom)/2;
 newWindowRect.right = newWindowRect.left + newClientRect.right + xDiff;
 newWindowRect.bottom = newWindowRect.top + newClientRect.bottom + yDiff;
 
 pWnd->MoveWindow(newWindowRect);
 
 pWnd->SetFont(pFont);
 
 // iterate through and move all child windows and change their font.
 CWnd* pChildWnd = pWnd->GetWindow(GW_CHILD);
 
 while (pChildWnd)
 {
  pChildWnd->SetFont(pFont);
  pChildWnd->GetWindowRect(windowRect);
  
  CString strClass;
  ::GetClassName(pChildWnd->m_hWnd, strClass.GetBufferSetLength(32), 31);
  strClass.MakeUpper();
  if(strClass==_T("COMBOBOX"))
  {
   CRect rect;
   pChildWnd->SendMessage(CB_GETDROPPEDCONTROLRECT,0,(LPARAM) &rect);
   windowRect.right = rect.right;
   windowRect.bottom = rect.bottom;
  }
  
  pWnd->ScreenToClient(windowRect);
  windowRect.left = windowRect.left * tmNew.tmAveCharWidth / tmOld.tmAveCharWidth;
  windowRect.right = windowRect.right * tmNew.tmAveCharWidth / tmOld.tmAveCharWidth;
  windowRect.top = windowRect.top * newHeight / oldHeight;
  windowRect.bottom = windowRect.bottom * newHeight / oldHeight;
  pChildWnd->MoveWindow(windowRect);
  
  pChildWnd = pChildWnd->GetWindow(GW_HWNDNEXT);
 }
}

  最好的方法常常是在各种高分辨率下指定一个尺寸和一个区域的尺寸并且使用字体尺寸作为度量比例尺来指定这个页面中的其它元素,比如说,可以设定按钮之间的间距为缺省字体的高度的多少,使用GetTextMetrics()这个函数可以重新设定一个字体的高度。

 

  最好不要使用TEXTMETRIC提供的tmAveCharWidth方法,因为它只可以处理英文字母,除此以外我们还可以使用GetTextExtent()的这个方法来确认所关心的字符串的尺寸,我们可以用GetTextExtentPoint32()绘制一个环绕字符串的矩形,示例如下:
 
Image

SIZE size;
GetTextExtentPoint32 (hdc, string, strlen (string), &size);

Int paddingX = SCALEX (8);
Int paddingY = SCALEX (8);
Rectangle (hdc, x - paddingX, y - paddingY, x + size.cx
+ paddingX, y + size.cy + paddingY);
TextOut (hdc, x, y, string, strlen (string));

  最后,我们意识到尽管TrueType字体缩放精细,但是他们不是线性缩放,也就是说在DPI增加10%以后字符串的长度不能正确的增加10%,(使用GDI+就没有这个问题),因为一些特定的字母只能在几个尺寸上看起来不错,而TrueType却可以自动选择一个近似的尺寸正确显示,这是使用GetTextExtent这个函数的原因。


图像

 

  图像是以光栅为基础的文件,(比如说BMP、JPEG和GIF),如图标和鼠标指针等。图像相对于字体来说要更难处理一些,因为图像是由离散的像素组成,如果当前显示分辨率和图像设计时的分辨率不一致,那么图像就需要根据正确的物理尺寸缩放,我们可以通过StretchBlt()函数缩放一个位图而不是BitBlt(),当图像被Load时它可以轻易的帮助应用系统缩放图像,而且更准确些。

 

BITMAP info;
GetObject (bitmap, sizeof (info), (PTSTR) &info);

HDC hdcBitmap = CreateCompatibleDC (target);
SelectObject (hdcBitmap, bitmap);

StretchBlt (target, x, y,
SCALEX (info.bmWidth), SCALEY (info.bmHeight),
hdcBitmap, 0, 0, info.bmWidth, info.bmHeight, SRCCOPY);
DeleteDC (hdcBitmap);

 

  当然,缩放肯定会衰减图像的质量,尤其是当从一个小的分辨率放大到一个大分辨率的时候;而且缩小也有一些问题,缺省是拉伸模式COLORONCOLOR,它运算虽然快速,但是会丢失一些细节,HALFTONE方式拉伸运算速度很慢,但是质量会更高,(GDI+提供了一个扩展的选项)。
 
 Image
SetStretchBltMode (hdc, HALFTONE);

Image 

  需要特别指出的是ICO和.CUR文件是可以在一个单独的文件中存储多个图片的文件,那么我们就需要在多种分辨率下设计不同的图片,建议使用GetSystemMetrics()来解决,那么如果必须缩放的话,系统将会替我们选择合适的图片。但是BMP或其它很多种文件合适是不支持在一个单独的文件中存储多个文件的,但是我们可以通过判断来确定在Load的时候选择建立哪个文件。

 

If (GetDeviceCaps (hdc, LOGPIXELSX) < 130) //当小于130-DPI的时候
   Bitmap = LoadBitmap (hInstance, (char*) IDB_BITMAP1); //Bitmap1
Else
   Bitmap = LoadBitmap (hInstance, (char*) IDB_BITMAP2); //Bitmap2

 

Image
  对于特殊的ICON和鼠标指针,目前我们采用的是标准的16x16 pixel 和 32x32 pixel大小,高分辨率的应用程序最大可以支持到64x64 pixel,当然这是在不改动注册表的前提下。理想的情况是在每个主要的分辨率下都有相应的大图标和小图标。

 

Image 

   如果使用Comctl2.0提供的图片序列(HIMAGELIST),需要在放置到序列里面之前把它们缩放到合适大小,一个更好的选择是使用最新的comctl6.0,但是这只在Windows XP下支持,最新的控件支持会自动缩放它们在不同的分辨率下(halftone StretchBlt)。 

 

界面布局

 

Image

  版面是另一个会导致在高分辨率下出现问题的环节,很多对话框都使用对话单位(DLU)作为规格设置单位,因为它可以随着系统分辨率而自动运算缩放变化;但是一些自定义的界面上常常需要被我们重新手动转换并且设定,因为有很多界面或对话框理论上工作在像素下,我们可以重新规划界面和对话框的设定,比如说完全使用对话单位,尽管我们也可以调用SetWindowPos()提供的方法,或者可以抛弃关于DPI的假设继续工作,使用system metrics来自动处理这些字体和控件之间的关联。

重绘

 

  重绘也是一样,有些时候我们需要绘制屏幕或控件,需要计算不同的分辨率。如果我们开发了一个自定义控件,那么它或许可以工作在像素环境下,但是我们需要使用system metrics来避免分辨率的问题,如果我们在绘制一个复杂的图形可以使用SetMapMode来使用图形缩放引擎。
 
Image

图表 2 GK-Star中的Owner-Draw样式

GDI+

 

  GDI+是微软下一代的2D图形解决方案,是对GDI的增强和延续,GDI+提供了在高分辨率的解决方案,比如说线性的文本缩放,和平滑图片、缩放功能都被很好的改善,GDI+提供了许多针对速度和质量等方面图片缩放的运算规则,但是相比较GDI StretchBlt的而言,对于小图像InterpolationModeBilinear更快速质量也不错,对于会有一些质量问题,所以使用GDI+提供的InterpolationModeHighQualityBicubic是个不错的选择。
 

Image
  图片和图形的真实感增强 Office XP采用改进的图形系统 (GDI+),使用该图形系统,图形和艺术字将具有更平滑的轮廓以及可调整的用真实颜色调和的透明度级别。在调整图片大小时,图片的显示将更加清晰。
 


  另外GDI+ 围绕分辨率的问题还提供了(Image::GetPhysicalDimension 和 Bitmap::SetResolution, for example). 这样的函数,可以使用这些信息来恰当的缩放图片,或者可以让GDI+去作这些,如果在调用Graphics::DrawImage的时候没有指定一个高度和宽度,GDI+也会根据屏幕分辨率来计算图像分辨率。

 

如何测试高分辨率下的应用程序是否有问题?

改变下面系统对于分辨率的设置:
1. 在windows上点击右键.
2. 点击“属性”.
3. 打开“设置”Tab标签然后点击“高级”.
4. 在“常规”标签, 在字体尺寸框中改变的系统 DPI.
5. 重启系统.

在检测应用程序外观的时候要特别注意以下检测要点:
• 文本和给予的空间(控件或容器)不匹配。
• 文本和控件重叠或不恰当的间隔。
• 文本和图片太小(不可用或不可视)。
• 图像尺寸是恰当的,但是因为缩放导致质量非常低。
• 线条太细不容易看到(因为在200dpi下,一个一个像素的线条几乎不可见)
最好在一些不同的DPI下测试的应用程序,因为一些显示器厂商在精确度方面会有些许的不同,在96、120、135、170、200下都进行一些测试。

 

如您喜欢我们的文章并希望转载,请阅读我们的转载注意事项

 

 
< Prev   Next >
 
ClickHeat : track clicks