WINCE屏幕绘图

更新时间:2024-04-25 00:28:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

第2章 屏幕绘图

概述

在第1章,示例程序HelloCE完成一项工作:在屏幕上显示一行文字。显示这行文字只需要调用一次DrawText即可,因为Windows CE代为处理了很多细节,例如字体、字体颜色、文本行在屏幕上的位置等等。借助图形用户接口的力量,应用程序不只能在屏幕上输出本文行,还能做更多的事情。应用程序可以绘制出非常精细的显示外观。

纵观微软Windows操作系统,用于绘制屏幕的函数数量发生了巨大的扩展。Windows每个后续的版本里,都增加了许多函数以扩展程序员可以使用的工具集。虽然新函数增加了,但旧函数依然被保留,这样即使有旧函数被新函数取代,旧程序依然可以继续运行在新版本的Windows上。这种函数不断堆积,旧函数被保留以向后兼容的策略,在最初的Windows CE版本里却被废弃了。因为需要制作更小版本的Windows,CE团队苦览Win32 API,并只复制适合Windwos CE目标市场的应用程序绝对需要的API。

这种精简对Win32 API影响最大的领域之一就是图形函数。到不是您会缺乏用于工作的函数,只是在Win32 API的冗余度方面,对图形函数做了教大的精简。程序员面临的新挑战之一就是不同的Windows CE平台支持略微不同的API集合。Windows CE图形功能与桌面系统不同之处,其中之一就是Windows CE不支持不同的映射模式,而这在其他Windows系统里是支持的。Windows CE设备环境始终设置为MM_TEXT映射模式。坐标转化在Windows CE下也不支持。虽然这些特性在一些类型的应用中很有用,但在小型便携式设备的Windows CE环境里,这些需求并不突出。所以当你阅读本章里使用的函数和技术时,请记住其中一些可能不能在所有平台上被支持。通过GetDeviceCaps函数,程序可以判断系统支持什么函数。GetDeviceCaps返回当前图形设备的实际能力。贯穿本章始末, 当判定在目标设备上什么函数被支持时,我会谈到GetDeviceCaps函数的。

像书中第一部分里其它章节一样,本章回顾Windows CE所支持的绘画功能。需要记住的最重要的事情之一是虽然Windows CE不支持全部Win32 图形API,但它的快速发展使它可以支持一些Win32里最新的函数--其中一些非常新,可能您对它们都不熟悉。本章将为您展示您可以使用的函数以及如何在这个有一些函数不被Windows CE支持的领域里工作。

绘图基础

综观历史,Windows被细分成三个主要部分:核心层,处理进程和管理内存;用户层,处理窗口接口和控件;图形设备接口(GDI)负责底层绘制。在Windows CE里,用户层和GDI层合成为图形窗口及事件处理器,即GWE。你可能有时会听Windows CE程序员谈起GWE。GWE并不是什么新事务,只是标准Windows部件的不同包装而已。在本书里,我通常将GWE的图形部分依然称为GDI,以保持和标准Windows编程术语的一致性。

不论你是为Windows CE、2000、还是XP编写程序,需要做的不仅仅是处理WM_PAINT消息这么简单。理解什么时候和为什么WM_PAINT消息要被送到窗口是很有益处的。 有效和无效区域

某些情况下,窗口的一部分暴露给用户,这些部分在Windows里称为区域,这些区域被标记为无效。当应用程序消息队列里没有其它消息在等待处理并且应用程序窗口包含有无效区域的时候,Windows会给窗口发送WM_PAINT消息。正如第一章里提到的,处理WM_PAINT消息的绘制操作是包含在BeginPaint和EndPaint调用之间的。BeginPaint实际上执行很多动作。首先将无效区域标记为有效,接下来计算需要裁减的区域。裁减区是限制绘图动作的区域。BeginPaint接下来发送WM_ERASEBACKGROUND消息,如果需要,还会重绘背景,如果

用于指示文本输入的光标可见,它还会隐藏光标。最后BeginPaint获得显示设备环境变量的句柄,该句柄可以用于应用程序中。EndPaint函数则释放设备环境,如果必要,还会重新显示光标。如果WM_PAINT处理过程没有更多的操作要执行,您也必须至少调用BeginPaint 和EndPaint ,以便将无效区域标记为有效。

作为替代,您可以调用ValidateRect来强制使矩形有效。但这种情况下没有绘制动作发生,因为应用程序在窗口上绘制任何东西之前,必须有设备环境句柄。

应用程序经常需要强制重画它的窗口。应用程序决不应该邮递或发送(post or send)WM_PAINT消息给自身或其它窗口。您应该使用以下函数:

BOOL InvalidateRect (HWND hWnd, const RECT *lpRect, BOOL bErase);

请注意InvalidateRect并不要求窗口设备环境句柄,只要窗口句柄自身。lpRect表示窗口中需要无效的区域。该参数为NULL则表示整个窗口无效。bErase用来指出在调用BeginPaint的时候是否重画窗口背景。请注意不像Windows的其它版本,Windows CE要求hWnd必须是一个有效的窗口句柄。 设备环境

设备环境经常被简称为DC,它被Windows用于管理对显示器和打印机的访问,当然在本章我将只讨论显示器。除非特别说明,下面的讨论适合所有Windows,而并不具体针对Windows CE。

Windows应用程序从不直接写屏幕。相反,它们为适当的窗口请求一个显示设备环境句柄,之后用该句柄在设备环境里绘制。接下来Windows作为中间人将像素从DC操纵到屏幕上。 只应该在WM_PAINT消息里调用BeginPaint,它为窗口返回一个显示DC句柄。通常应用程序在处理WM_PAINT消息时在屏幕上绘制东西。Windows将绘制作为一个低优先级的任务,这样做是适当的,因为将绘制作为高优先级会导致为每个小的显示改变都产生一个绘图消息,这将使绘图消息泛滥。而通过处理所有正在等待的消息,允许应用程序先完成所有未完成的事务,这样使所有无效区域能够被有效的一次性绘制完成。用户并不会注意到由WM_PAINT消息低优先级所带来的微小延迟。

当然,有些时候是需要立即绘制的。字处理器就是一个例子,当键盘被按下后,字处理器需要立即显示对应的字符。为了在WM_PAINT消息以外的时刻进行绘制,可以用GetDC函数来获得DC句柄。GetDC原型如下:HDC GetDC (HWND hWnd);

GetDC返回窗口客户区DC句柄。接下来可以在窗口客户区的任何地方进行绘制,因为这个过程不像WM_PAINT消息的处理过程,没有裁减区域来限制您只能在一个无效区域里绘制。 Windows CE支持另一个获得DC的函数,该函数如下:

HDC GetDCEx (HWND hWnd, HRGN hrgnClip, DWORD flags);

GetDCEx允许您对返回的设备环境有更多的控制。新参数hrgnClip使您可以定义用来限制绘制区域的裁减区域。flags指出当您在DC上绘制的时候,DC如何反应。注意Windows CE不支持下面的标志:DCX_PARENTCLIP, DCX_NORESETATTRS, DCX_LOCKWINDOWUPDATE, 和DCX_VALIDATE。

当绘制完成后,必须调用ReleaseDC来释放设备环境。ReleaseDC原型如下: int ReleaseDC (HWND hWnd, HDC hDC);

GetDC用于在客户区内绘制,有时应用程序需要访问窗口非客户区,比如标题栏。为了获得整个窗口的DC,使用以下函数:HDC GetWindowDC (HWND hWnd); 如前所述,当绘制完成后,要调用ReleaseDC。

Windows CE下的DC函数同XP下的设备环境函数是一样的。这是可以预料到的,因为DC是Windows 绘图体系里的核心。对这个领域的API进行改变将导致Windows CE程序与它的桌面程序严重的不兼容。

输出文本

在第一章里,例子程序HelloCE调用DrawText函数显示了一行文本。代码如下: DrawText (hdc, TEXT (\ DT_CENTER | DT_VCENTER | DT_SINGLELINE);

DrawText是一个相当高级的函数,允许由程序显示文本,而由Windows处理大部分细节。DrawText的头几个参数几乎是不言而喻,很直观。当前正在使用的设备环境句柄被传入,同时传入的还有被TEXT宏包围的用来显示的文本,声明成Unicode字符串是为了符合Windows CE的需要。

第三个参数是要输出的字符个数,当为-1,则表示传入的是以NULL为终止符的字符串并由Windows计算其长度。

第四个参数是一个指向rect结构的指针,为文本规定了格式化矩形。DrawText用该矩形作为文本格式化输出的基础。文本如何格式化取决于函数的最后一个参数--格式化标志位。这些标志位指定文本如何被放在格式化的矩形里。在指定了DT_CALCRECT标志位的情况下,由DrawText来计算需要输出的文本的尺寸。DrawText甚至用自动计算出的行中断(line break)来将文本格式化多行。在HelloCE的情况里,标志位规定文本应该水平居中(DT_CENTER)和垂直居中(DT_VCENTER)。DT_VCENTER标志只在单行文本的情况下有效,所以最后一个标志位DT_SINGLELINE规定如果矩形宽度不足以显示整个字符串时,文本不应该折成多行。 画文本的另一个方法就是使用下面的函数:

BOOL ExtTextOut (HDC hdc, int X, int Y, UINT fuOptions, const RECT *lprc, LPCTSTR lpString, UINT cbCount, const int *lpDx);

ExtTextOut函数同DrawText相比有一些优势。首先,ExtTextOut画单行文本往往更快一些。其次,文本并不在矩形里格式化,而是以传入的x、y坐标作为文本绘制的起始坐标。通常,该点是矩形的左上角坐标,但它可以随着DC中文本对齐方式来改变。传入的rect参数用做剪切矩形,如果背景模式是opaque,则背景颜色的区域被绘制。该矩形参数可以是NULL,表示不需要剪切或者opaquing(不透明化)。接下来的两个参数是文本及字符个数。最后一个参数允许应用程序指定相邻字符之间的水平距离。

Windows CE与其它版本的Windows不同的地方在于只有这两个文本绘制函数用于显示文本。通过使用DrawText或ExTextOut,您可以模拟TextOut 和 TabbedTextOut等其它版本Windows里文本函数所能做的大部分操作。这是Windows CE同Windows早期版本不同的地方之一,通过牺牲向后兼容性来获得一个更小的操作系统。 设备环境属性

关于HelloCe中用的DrawText,我还没有提到的是当显示文本时程序对DC配置做的大量假设。在Windows设备环境上绘制需要很多参数,例如前景色和背景色、文字如何绘制在背景上

以及文字的字体等。每次绘制时并不是指定所有这些参数,设备环境保持当前设置,也就是属性,每次在绘制设备变量上绘制时都使用这些属性。

前景色和背景色

文本属性最显而易见的是前景色和背景色。SetTextColor 和GetTextColor允许程序设置和获取当前颜色。这两个函数在Windwos CE设备支持的灰度级屏幕和彩色屏幕上都可以运行的很好。

使用前面提到的GetDeviceCaps函数,可以确定设备支持多少颜色。该函数原型如下: int GetDeviceCaps (HDC hdc, int nIndex);

您需要被查询的DC的句柄,因为不同的DC有不同的内容。例如,打印机DC就不同于显示器DC。第二个参数指出要查询的内容。在返回设备上可用颜色时,当设备支持256色或更少颜色时,NUMCOLORS返回支持的颜色数量。当超过256时,NUMCOLORS对应的返回值为-1,用BITSPIXEL可以返回颜色,此时返回的是每个像素所用的位(bit)数。通过将BITSPIXEL的返回值左移动一位,可以将这个值转换为颜色数,代码示例如下: nNumColors = GetDeviceCaps (hdc, NUMCOLORS); if (nNumColors == -1)

nNumColors = 1 << GetDeviceCaps (hdc, BITSPIXEL); 文字对齐方式

当用ExtTextOut显示文本时,系统用DC的文字对齐方式来决定在哪里绘制文本。通过SetTextAlign 函数,文字可以水平和垂直对齐。 SetTextAlign 如下:

UINT WINAPI SetTextAlign (HDC hdc, INT fmode); 传给fmode的对齐标方式标志位如下所示:

TA_LEFT 文本左边缘与控制点对齐(控制点解释见后) TA_RIGHT文本右边缘与控制点对齐 TA_TOP文本顶部与控制点对齐

TA_CENTER文本以控制点为中心水平居中 TA_BOTTOM文本底部边缘与控制点对齐 TA_BASELINE文本基线与控制点对齐

TA_NOUPDATECP在调用ExtTextOut后,DC的当前点并不更新 TA_UPDATECP在调用ExtTextOut后,DC的当前点更新

上面描述中提到的控制点指的是传给ExtTextOut的x、y坐标。对SetTextAlign的每次调用,垂直对齐标志和水平对齐标志可以组合在一起。

因为很难形象化的描述这些标志位的效果,图2-1展示了每个标志的效果。在图中,X表示控制点。

图2-1(略):当前绘制点和文本对齐标志之间的关系 绘制模式

影响文本输出的另一个属性是背景模式。当字母在设备环境上绘制时,系统使用前景色绘制字母自身。字母之间的空间则是另一回事。如果背景模式设置为不透明,则使用当前背景色来绘制该空间。但如果背景模式设置为透明,字母之间的空间保持文字绘制之前的状态。虽然这可能不像是一个大的区别,但设想一个使用图画或者图片来填充的窗口背景。如果文字在图片顶部输出,

背景模式设置为不透明,文字周围的区域会被填充,背景色会填充在图片上。如果背景模式是透明,文字在图片上看起来就像文字原本就在图片上一样,图片会在文本的字母之间显示出来。 TextDemo示例程序

TextDemo程序,演示了文本颜色、背景色和背景模式之间的关系。程序如列表2-1所示: 列表2-1:TextDemo程序 TextDemo.h

//================================================================ // Header file //

// Written for the book Programming Windows CE // Copyright (C) 2003 Douglas Boling

//====================================================================== // Returns number of elements

#define dim(x) (sizeof(x) / sizeof(x[0]))

//---------------------------------------------------------------------- // Generic defines and data types //

struct decodeUINT { // Structure associates UINT Code; // messages // with a function. LRESULT (*Fxn)(HWND, UINT, WPARAM, LPARAM); };

struct decodeCMD { // Structure associates UINT Code; // menu IDs with a

LRESULT (*Fxn)(HWND, WORD, HWND, WORD); // function. };

//---------------------------------------------------------------------- // Function prototypes //

HWND InitInstance (HINSTANCE, LPWSTR, int); int TermInstance (HINSTANCE, int);

// Window procedures

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

// Message handlers

LRESULT DoPaintMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoDestroyMain (HWND, UINT, WPARAM, LPARAM);

TextDemo.cpp

//================================================================

// TextDemo - Text output demo //

// Written for the book Programming Windows CE // Copyright (C) 2003 Douglas Boling

//======================================================================

#include // For all that Windows stuff #include \// Program-specific stuff

//---------------------------------------------------------------------- // Global data //

const TCHAR szAppName[] = TEXT (\

HINSTANCE hInst; // Program instance handle

// Message dispatch table for MainWindowProc const struct decodeUINT MainMessages[] = { WM_PAINT, DoPaintMain, WM_DESTROY, DoDestroyMain, };

//====================================================================== // Program Entry Point //

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) { MSG msg; int rc = 0; HWND hwndMain;

// Initialize this instance.

hwndMain = InitInstance (hInstance, lpCmdLine, nCmdShow); if (hwndMain == 0) return 0x10;

// Application message loop

while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); }

// Instance cleanup

return TermInstance (hInstance, msg.wParam); }

//---------------------------------------------------------------------- // InitInstance - Instance initialization //

HWND InitInstance (HINSTANCE hInstance, LPWSTR lpCmdLine, int nCmdShow){ WNDCLASS wc; HWND hWnd;

hInst = hInstance; // Save handle in global variable.

#if defined(WIN32_PLATFORM_PSPC)

// If Pocket PC, allow only one instance of the application. hWnd = FindWindow (szAppName, NULL); if (hWnd) {

SetForegroundWindow ((HWND)(((DWORD)hWnd) | 0x01)); return 0; } #endif

// Register application main window class. wc.style = 0; // Window style

wc.lpfnWndProc = MainWndProc; // Callback function wc.cbClsExtra = 0; // Extra class data wc.cbWndExtra = 0; // Extra window data wc.hInstance = hInstance; // Owner handle wc.hIcon = NULL, // Application icon

wc.hCursor = LoadCursor (NULL, IDC_ARROW);// Default cursor wc.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH); wc.lpszMenuName = NULL; // Menu name

wc.lpszClassName = szAppName; // Window class name

if (RegisterClass (&wc) == 0) return 0;

// Create main window.

hWnd = CreateWindowEx (WS_EX_NODRAG, // Ex Style flags szAppName, // Window class TEXT(\// Window title // Style flags

WS_VISIBLE | WS_CAPTION | WS_SYSMENU, CW_USEDEFAULT, // x position CW_USEDEFAULT, // y position CW_USEDEFAULT, // Initial width CW_USEDEFAULT, // Initial height NULL, // Parent

NULL, // Menu, must be null hInstance, // Application instance NULL); // Pointer to create // Parameters // Return fail code if window not created. if ((!hWnd) || (!IsWindow (hWnd))) return 0;

// Standard show and update calls ShowWindow (hWnd, nCmdShow); UpdateWindow (hWnd); return hWnd; }

//---------------------------------------------------------------------- // TermInstance - Program cleanup //

int TermInstance (HINSTANCE hInstance, int nDefRC) { return nDefRC; }

//======================================================================

// Message handling procedures for MainWindow //

//---------------------------------------------------------------------- // MainWndProc - Callback function for application window //

LRESULT CALLBACK MainWndProc (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) { INT i; //

// Search message list to see if we need to handle this // message. If in list, call procedure. //

for (i = 0; i < dim(MainMessages); i++) { if (wMsg == MainMessages[i].Code)

return (*MainMessages[i].Fxn)(hWnd, wMsg, wParam, lParam); }

return DefWindowProc (hWnd, wMsg, wParam, lParam); }

//---------------------------------------------------------------------- // DoPaintMain - Process WM_PAINT message for window. //

LRESULT DoPaintMain (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps;

RECT rect, rectCli; HBRUSH hbrOld; HDC hdc; INT i, cy;

DWORD dwColorTable[] = {0x00000000, 0x00808080, 0x00cccccc, 0x00ffffff};

GetClientRect (hWnd, &rectCli);

hdc = BeginPaint (hWnd, &ps);

// Get the height and length of the string.

DrawText (hdc, TEXT (\ DT_CALCRECT | DT_CENTER | DT_SINGLELINE);

cy = rect.bottom - rect.top + 5;

// Draw black rectangle on right half of window.

hbrOld = (HBRUSH)SelectObject (hdc, GetStockObject (BLACK_BRUSH)); Rectangle (hdc, rectCli.left + (rectCli.right - rectCli.left) / 2, rectCli.top, rectCli.right, rectCli.bottom); SelectObject (hdc, hbrOld);

rectCli.bottom = rectCli.top + cy; SetBkMode (hdc, TRANSPARENT); for (i = 0; i < 4; i++) {

SetTextColor (hdc, dwColorTable[i]); SetBkColor (hdc, dwColorTable[3-i]);

DrawText (hdc, TEXT (\ DT_CENTER | DT_SINGLELINE); rectCli.top += cy; rectCli.bottom += cy; }

SetBkMode (hdc, OPAQUE); for (i = 0; i < 4; i++) {

SetTextColor (hdc, dwColorTable[i]); SetBkColor (hdc, dwColorTable[3-i]);

DrawText (hdc, TEXT (\ DT_CENTER | DT_SINGLELINE); rectCli.top += cy; rectCli.bottom += cy;

}

EndPaint (hWnd, &ps); return 0; }

//---------------------------------------------------------------------- // DoDestroyMain - Process WM_DESTROY message for window. //

LRESULT DoDestroyMain (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) { PostQuitMessage (0); return 0; }

TextDemo的实质内容在OnPaintMain函数里。对DrawText的第一次调用并没有在设备环境上画任何东西。相反,使用DT_CALCRECT标志指示Windows将文本串的矩形尺寸存在rect里。该信息用于计算字符串的高度,并存储在cy里。接下来,在窗口的右侧绘制了一个黑色矩形。我将在本章稍后一些讨论如何绘制一个矩形。该矩形用来在文本被书写之前,产生两个不同的背景。函数接下来用不同的前景色、背景色在透明和不透明模式下输出了 同样的字符串。程序结果如图2-2所示:

头四行使用透明模式来绘制。后四行使用不透明模式来绘制。文本颜色从黑到白,每行使用不同的颜色,同时背景色设置为从白到黑。在透明模式下,背景色是无关紧要的,因为并不使用该背景色。但是在不通明模式下,背景色欣然出现在每行里。

图2-2(略):TextDemo展示了文本色、背景色以及背景模式之间的关系。 字体

如果Windows提供的全部灵活性就是设置前景色和背景色,那我们还是回到MS-DOS和字符属性时代的好。有证据表明,Windows同MS-DOS最显著的变化就是Windows可改变显示文本字体的能力。所有Windows操作系统都是建立在WYS/WYG-所见即所得-的概念上的,而采用可变字体就是达到这一目的的一个主要手段。

在所有Windows操作系统里都有两种字体类型-光栅型(raster)和TrueType型。光栅型字体存储为位图,即小的像素图像,每个字符都有一个。光栅字体容易存储和使用,但有一个重要问题:不能很好的缩放。就像小图放大时出现锯齿纹一样,光栅字体放大到更大的字体时会出现锯齿纹。

TrueType字体解决了缩放问题。它不是存储成图象,每个TrueType字符存都存储成如何绘制字符的描述信息。作为在屏幕上绘制字符的Windows功能的一部分,字体引擎获得描述信息,并按需要的尺寸在屏幕上绘制字符。一个Windows CE系统支持TrueType或光栅字体,但不会同时支持。幸运的是,对光栅字体和TrueType字体来说,编程接口都是一样的,这减少了Windows 开发者对字体技术的担忧,毕竟字体技术用在所有应用里,也是最让人吃力的技术之一。

Windows CE里的字体函数同Windows其它版本中的字体函数很相近。从创建字体、选择进DC到最后删除字体,让我们看一下字体生存周期中用到的函数吧。如何查询当前字体以及枚举可使用的字体等,都会在下面几节里涉及到。

创建字体

在应用可以使用非默认字体之前,该字体必须被创建并被选进设备环境里。在新字体被选进DC后,在DC里绘制的任何文本都使用这个新字体。 在Windows CE中创建字体的方法如下:

HFONT CreateFontIndirect (const LOGFONT *lplf);

该函数接受一个指向LOGFONT结构的指针,该结构必须使用您需要的字体描述来填充。 typedef struct tagLOGFONT { LONG lfHeight; LONG lfWidth; LONG lfEscapement; LONG lfOrientation; LONG lfWeight; BYTE lfItalic; BYTE lfUnderline; BYTE lfStrikeOut; BYTE lfCharSet; BYTE lfOutPrecision; BYTE lfClipPrecision; BYTE lfQuality;

BYTE lfPitchAndFamily;

TCHAR lfFaceName[LF_FACESIZE]; } LOGFONT;

lfHeight 规定字体在设备单位下的高度。如果该域为0,字体管理器返回使用的字体系中默认字体尺寸。对于大多数应用程序来说,您需要创建一个特殊尺寸的字体。下面的公式用于给lfHeight转换磅值:

lfHeight = –1 * (PointSize * GetDeviceCaps (hdc, LOGPIXELSY) / 72);

这里传给GetDeviceCaps的LOGPIXELSY告诉函数返回垂直方向上每英寸的逻辑像素数。72表示每英寸的磅值(磅是用于排版的计算单位)

lfWidth规定了平均字符宽度。因为字体的高度比宽度更重要,所以大多数程序都把这个值设为0。表示让字体管理器根据字体高度计算出合适的宽度。lfEscapement 和lfOrientation 规定了字符基线同X坐标轴之间以十分之一度为单位的旋转角度。lfWeight规定了从0到1000范围内的字体粗细程度,其中400是标准字体,700是加重字体。接下来的三个域规定了字体是否是斜体、下划线或者删除线。

lpCharSet规定了您选择的字符集。该域对于软件的国际版本是很重要的,因为国际版本一般都要求特定的语言字符集合。lfOutPrecision 规定Windows匹配您请求的字体的精确程度。在众多可以使用的标志当中,OUT_TT_ONLY_PRECIS标志规定创建的字体必须是TrueType字体。

lfClipPrecision规定Windows如何裁剪超出显示区域的字符。 lfQuality 可以设置为以下几种: DEFAULT_QUALITY 默认系统质量 DRAFT_QUALITY 牺牲质量换取速度

CLEARTYPE_QUALITY 用ClearType技术绘制文本

CLEARTYPE_COMPAT_QUALITY 用ClearType技术绘制文本。对非ClearType字体使用同样的空间。

ClearType是一种为字体提供更清晰的外观的文本显示技术,该技术独立寻址红、绿、蓝LCD,它们在彩色LCD显示器上构成一个像素。根据系统的不同,有的可能不支持ClearType,有的可能对系统里的所有字体都支持。对于支持ClearType但不完全支持的系统来说,使用CLEARTYPE_QUALITY 或CLEARTYPE_COMPAT_QUALITY 可以创建用ClearType绘制的字体。因为ClearType并不改进所有字体的外观,所以您应该测试一下,看ClearType是否对您选择的字体有所改进。

lfPitchAndFamily规定了您选择的字体的字系。当您请求诸如Swiss字系--一种没有衬线的专业字体--时,使用该域很方便。再例如选择Roman字系--一种带衬线的专业字体--时,也很方便,但您要注意不要针对特定的字体。您也可以用该域来规定均衡或等宽字体,并让Windows来决定使用哪种字体来匹配传给LOGFONT结构的特定特性。最后,lfFaceName域用来指明具体字体的字体名称。当用一个填充后的LOGFONT结构调用CreateFontIndirect时,Windows根据提供的特性,创建一个最匹配的逻辑字体。为了使用该字体,需要做的最后一步是选择该字体到一个设备环境里。 选择字体到设备环境

用SelectObject 函数将字体选进DC里,函数如下所示: HGDIOBJ SelectObject (HDC hdc, HGDIOBJ hgdiobj);

该函数不仅仅用于设置默认字体,还用在很多地方;正如您很快就会看到的,可以用该函数来选择其它GDI对象。该函数返回先前被选择的对象(在本例中返回的是先前选择的字体),应该保存该返回值以便当您用完新的字体后可以把先前的选择回DC里。代码行如下所示: hOldFont = (HFONT)SelectObject (hdc, hFont);

当逻辑字体被选择时,系统从可使用的字体中选择最匹配的逻辑字体。对没有TrueType字体的设备,匹配的字体同指定的参数相比有一定数量的差异。因此,不要想当然的认为您请求了一个特殊字体,返回的就是准确匹配的字体。例如,您要求的字体高度可能和选进设备环境的字体高度是不一样的。 查询字体特性

为了确定选进设备环境的字体的特性,调用GetTextMetrics 函数来返回字体特性,函数原型如下:

BOOL GetTextMetrics (HDC hdc, LPTEXTMETRIC lptm); TEXTMETRIC结构带有返回信息,该结构定义如下: typedef struct tagTEXTMETRIC { LONG tmHeight; LONG tmAscent; LONG tmDescent; LONG tmInternalLeading; LONG tmExternalLeading; LONG tmAveCharWidth; LONG tmMaxCharWidth; LONG tmWeight;

LONG tmOverhang; LONG tmDigitizedAspectX; LONG tmDigitizedAspectY; char tmFirstChar; char tmLastChar; char tmDefaultChar; char tmBreakChar; BYTE tmItalic; BYTE tmUnderlined; BYTE tmStruckOut; BYTE tmPitchAndFamily; BYTE tmCharSet; } TEXTMETRIC;

TEXTMETRIC结构包含了LOGFONT结构中的很多域,但此时TEXTMETRIC的值是选进设备变量的字体的特性。图2-3显示了一些域同实际字符之间的关系。 图2-3(图略):TEXTMETRIC结构及其与字体的关系。

除了可以判断您是否真的获得了您需要的字体外,GetTextmetrics函数调用还有另外一个有价值的用途--确定字体高度。回忆一下TextDemo程序,行的高度是通过调用DrawText函数计算出来的。虽然那种方法很方便,但它有点慢。您可以使用TEXTMETRIC数据,以更加直接的方法来计算高度。将表示字符高度的tmHeight域与tmExternalLeading域--表示一行底部像素到下一行顶部像素之间的间距--相加,您就可以获得两行文本基线的间距了。

虽然GetTextMetrics对确定字符高度来说是很好用的,但它只提供了字体的平均宽度和最大宽度。如果需要TrueType的更多细节,可以使用函数GetCharABCWidths,函数原型如下: BOOL GetCharABCWidths (HDC hdc, UINT uFirstChar, UINT uLastChar, LPABC lpabc);

GetCharABCWidths返回由uFirstChar 和 uLastChar 参数所确定的一系列字符的ABC宽度。该函数检查hdc参数指定的DC里的字体。ABC结构如下: typedef struct _ABC { int abcA; UINT abcB; int abcC; } ABC;

abcA为在放置字符轮廓前的空白间距,abcB为字符轮廓本身的间距,abcC为字符轮廓右方的空白间距。abcA和abcC都可以是负值,用来指示缩进或者凸起。

要获得点阵字体(bitmap fonts)的宽度,可以使用GetCharWidth32函数。对指定字符范围内的每个字符,该函数返回一个字符宽度数组。 销毁字体

同其它GDI资源一样,在程序用完后,字体必须被销毁。在终止程序前如果删除字体失败,将导致资源泄露--孤立的图形资源,占用了珍贵的内存但却不再被应用程序拥有。

为了销毁字体,首先将字体从所在的设备环境里取消选择,这可以通过调用SelectObject来完成。传入的字体是最初调用SelectObject来选择字体时返回的字体。字体取消选择后,调用

DeleteObject从系统里删除字体。DeleteObject原型如下: BOOL DeleteObject (HGDIOBJ hObject); hObject 表示要删除的字体句柄。

从这个过程可以看出,字体管理不是一个小事情。LOGFONT结构的许多参数可能令人畏惧,但它们赋予了应用程序准确指定字体的巨大能力。

处理字体时的一个问题是判断在具体设备上能够支持什么类型的字体。Windows CE设备提供标准字体集合,但制造商或者用户可能在具体系统上装载了附加字体。幸运地是,Windows提供了枚举系统上所有字体的方法。 枚举字体

为了判断系统上有什么字体可以使用,Windows提供了以下函数:

int EnumFontFamilies (HDC hdc, LPCTSTR lpszFamily, FONTENUMPROC lpEnumFontFamProc, LPARAM lParam);

该函数列举所以字系以及字系里的每个字体。头一个参数是设备环境。第二个参数是需要枚举的字系的名字。如果该参数为NULL,表示枚举每个可以使用的字系。

第三个参数稍微有些不同,是一个指向由应用程序提供的函数的指针。该函数是一个回调函数,由Windows为每个枚举的字体调用。最后一个参数,lParam,是由应用程序使用的普通参数。该值不经修改,直接传给了应用程序回调过程。

虽然回调函数的名字可以随便起,但函数原型必须声明为:

int CALLBACK EnumFontFamProc (LOGFONT *lpelf, TEXTMETRIC *lpntm, DWORD FontType, LPARAM lParam);

传给回调函数的第一个参数是指向LOGFONT结构的指针,用来描述被枚举的字体。第2个参数,指向textmetric的指针,进一步描述该字体。字体类型参数指明字体是光栅字体还是TrueType字体。 FontList示例程序

FontList程序用两种方式使用EnumFontFamilies函数来枚举系统里所有字体 清单2-2 FontList.h

//================================================================ // Header file //

// Written for the book Programming Windows CE // Copyright (C) 2003 Douglas Boling

//====================================================================== // Returns number of elements

#define dim(x) (sizeof(x) / sizeof(x[0]))

//---------------------------------------------------------------------- // Generic defines and data types //

struct decodeUINT { // Structure associates

UINT Code; // messages // with a function. LRESULT (*Fxn)(HWND, UINT, WPARAM, LPARAM); };

struct decodeCMD { // Structure associates UINT Code; // menu IDs with a

LRESULT (*Fxn)(HWND, WORD, HWND, WORD); // function. };

//---------------------------------------------------------------------- // Program-specific structures //

#define FAMILYMAX 24 typedef struct { int nNumFonts;

TCHAR szFontFamily[LF_FACESIZE]; } FONTFAMSTRUCT;

typedef FONTFAMSTRUCT *PFONTFAMSTRUCT;

typedef struct { INT yCurrent; HDC hdc; } PAINTFONTINFO;

typedef PAINTFONTINFO *PPAINTFONTINFO;

//---------------------------------------------------------------------- // Function prototypes //

HWND InitInstance (HINSTANCE, LPWSTR, int); int TermInstance (HINSTANCE, int);

// Window procedures

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

// Message handlers

LRESULT DoCreateMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoPaintMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoDestroyMain (HWND, UINT, WPARAM, LPARAM); FontList.cpp

//======================================================================

// FontList - Lists the available fonts in the system //

// Written for the book Programming Windows CE // Copyright (C) 2003 Douglas Boling

//======================================================================

#include // For all that Windows stuff #include \// Program-specific stuff

//---------------------------------------------------------------------- // Global data //

const TCHAR szAppName[] = TEXT (\

HINSTANCE hInst; // Program instance handle FONTFAMSTRUCT ffs[FAMILYMAX]; INT sFamilyCnt = 0;

// Message dispatch table for MainWindowProc const struct decodeUINT MainMessages[] = { WM_CREATE, DoCreateMain, WM_PAINT, DoPaintMain, WM_DESTROY, DoDestroyMain, };

//====================================================================== // Program entry point //

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) { MSG msg; int rc = 0; HWND hwndMain;

// Initialize this instance.

hwndMain = InitInstance (hInstance, lpCmdLine, nCmdShow); if (hwndMain == 0) return 0x10;

// Application message loop

while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); }

// Instance cleanup

return TermInstance (hInstance, msg.wParam); }

//---------------------------------------------------------------------- // InitInstance - Instance initialization

//

HWND InitInstance (HINSTANCE hInstance, LPWSTR lpCmdLine, int nCmdShow) { WNDCLASS wc; HWND hWnd;

// Save program instance handle in global variable. hInst = hInstance;

#if defined(WIN32_PLATFORM_PSPC)

// If Pocket PC, allow only one instance of the application. hWnd = FindWindow (szAppName, NULL); if (hWnd) {

SetForegroundWindow ((HWND)(((DWORD)hWnd) | 0x01)); return 0; } #endif

// Register application main window class. wc.style = 0; // Window style

wc.lpfnWndProc = MainWndProc; // Callback function wc.cbClsExtra = 0; // Extra class data wc.cbWndExtra = 0; // Extra window data wc.hInstance = hInstance; // Owner handle wc.hIcon = NULL, // Application icon

wc.hCursor = LoadCursor (NULL, IDC_ARROW);// Default cursor wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); wc.lpszMenuName = NULL; // Menu name

wc.lpszClassName = szAppName; // Window class name

if (RegisterClass (&wc) == 0) return 0;

// Create main window.

hWnd = CreateWindowEx (WS_EX_NODRAG, // Ex style flags szAppName, // Window class TEXT(\ // Style flags

WS_VISIBLE | WS_CAPTION | WS_SYSMENU, CW_USEDEFAULT, // x position CW_USEDEFAULT, // y position CW_USEDEFAULT, // Initial width CW_USEDEFAULT, // Initial height NULL, // Parent

NULL, // Menu, must be null hInstance, // Application instance NULL); // Pointer to create

// parameters // Return fail code if window not created. if (!IsWindow (hWnd)) return 0;

// Standard show and update calls ShowWindow (hWnd, nCmdShow); UpdateWindow (hWnd); return hWnd; }

//---------------------------------------------------------------------- // TermInstance - Program cleanup //

int TermInstance (HINSTANCE hInstance, int nDefRC) { return nDefRC; }

//====================================================================== // Font callback functions //

//---------------------------------------------------------------------- // FontFamilyCallback - Callback function that enumerates the font // families //

int CALLBACK FontFamilyCallback (CONST LOGFONT *lplf, CONST TEXTMETRIC *lpntm,

DWORD nFontType, LPARAM lParam) { int rc = 1;

// Stop enumeration if array filled. if (sFamilyCnt >= FAMILYMAX) return 0;

// Copy face name of font.

lstrcpy (ffs[sFamilyCnt++].szFontFamily, lplf->lfFaceName); return rc; }

//---------------------------------------------------------------------- // EnumSingleFontFamily - Callback function that enumerates fonts //

int CALLBACK EnumSingleFontFamily (CONST LOGFONT *lplf, CONST TEXTMETRIC *lpntm,

DWORD nFontType, LPARAM lParam) { PFONTFAMSTRUCT pffs;

pffs = (PFONTFAMSTRUCT) lParam;

pffs->nNumFonts++; // Increment count of fonts in family return 1; }

//---------------------------------------------------------------- // PaintSingleFontFamily - Callback function that draws a font //

int CALLBACK PaintSingleFontFamily (CONST LOGFONT *lplf, CONST TEXTMETRIC *lpntm,

DWORD nFontType, LPARAM lParam) { PPAINTFONTINFO ppfi; TCHAR szOut[256];

INT nFontHeight, nPointSize; HFONT hFont, hOldFont;

ppfi = (PPAINTFONTINFO) lParam; // Translate lParam into struct // pointer.

// Create the font from the LOGFONT structure passed. hFont = CreateFontIndirect (lplf);

// Select the font into the device context.

hOldFont = (HFONT)SelectObject (ppfi->hdc, hFont);

// Compute font size.

nPointSize = (lplf->lfHeight * 72) /

GetDeviceCaps(ppfi->hdc,LOGPIXELSY);

// Format string and paint on display.

wsprintf (szOut, TEXT (\Point:%d\ nPointSize);

ExtTextOut (ppfi->hdc, 25, ppfi->yCurrent, 0, NULL, szOut, lstrlen (szOut), NULL);

// Compute the height of the default font.

nFontHeight = lpntm->tmHeight + lpntm->tmExternalLeading; // Update new draw point. ppfi->yCurrent += nFontHeight; // Deselect font and delete.

SelectObject (ppfi->hdc, hOldFont); DeleteObject (hFont); return 1; }

//=================================================

===============

// Message handling procedures for MainWindow //

//---------------------------------------------------------------- // MainWndProc - Callback function for application window //

LRESULT CALLBACK MainWndProc (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) { INT i; //

// Search message list to see if we need to handle this // message. If in list, call procedure. //

for (i = 0; i < dim(MainMessages); i++) { if (wMsg == MainMessages[i].Code)

return (*MainMessages[i].Fxn)(hWnd, wMsg, wParam, lParam); }

return DefWindowProc (hWnd, wMsg, wParam, lParam); }

//---------------------------------------------------------------------- // DoCreateMain - Process WM_CREATE message for window. //

LRESULT DoCreateMain (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) { HDC hdc; INT i, rc;

//Enumerate the available fonts. hdc = GetDC (hWnd);

rc = EnumFontFamilies ((HDC)hdc, (LPTSTR)NULL, FontFamilyCallback, 0);

for (i = 0; i < sFamilyCnt; i++) { ffs[i].nNumFonts = 0;

rc = EnumFontFamilies ((HDC)hdc, ffs[i].szFontFamily, EnumSingleFontFamily,

(LPARAM)(PFONTFAMSTRUCT)&ffs[i]); }

ReleaseDC (hWnd, hdc); return 0; }

//--------------------------------------------------------------- // DoPaintMain - Process WM_PAINT message for window. //

LRESULT DoPaintMain (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; RECT rect; HDC hdc; TEXTMETRIC tm; INT nFontHeight, i; TCHAR szOut[256]; PAINTFONTINFO pfi;

GetClientRect (hWnd, &rect);

hdc = BeginPaint (hWnd, &ps);

// Get the height of the default font. GetTextMetrics (hdc, &tm);

nFontHeight = tm.tmHeight + tm.tmExternalLeading;

// Initialize struct that is passed to enumerate function. pfi.yCurrent = rect.top; pfi.hdc = hdc;

for (i = 0; i < sFamilyCnt; i++) {

// Format output string, and paint font family name. wsprintf (szOut, TEXT(\\ ffs[i].szFontFamily);

ExtTextOut (hdc, 5, pfi.yCurrent, 0, NULL, szOut, lstrlen (szOut), NULL); pfi.yCurrent += nFontHeight;

// Enumerate each family to draw a sample of that font. EnumFontFamilies ((HDC)hdc, ffs[i].szFontFamily, PaintSingleFontFamily, (LPARAM)&pfi); }

EndPaint (hWnd, &ps); return 0; }

//---------------------------------------------------------------- // DoDestroyMain - Process WM_DESTROY message for window. //

LRESULT DoDestroyMain (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) { PostQuitMessage (0);

return 0; }

当应用程序在OnCreateMain中处理WM_CREATE消息时,首先枚举不同的字体。此处调用EnumFontFamilies函数,其中FontFamily域设为NULL表示每个字系都将被枚举。在回调函数FontFamilyCallback里,字系的名字被复制进一个字符串数组里。

在处理WM_PAINT消息的过程里完成剩余的工作。OnPaint函数以惯常的标准方式开头,依然是先获取客户区域大小并调用BeginPaint函数,该函数返回窗口的设备环境句柄。接下来调用GetTextMetrics来计算默认字体的行高。之后进入循环,为OnCreateMain中枚举出的每个字系调用EnumFontFamilies函数。用于这个回调序列的回调过程是迄今为止比较复杂的代码。

用于枚举单个字体的回调过程PaintSingleFontFamily使用lParam参数来获得一个指向PAINTFONTINFO结构的指针。该结构包含当前垂直绘制坐标以及设备环境的句柄。通过使用lParam指针,FontList可以避免声明全局变量来和回调过程通信。

接下来回调过程使用传进来的LOGFONT指针创建字体。新字体随后被选进设备环境,之前被选择的字体句柄被保存在hOldFont中。使用本章前面提到的转换公式来计算被枚举的字体的磅值。接下来输出一行文本,显示字系名称以及该字体的磅值。没有使用DrawText,回调过程用的是ExtTextOut来绘制字符串的。

显示完文本后,函数通过使用传入的TEXTMETRIC结构里的tmHeight和

tmExternalLeading域来计算刚绘制的文本的行高。之后用最初选择的字体句柄调用SelectObject来取消对新字体的选择。随后用DeleteObject来删除新字体。最后,回调函数返回一个非零值来通知Windows一起正常,可以开始另一次枚举回调了。图2-4显示了字体列表窗口。注意字体名字体名称,每个字体有特定的大小集合。 图2-4(略):字体列表窗口显示了手持PC上一些可以使用的字体。 未完的事情

如果你仔细看图2-4,你会注意到一个显示上的问题。字体列表显示在了Font列表窗口的底边上。解决方法是为窗口增加滚动条。因为我将在第四章详细描述窗口控件,其中就包括滚动条,所以我暂时不讲述如何实现该方法。

位图

位图是一种图形化对象,用于在设备环境里创建、绘制、操纵和接收图片。从[开始按钮]上的小Winodws标志到标题栏上的[关闭]按钮,位图在Windows里无处不在。位图可以看作是一种由像素数组构成的图片,这些像素可以在屏幕上进行绘制。和所有图片一样,位图有自己的高度和宽度。也提供方法来判断位图使用什么颜色。最后,位图也是一个描述位图中每个像素的位(bits)数组。

习惯上,Windows下的位图被划分成两种类型:设备相关位图(DDBs)和设备无关位图(DIBs)。DDBs是一种和具体DC的特性有紧密关系的位图,不容易在有不同特性的DC上绘制。DIBs则相反,它与具体设备无关,因此需要携带足够的信息以便于在任何设备上准确的绘制。 Windwos CE包含了许多在其它Windows版本里可以使用的位图函数。不同之处包括只有Windows CE才支持的一种新的四色格式和对DIBs不同的操纵方式。 设备相关位图

可以使用CreateBitmap函数来创建设备相关位图,函数原型如下:

HBITMAP CreateBitmap (int nWidth, int nHeight, UINT cPlanes, UINT cBitsPerPel, CONST VOID *lpvBits);

nWidth和nHeight表示位图的尺寸。cPlanes是一个历史产物,当时显示器采用不同的硬件平面来实现像素里的每个颜色。对Windows CE来说,该参数必须是1。cBitspPerPel表示每个像素使用的位数。颜色数是cBitspPerPel的2次幂。在Windows CE下,允许使用的是1、2、4、8、16和24。正如我说过的,4色位图是Windows CE特有的,其它Windows 平台不则不支持。

最后一个参数是指向位图位数的指针。在Windows CE下,位数总是按压缩像素格式排列的。也就是,每个像素按字节存储成一系列的比特位,下一个像素紧接上一个。位数组的第一个像素是位图左上角的像素。像素沿位图顶行排列,随后是第二行,依次类推。位图每行必须是双字(4字节)对齐排列。为了对齐下一行,可在行尾使用0来填充。图2-5演示了这种排列方式,图中展示了一个126*64像素的位图,每个像素使用8位。

图2-5(略) 位图里的字节布局

函数CreateCompatibleBitmap,其原型如下:

HBITMAP CreateCompatibleBitmap (HDC hdc, int nWidth, int nHeight);

用该函数可以创建一个格式与传入的设备环境兼容的位图。所以如果设备环境是四色DC,创建的位图也是一个四色位图。当您要在屏幕上操纵图片时,使用该函数很就很方便,因为该函数可以很容易创建一个与屏幕直接颜色兼容的空位图。 设备无关位图

设备无关位图和设备相关位图之间的基本差异是存储成DIBs的图象有自己的颜色信息。自从使用BMP作为扩展名的Windows 3.0起,几乎每个位图文件都含有在Windows里创建DIB时所需要的信息。

在Windows早期,写程序手工读DIB文件并把数据转换为位图是程序员必备的技能。现在,这个烦冗的任务可以通过Windows CE特有的函数SHLoadDIBitmap来完成,函数原型如下: HBITMAP SHLoadDIBitmap (LPCTSTR szFileName);

该函数直接从位图文件里装载位图并提供位图句柄。在Windows XP里可以使用带

LR_LOADFROMFILE参数标志的LoadImage函数来完成同样的处理,但Windows CE下的LoadImage不支持这个标志。 DIB片段

虽然Windows CE里很容易装载位图文件,但有时您必须读屏幕上图象、操纵图象以及将图象重画到屏幕上。这是DIBs比DDBs更好一些的地方之一。虽然设备相关位图的位数据可以获取得到,但缓冲区的格式直接依赖于屏幕格式。而通过使用DIB,或者更准确地说,通过使用DIB片段,您的程序可以把位图读到预定义格式的缓冲区里,而不用担心显示设备的格式。

虽然从Windows 3.0起就不断加入许多DIB创建函数,但Windows CE只支持XP中的一部分DIB函数。CreateDIBSection是这些函数中的第一个:

HBITMAP CreateDIBSection (HDC hdc, const BITMAPINFO *pbmi, UINT iUsage, void *ppvBits,

HANDLE hSection, DWORD dwOffset);

因为他们是相当晚才加到Win32 API里的,所以DIB片段对程序员来说可能是比较新鲜的。使用DIB片段是为了改进Winodows NT上直接操纵位图的应用程序性能。简而言之,DIB片段允许程序员在直接访问位图位数据时,选择一个设备环境里的DIB。为达到这个目的,DIB片段将一个缓冲区与内存DC结合到一起,该缓冲区同时包含了该DC的位数据。因为图象是映射到一个DC的,所以可以使用其它图形函数调用来修改图片。同时,DC里的DIB格式的原始位数据可以被直接操纵。能改进在NT上的性能固然是很好,但对Window CE程序员来说,能够简化位图的使用和操作位图的内容才是最有价值的。

该函数中最重要的参数是指向BITMAPINFO结构的指针。该结构描述了设备无关位图的布局和颜色构成,该结构包含一个BITMAPINFOHEADER结构和一个代表位图使用的调色板的RGBQUAD数组。

BITMAPINFOHEADER定义如下

typedef struct tagBITMAPINFOHEADER{ DWORD biSize; LONG biWidth; LONG biHeight; WORD biPlanes; WORD biBitCount; DWORD biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD biClrImportant; } BITMAPINFOHEADER;

如你所见,该结构包含的信息远多于传给CreateBitmap的参数。第一个域是该结构的尺寸,必须由调用者填充,用于区别由OS/2管理器沿袭来的BITMAPCOREINFOHEADER结构。biWidth, biHeight, biPlanes,和biBitCount都和CreateBitmap里的同名参数类似,但有一个例外,biHeight的正负号指定了位数组的组织排列方式。如果biHeight是正数,位数组按由上到下的格式排列,这一点和CreateBitmap相同。如果biHeight是负数,位数组则按由下到上的格式排列,也就是位图的底部行定义在该位数组的首位。和CreateBitmap一样,biPlanes必须设置为1。

biCompression指出位数组使用的压缩方式。Windows CE里,允许使用的标志有,BI_RGB,指出缓冲区没有压缩;BI_BITFIELDS,指出像素格式被定义在颜色表的头三个入口里。biSizeImage用于指出位数组的大小。但是,当使用BI_RGB标志时,biSizeImage可以设置为0,表示用BITMAPINFOHEADER结构里提供的尺寸(dimensions )和像素的位数来计算数组的大小。

biXPelsPerMeter和biYPelsPerMeter提供图片的准确尺寸信息。但是对于

CreateBIBSection来说,这些参数可以设置为0。biClrUsed指出实际使用的调色板里的颜色数。在256色图片里,调色板有256个入口,但畏途自身可能只需要大约100个不同的颜色。这个域帮助调色板管理器--Windows管理颜色匹配的部件--将系统调色板里的颜色同位图要求的颜色进行匹配。biClrImportant进一步指出真正需要的颜色。对更多颜色的位图,这两个域设置为0,表示使用所有颜色并且所有颜色都重要。

前面提到过,BITMAPINFOHEADER结构之后是RGBQUAD结构数组。该结构定义如下: typedef struct tagRGBQUAD { /* rgbq */ BYTE rgbBlue; BYTE rgbGreen; BYTE rgbRed; BYTE rgbReserved; } RGBQUAD

该结构允许有红蓝绿各256级色度(shade )。虽然用该结构可以创建几乎任何色度,但设备上实际渲染的颜色是受设备能显示的颜色的限制的。

总体来看,RGBQUAD结构数组描述了DIB的调色板。调色板是位图里的颜色列表。如果位图有调色板,位图数组的每个入口包含的就不再是颜色,而是包含每个像素颜色的调色板索引。虽然对单色位图来说是多余的,但当在彩色设备上绘制彩色位图时,调色板就相当重要了。例如,虽然256色位图中每个像素一个字节,但该字节指向一个代表红绿蓝色的24位值。所以尽管256色位图只能包含256个不同的颜色,但由于是使用24位调色板入口进行颜色绘制的,因而这些颜色中的每个都可使用出的1千6百万种颜色中的一个。为了方便在32位中使用,每个只包含24位颜色信息的调色板入口都被扩充到32位宽度了,这也是RGBQUAD名字的来源。(译者注:QUAD有四个一套的意思)

CreateDIBSection剩余的四个参数中只有两个用于Windows CE。IUsage指出调色板里的颜色是如何被绘制的。如果该参数是DIB_RGB_COLORS,表示位图里的位数据包含了每个像素的全部RGB颜色信息;DIB_PAL_COLORS,表示位图像素包含DC里当前选择的调色板的索引。PpvBits 是指向构成位图图象的位数据的指针。最后两个参数,hSection和dwOffset,Windows CE不支持它们,必须设置为0。在Windows的其它版本里,它们允许使用内存映射文件来给出位数据。因为Windows CE不支持内存映射文件,所以它们不能被CreateDIBSection支持。

GetDIBColorTable和SetDIBColorTable是管理DIB调色板的两个函数,它们的原型如下: UINT GetDIBColorTable (HDC hdc, UINT uStartIndex, UINT cEntries, RGBQUAD *pColors); 和

UINT SetDIBColorTable (HDC hdc, UINT uStartIndex, UINT cEntries, RGBQUAD *pColors);

对这两个函数来说,uStartIndex指出将被设置或者查询的调色板的第一个入口。CEntries指出有多少调色板入口将改变。指向RGBQUAD数组的指针是用于设置(对SetDIBColorTable)或者查询(对GetDIBColorTable)的颜色数组。 绘制位图

能够创建和装载位图固然很好,但如果您创建的位图不能绘制在屏幕上,那就没什么大用处。绘

制位图可能并不是您想象的那么简单。位图被绘制到屏幕DC之前,必须先将位图选进一个DC,再将其复制到屏幕设备环境里。虽然这个过程听起来可能有点费解,但这是有合理的原因的。 把位图选择到一个设备环境的过程与把逻辑字体选择到设备环境的过程类似。下面让我们把理想变成现实吧。正如Windows要为请求的字体找到最可能匹配的字体一样,位图选择过程也必须为位图要求的颜色找到匹配的设备上可用的颜色。只有在这个过程完成后,位图才能绘制到屏幕上。为了帮助完成这一中间步骤,Windows提供了一个替身DC—内存设备环境。 要创建内存设备环境,可以使用函数CreateCompatibleDC: HDC CreateCompatibleDC (HDC hdc);

该函数创建一个与当前屏幕DC兼容的内存DC。一旦创建成功,可以使用您以前用来选择逻辑字体的SelectObject函数将源位图选进这个内存DC。最后,用BitBlt 或StretchBlt将位图从内存DC复制到屏幕DC。 位图函数的主力是

BOOL BitBlt (HDC hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, HDC hdcSrc, int nXSrc, int nYSrc, DWORD dwRop);

BitBlt函数发音为―bit blit‖,它是一个有意思的函数,它在设备环境上操作,而不是内存里,它有时是一个很特别的函数。第一个参数是位图即将被复制到其上的目的设备环境的句柄。接下来的4个参数规定了位图最终位于的目的矩形的位置和大小。接下来的3个参数规定了源设备环境的句柄以及源图象左上角在该DC里的位置。

最后一个参数dwRop规定图象如何从源设备环境复制到目的设备环境。ROP代码规定了源位图和当前目的设备如何组合来产生最终图片。ROP代码为SRCOPY,表示简单复制源图象。ROP代码为SRCPAINT,表示源图象和目的之间进行或操作。ROP代码为SRCINVERT,表示复制一个逻辑反转图象,本质上是一个负的源图象。一些ROP代码还将当前选择的画刷(brush)一起作为计算结果图象的因素。ROP代码很多,所以这里不可能覆盖全,要获得全部列表,请参考Windows CE编程文档。 下面的代码片段总结了如何绘制位图: // Create a DC that matches the device. hdcMem = CreateCompatibleDC (hdc);

// Select the bitmap into the compatible device context. hOldSel = SelectObject (hdcMem, hBitmap);

// Get the bitmap dimensions from the bitmap. GetObject (hBitmap, sizeof (BITMAP), &bmp);

// Copy the bitmap image from the memory DC to the screen DC. BitBlt (hdc, rect.left, rect.top, bmp.bmWidth, bmp.bmHeight, hdcMem, 0, 0, SRCCOPY);

// Restore original bitmap selection and destroy the memory DC. SelectObject (hdcMem, hOldSel); DeleteDC (hdcMem);

创建内存设备环境,即将被绘制的位图被选进该DC。因为您可能没有存储即将被绘制的位图尺寸,通常可以调用GetObject来获得。GetObject返回关于图形对象的信息,在本例中是一个位图。可以用这个很有用的函数来查询字体和其它图象对象的信息。接下来,使用BitBlit将位图复制进屏幕DC。为了清理,需要将位图从内存设备环境中取消选择,并使用DeleteDC将该内存DC删除。不要将DeleteDC同ReleaseDC混淆,ReleaseDC是释放一个显示DC。DeleteDC只应该和CreateCompatibleDC一起使用,ReleaseDC只应该和GetDC或GetWindowsDC一起使用。

用StretchBlt除了复制位图,还可以拉伸或者压缩位图。该函数原型如下:

BOOL StretchBlt (HDC hdcDest, int nXOriginDest, int nYOriginDest, int nWidthDest, int nHeightDest, HDC hdcSrc, int nXOriginSrc, int nYOriginSrc, int nWidthSrc, int nHeightSrc, DWORD dwRop);

StretchBlt里的参数和BitBlt里的基本相同,但是有一点例外的是现在可以指定源图象的宽度和高度。同样的,这里的ROP代码规定了源和目的之间如何组合来产生最终图象。 Windows CE还有另外一个位图函数TransparentImage,原型如下:

BOOL TransparentImage (HDC hdcDest, LONG DstX, LONG DstY, LONG DstCx, LONG DstCy, HANDLE hSrc, LONG SrcX, LONG SrcY, LONG SrcCx, LONG SrcCy, COLORREF TransparentColor); 该函数同StretchBlt类似,但有两个很重要的例外。首先,您可以指定位图里作为透明色的颜色。当把位图复制到目标中时,位图里透明色的像素不被复制。第二个不同是hSrc参数要么是设备环境要么是位图句柄,该参数允许您不必理会在屏幕上绘制图象前必须将源图象选进一个设备环境的要求。TransparentImage与Windows 2000中的TransparentBlt函数基本相同,除了TransparetBlt不能直接使用位图作为源图象以外。

和其它版本的Windows一样,Windows CE还支持2个其它blit函数:PatBlt和MaskBlt。PatBlt函数将当前选择的画刷同目的DC里当前图象进行组合,产生结果图象。我在本章后面会谈到画刷。MaskBlt函数与BitBlt类似,但包含一个掩码图象,用来提供只绘制源图象的一部分到目的DC里的功能。

线条和形状

同Windows其它版本相比,Windows CE提供相当少的功能的领域之一就是基本线条绘制和形状绘制功能。用来创建复杂环形的Chord, Arc, 和Pie函数被去掉了。大部分使用\当前点[current point]\概念的函数也被去掉了。除了MoveToEx, LineTo和GetCurrentPositionEx外,处理当前点的其它GDI函数都不被Windows CE支持。因此想用 ArcTo、PolyBezierTo等函数来绘制一系列连接的直线和曲线是不可能了。不过即使在缺少很多图形函数的情况下,Windows CE依然提供了绘制直线和形状所需要的基本函数。 线条

简单调用Polyline就可以绘制一个或者多个线条了。函数原型如下: BOOL Polyline (HDC hdc, const POINT *lppt, int cPoints); 第2个参数是指向POINT结构数组的指针,该结构定义如下: typedef struct tagPOINT {

LONG x; LONG y; } POINT

每个X、Y结合起来描述一个从屏幕左上角开始的像素。第三个参数是数组里的point结构的数量。因此绘制一个从(0,0)到(50,100)的直线,代码看起来如下: POINTS pts[2];

pts[0].x = 0; pts[0].y = 0; pts[1].x = 50; pts[1].y = 100;

PolyLine (hdc, &pts, 2);

绘制该直线的另外一个方法是使用MoveToEx和LineTo函数。它们的原型如下: BOOL WINAPI MoveToEx (HDC hdc, int X, int Y, LPPOINT lpPoint); BOOL WINAPI LineTo (HDC hdc, int X, int Y);

要使用这两个函数绘制线条,首先要调用MoveToEx将当前点移动到线条的起始坐标处,接下来用终点坐标调用LineTo。调用代码如下: MoveToEx (hdc, 0, 0, NULL); LineTo (hdc, 50, 100);

要查询当前点,可调用函数GetCurrentPositionEx,原型如下:

WINGDIAPI BOOL WINAPI GetCurrentPositionEx (HDC hdc, LPPOINT pPoint); 和前面绘制文本的例子一样,这些代码片段对设备描述表的状态做了大量假设。例如,绘制的(0, 0)和(50, 100)之间的线条是什么样子?宽度和颜色是什么以及是实心线条吗?所有版本的Windows,包括Windows CE在内,都允许指定这些参数。 画笔

画笔(pen)是用于指定线条外观和形状轮廓的工具。画笔是另一个GDI对象,像本章里描述的其它GDI对象一样,画笔要被创建、选进设备描述表,使用、取消选择,最后被销毁。 同其它备用GDI对象一样,可以使用GetStockObject来检索备用画笔。该函数原型如下: HGDIOBJ GetStockObject (int fnObject);

所有版本的Windows都提供三种备用画笔,每个1像素宽。这些备用画笔有3种颜色:白色、黑色和NULL。当您使用GetStockObject时,该函数分别使用参数WHITE_PEN, BLACK_PEN和NULL_PEN来检索这些画笔中的一个。与应用程序创建的标准图形对象不同,备用对象不应该被应用程序删除。相反,当画笔不再需要的时候,应用程序只应简单地将画笔从设备描述表中取消选择即可。

要在Windows下创建自定义画笔,可以使用以下两个函数。第一个是: HPEN CreatePen (int fnPenStyle, int nWidth, COLORREF crColor);

fnPenStyle规定要绘制的线条的外观。例如,使用PS_DASH标志可以创建一个虚线。Windows CE只支持PS_SOLID、PS_DASH和PS_NULL这三个风格标志。nWidth参数规定画笔的宽度。最后,crColor规定画笔的颜色。crColor的参数类型是COLORREF,可以使用RGB宏来构造该类型。RGB宏定义如下:

COLORREF RGB (BYTE bRed, BYTE bGreen, BYTE bBlue); 因此要创建一个红色实心线条,代码看起来像这样: hPen = CreatePen (PS_SOLID, 1, RGB (0xff, 0, 0)); 另一种画笔创建函数如下:

HPEN CreatePenIndirect (const LOGPEN *lplgpn); 其中逻辑画笔结构LOGPEN定义如下: typedef struct tagLOGPEN { UINT lopnStyle; POINT lopnWidth; COLORREF lopnColor; } LOGPEN;

CreatePenIndirect用不同的形式为Windows提供了同样的参数。用CreatePenIndirect创建同样是1像素宽的红色画笔,代码如下: LOGPEN lp; HPEN hPen;

lp.lopnStyle = PS_SOLID; lp.lopnWidth.x = 1; lp.lopnWidth.y = 1;

lp.lopnColor = RGB (0xff, 0, 0);

hPen = CreatePenIndirect (&lp);

Windows CE不支持复杂画笔,比如宽度超过1像素的虚线。要确定支持什么,我们熟悉的GetDeviceCaps就派上用场了,给它的第2个参数取LINECAPS即可。具体可以参考Windows CE文档。 形状

线条很有用,不过Windows还提供了绘制形状的函数,包括填充和非填充的形状。Windows CE提供了Windows程序员大部分常见的函数。

Rectangle, RoundRect, Ellipse和Polygon都支持。 画刷

在讨论矩形和椭圆形之前,需要先讲述另一个曾经简要提到过的GDI对象--画刷(brush)。画刷通常是一个8*8像素的位图,用于填充形状。

Windows也用它来填充客户窗口地背景。Windows CE提供许多备用画刷,并提供从应用程序定义的图案创建画刷的能力。许多纯色备用画刷可以使用GetStockObject来检索。在众多可用画刷中,有一个是用于四色灰度级显示器的每个灰度的,四种灰度是:白色、浅灰色、深灰色和黑色。

要创建纯色画刷,可以调用以下函数:

HBRUSH CreateSolidBrush (COLORREF crColor); crColor规定了画刷的颜色。颜色可以使用RGB宏来指定。

Windows CE下用Win32函数 CreateDIBPatternBrushPt创建自定义图案的画刷。函数原型如下:

HBRUSH CreateDIBPatternBrushPt (const void *lpPackedDIB, UINT iUsage);

第一个参数指向紧凑格式的DIB。这意味着指针指向一个缓冲区,包含有BITMAPINFO结构,并且紧随其后的是位图的位数据。您应该还记得BITMAPINFO结构实际上由

BITMAPINFOHEADER结构及紧随其后的RGBQUAD格式的调色板构成,所以该缓冲区包含了创建DIB所需要的每个信息,即位图信息、调色板、位图的位数据。如果第二个参数设置为DIB_RGB_COLORS,则调色板在每个入口都包含有RGBQUAD值。对于每像素8位的位图来说,可以设置DIB_PAL_COLORS标志,但Windows CE会忽略位图的颜色表。 在Windows CE下CreateDIBPatternBrushPt更加重要,因为Windows CE下不支持阴影画刷,而其它版本的Windows使用CreateHatchBrush函数来支持阴影画刷。阴影画刷是由水平线条、垂直线条或斜线构成的画刷。这些画刷在灰度级显示器上特别有用,因为您可以使用不同阴影图案来突出图表的不同区域。不过,您可以使用CreateDIBPatternBrushPt和适当的位图图案来复制出这些画刷。本章后面的示例代码演示了在Windows CE下创建阴影画刷的方法。

默认情况下,画刷原点在窗口左上角。这并不总是你所希望的。例如,一个条形图使用阴影画刷填充从(100,100)到(125,220)的矩形。因为该矩形不能被8(画刷通常是8*8像素的正方形)整除,所以就用一个不怎么美观的局部画刷来填充条形图的左上角。为了避免这种情况,您可以移动画刷的原点,这样可以使用与形状的边角正确对齐的画刷来绘制各个形状了。用来完成这一调整的函数如下:BOOL SetBrushOrgEx (HDC hdc, int nXOrg, int nYOrg, LPPOINT lppt);

nXOrg和nYOrg允许将原点设置为0到7之间,这样就可以将原点定位在画刷8*8范围内任何一点。lppt填充的是画刷先前的版本,这样可以在必要的时候恢复先前的原点。 矩形

矩形函数绘制一个填充矩形或者一个中空矩形。该函数定义如下:

BOOL Rectangle (HDC hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);

该函数使用当前选择的画笔绘制矩形外框,使用当前画刷填充内部。要绘制中空矩形,需要在调用Rectangle之前把空画刷选择进设备描述表中。

理解绘制边框的实际像素是很重要的。假定我们要在(0,0)处绘制一个5*7的矩形,函数调用如下:

Rectangle(0,0,5,7);

假设画笔是1像素宽,结果矩形如图2-6所示。 图2-6:放大后的用Rectangele绘制的矩形视图。

注意观察矩形右边界实际上是如何绘制到第4列的,底部边缘是如何绘制到第6行的。这是标准的Windows惯例。矩形在Rectangle函数指定的右边界和底边界以内进行绘制。如果选择的画笔宽度超过1个像素,右边界和底边界以边框矩形居中进行绘制。(Windows其它版本允许使用PS_INSIDEFRAME画笔风格忽略画笔宽度,强制在框架内绘制矩形) 圆和椭圆

可以用Ellipse函数绘制圆和椭圆,该函数原型如下:

BOOL Ellipse (HDC hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);

使用传入的矩形作为边界矩形(bounding rectangle)来绘制椭圆,如图2-7所示。对于Rectangle函数,使用当前画刷来填充椭圆内部,使用当前画笔来绘制椭圆外框。 图2-7:展示了Ellipse使用传入的边界矩形绘制出的椭圆。

圆角矩形

RoundRect函数绘制一个圆角矩形,原型如下:

BOOL RoundRect (HDC hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect, int nWidth, int nHeight);

最后两个参数给出了用于圆角的椭圆的宽度和高度,如图2-8所示。指定椭圆的高度和宽度可以让程序绘制出完全一样的均匀的圆角。缩短椭圆的高度,可以使矩形的两侧更平,而缩短椭圆的宽度可以使矩形的顶部和底部更平。

图2-8:椭圆的高度和宽度规定了由RoundRect绘制的矩形的圆角 多边形

最后,Polygon绘制了一个多边形,函数原型如下:BOOL Polygon (HDC hdc, const POINT *lpPoints, int nCount);

第2个参数是一个指向Point结构数组的指针,该数组定义了描述多边形的各个点。从最终形状上看,会被点数多一条边,因为函数自动把最后一个点和第一个点连接起来,绘制出多边形的最后一条边。 填充函数

前面提到的函数都是使用画刷和画笔的组合在设备描述表上绘制形状的。只填充区域而不涉及绘制形状轮廓的画笔的函数也是有的。这些函数中的第一个就如下所示: int FillRect (HDC hDC, CONST RECT* lprc, HBRUSH hbr);

FillRect的参数设备描述表句柄、需要填充的矩形以及用来填充矩形的画刷。要在矩形区域里绘制一个纯色或者图案,使用FillRect函数会是一个快捷方便的方式。

虽然FillRect很方便,但GradientFill可能更棒一些。GradientFill函数填充一个矩形区域,使用一个颜色从一边开始绘制,并逐渐平滑过度到另外一个颜色直到另外一边。图2-9展示了一个客户区使用GradientFill进行绘制的窗口。书中印刷的黑白插图不能看出颜色的效果,但即使在这样的图上,依然能轻易的看到平滑过度的样子。 图2-9:使用GradientFill函数绘制的窗口。 GradientFill函数原型如下:

BOOL GradientFill (HDC hdc, PTRIVERTEX pVertex, ULONG dwNumVertex, PVOID pMesh, ULONG dwNumMesh, ULONG dwMode);

第一个参数依旧是设备描述表。pVertex是指向TRIVERTEX结构数组的指针,dwNumVertex是TRIVERTEX数组中入口的数量。TRIVERTEX结构定义如下: struct _TRIVERTEX { LONG x; Long y; COLOR16 Red; COLOR16 Green; COLOR16 Blue; COLOR16 Alpha;s } TRIVERTEX;

TRIVERTEX结构的各个域描述了设备描述表里的一个点和一个RGB颜色。这些点应该是要填充的矩形的左上角和右下角。pMesh是指向GRADIENT_RECT结构的指针,该结构定义如下: struct _GRADIENT_RECT {

ULONG UpperLeft;

ULONG LowerRight; } GRADIENT_RECT;

GRADIENT_RECT结构简单指出TRIVERTEX结构中的哪些入口是描述左上角或者是右下角的。最后,dwNumMesh参数包含GRADIENT_RECT结构的数量。dwMode结构包含标志位,指出是从左到右(GRADIENT_FILL_RECT_H)填充还是从上到下

(GRADIENT_FILL_RECT_V)填充。GradientFill函数实际上比表面上看到的要更复杂,因为在桌面系统里,它还执行三角形填充,这种填充方式在Windows CE下不支持。下面是创建图2-9的窗口的代码片段: TRIVERTEX vert[2]; GRADIENT_RECT gRect;

vert [0] .x = prect->left; vert [0] .y = prect->top; vert [0] .Red = 0x0000; vert [0] .Green = 0x0000; vert [0] .Blue = 0xff00; vert [0] .Alpha = 0x0000;

vert [1] .x = prect->right; vert [1] .y = prect->bottom; vert [1] .Red = 0x0000; vert [1] .Green = 0xff00; vert [1] .Blue = 0x0000; vert [1] .Alpha = 0x0000;

gRect.UpperLeft = 0; gRect.LowerRight = 1;

GradientFill(hdc,vert,2,&gRect,1,GRADIENT_FILL_RECT_H); Shapes示例程序

清单2-3中,Shapes程序演示了很多相关函数。在Shapes中,绘制了4个图形,每个使用不同的画刷进行了填充。Listing 2-3: Shapes示例程序 Shapes.h

//================================================================ // Header file //

// Written for the book Programming Windows CE // Copyright (C) 2003 Douglas Boling

//====================================================================== // Returns number of elements

#define dim(x) (sizeof(x) / sizeof(x[0]))

//---------------------------------------------------------------------- // Generic defines and data types //

struct decodeUINT { // Structure associates UINT Code; // messages // with a function. LRESULT (*Fxn)(HWND, UINT, WPARAM, LPARAM); };

struct decodeCMD { // Structure associates UINT Code; // menu IDs with a

LRESULT (*Fxn)(HWND, WORD, HWND, WORD); // function. };

//---------------------------------------------------------------------- // Defines used by MyCreateHatchBrush //

typedef struct {

BITMAPINFOHEADER bmi; COLORREF dwPal[2]; BYTE bBits[64]; } BRUSHBMP;

#define HS_HORIZONTAL 0 /* ----- */ #define HS_VERTICAL 1 /* ||||| */ #define HS_FDIAGONAL 2 /* \\\\\\\\\\ */ #define HS_BDIAGONAL 3 /* ///// */ #define HS_CROSS 4 /* +++++ */ #define HS_DIAGCROSS 5 /* xxxxx */

//---------------------------------------------------------------------- // Function prototypes //

HWND InitInstance (HINSTANCE, LPWSTR, int); int TermInstance (HINSTANCE, int);

// Window procedures

LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM); // Message handlers

LRESULT DoPaintMain (HWND, UINT, WPARAM, LPARAM); LRESULT DoDestroyMain (HWND, UINT, WPARAM, LPARAM); Shapes.cpp

//======================================================================

// Shapes- Brush and shapes demo for Windows CE

//

// Written for the book Programming Windows CE // Copyright (C) 2003 Douglas Boling

//======================================================================

#include // For all that Windows stuff #include \// Program-specific stuff

//---------------------------------------------------------------- // Global data //

const TCHAR szAppName[] = TEXT (\

HINSTANCE hInst; // Program instance handle

// Message dispatch table for MainWindowProc const struct decodeUINT MainMessages[] = { WM_PAINT, DoPaintMain, WM_DESTROY, DoDestroyMain, };

//====================================================================== //

// Program entry point //

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) { MSG msg; HWND hwndMain;

// Initialize this instance.

hwndMain = InitInstance(hInstance, lpCmdLine, nCmdShow); if (hwndMain == 0) return 0x10;

// Application message loop

while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); }

// Instance cleanup

return TermInstance (hInstance, msg.wParam); }

//----------------------------------------------------------------------

// InitInstance - Instance initialization //

HWND InitInstance (HINSTANCE hInstance, LPWSTR lpCmdLine, int nCmdShow){ WNDCLASS wc; HWND hWnd;

// Save program instance handle in global variable. hInst = hInstance;

#if defined(WIN32_PLATFORM_PSPC)

// If Pocket PC, allow only one instance of the application. hWnd = FindWindow (szAppName, NULL); if (hWnd) {

SetForegroundWindow ((HWND)(((DWORD)hWnd) | 0x01)); return 0; } #endif

// Register application main window class. wc.style = 0; // Window style

wc.lpfnWndProc = MainWndProc; // Callback function wc.cbClsExtra = 0; // Extra class data wc.cbWndExtra = 0; // Extra window data wc.hInstance = hInstance; // Owner handle wc.hIcon = NULL, // Application icon

wc.hCursor = LoadCursor (NULL, IDC_ARROW);// Default cursor wc.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH); wc.lpszMenuName = NULL; // Menu name

wc.lpszClassName = szAppName; // Window class name

if (RegisterClass (&wc) == 0) return 0; // Create main window.

hWnd = CreateWindowEx (WS_EX_NODRAG, // Ex Style szAppName, // Window class TEXT(\// Window title WS_VISIBLE, // Style flags CW_USEDEFAULT, // x position CW_USEDEFAULT, // y position CW_USEDEFAULT, // Initial width CW_USEDEFAULT, // Initial height NULL, // Parent

NULL, // Menu, must be null hInstance, // Application instance NULL); // Pointer to create // parameters

// Return fail code if window not created. if (!IsWindow (hWnd)) return 0;

// Standard show and update calls ShowWindow (hWnd, nCmdShow); UpdateWindow (hWnd); return hWnd; }

//---------------------------------------------------------------------- // TermInstance - Program cleanup //

int TermInstance (HINSTANCE hInstance, int nDefRC) {

return nDefRC; }

//======================================================================

// Message handling procedures for MainWindow //

//---------------------------------------------------------------------- // MainWndProc - Callback function for application window //

LRESULT CALLBACK MainWndProc (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) { INT i; //

// Search message list to see if we need to handle this // message. If in list, call procedure. //

for (i = 0; i < dim(MainMessages); i++) { if (wMsg == MainMessages[i].Code)

return (*MainMessages[i].Fxn)(hWnd, wMsg, wParam, lParam); }

return DefWindowProc (hWnd, wMsg, wParam, lParam); }

//---------------------------------------------------------------- // MyCreateHatchBrush - Creates hatched brushes //

HBRUSH MyCreateHatchBrush (INT fnStyle, COLORREF clrref) { BRUSHBMP brbmp; BYTE *pBytes; int i;

DWORD dwBits[6][2] = {

{0x000000ff,0x00000000}, {0x10101010,0x10101010}, {0x01020408,0x10204080}, {0x80402010,0x08040201}, {0x101010ff,0x10101010}, {0x81422418,0x18244281}, };

if ((fnStyle < 0) || (fnStyle > dim(dwBits))) return 0;

memset (&brbmp, 0, sizeof (brbmp));

brbmp.bmi.biSize = sizeof (BITMAPINFOHEADER); brbmp.bmi.biWidth = 8; brbmp.bmi.biHeight = 8; brbmp.bmi.biPlanes = 1; brbmp.bmi.biBitCount = 1; brbmp.bmi.biClrUsed = 2; brbmp.bmi.biClrImportant = 2;

// Initialize the palette of the bitmap.

brbmp.dwPal[0] = PALETTERGB(0xff,0xff,0xff);

brbmp.dwPal[1] = PALETTERGB((BYTE)((clrref >> 16) & 0xff), (BYTE)((clrref >> 8) & 0xff), (BYTE)(clrref & 0xff));

// Write the hatch data to the bitmap. pBytes = (BYTE *)&dwBits[fnStyle]; for (i = 0; i < 8; i++)

brbmp.bBits[i*4] = *pBytes++;

// Return the handle of the brush created.

return CreateDIBPatternBrushPt (&brbmp, DIB_RGB_COLORS); }

//---------------------------------------------------------------------- // DoPaintMain - Process WM_PAINT message for window. //

LRESULT DoPaintMain (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; RECT rect; HDC hdc;

POINT ptArray[6]; HBRUSH hBr, hOldBr; TCHAR szText[128];

GetClientRect (hWnd, &rect);

hdc = BeginPaint (hWnd, &ps);

// Draw ellipse.

hBr = (HBRUSH) GetStockObject (DKGRAY_BRUSH); hOldBr = (HBRUSH) SelectObject (hdc, hBr); Ellipse (hdc, 10, 50, 90, 130); SelectObject (hdc, hOldBr);

// Draw round rectangle.

hBr = (HBRUSH) GetStockObject (LTGRAY_BRUSH); hOldBr = (HBRUSH) SelectObject (hdc, hBr); RoundRect (hdc, 95, 50, 150, 130, 30, 30); SelectObject (hdc, hOldBr);

// Draw hexagon using Polygon.

hBr = (HBRUSH) GetStockObject (WHITE_BRUSH); hOldBr = (HBRUSH) SelectObject (hdc, hBr); ptArray[0].x = 192; ptArray[0].y = 50; ptArray[1].x = 155; ptArray[1].y = 75; ptArray[2].x = 155; ptArray[2].y = 105; ptArray[3].x = 192; ptArray[3].y = 130; ptArray[4].x = 230; ptArray[4].y = 105; ptArray[5].x = 230; ptArray[5].y = 75;

Polygon (hdc, ptArray, 6); SelectObject (hdc, hOldBr);

hBr = (HBRUSH) MyCreateHatchBrush (HS_DIAGCROSS, RGB (0, 0, 0)); hOldBr = (HBRUSH) SelectObject (hdc, hBr); Rectangle (hdc, 10, 145, 225, 210); SelectObject (hdc, hOldBr); DeleteObject (hBr);

SetBkMode (hdc, OPAQUE);

lstrcpy (szText, TEXT (\ ExtTextOut (hdc, 20, 160, 0, NULL, szText, lstrlen (szText), NULL);

SetBkMode (hdc, TRANSPARENT);

lstrcpy (szText, TEXT (\ ExtTextOut (hdc, 20, 185, 0, NULL, szText, lstrlen (szText), NULL);

EndPaint (hWnd, &ps); return 0; }

//---------------------------------------------------------------------- // DoDestroyMain - Process WM_DESTROY message for window //

LRESULT DoDestroyMain (HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam) { PostQuitMessage (0); return 0; }

在Shapes里,OnPaintMain使用先前讨论过的那些不同的函数绘制了四个图形。对每个形状,都创建了一个不同的画刷,并选择进设备描述表,绘制图形,最后从设备描述表中取消选择。第一个图形使用纯灰色阴影来填充,用的是GetStockObject函数来装载这些纯色画刷的。最后一个图形则是使用由CreateDIBPatternBrushPt函数创建的画刷来填充的。该画刷的创建过程封装到MyCreateHatchBrush函数里了,该函数模拟了Windows CE下不支持的CreateHatchBrush函数。通过填充一个位图结构和设置构成阴影图案的位数据,创建了一个黑白位图,用于创建阴影画刷。这个位图是一个由CreateDIBPatternBrushPta函数指定的8*8的位图。因为该位图是单色的,所以包括调色板和头在内,它的总大小只有大约100字节。注意,因为位图的每个扫描行必须是双字对齐的,所以每个一字节扫描行的最后3个字节是没有使用的。

最后,程序写两行文字到最后的矩形里。文本进一步演示了opaque和transparent这两个系统绘图模式之间的差异。在本例中,文本绘制时使用opaque模式会更适合实际情况,因为在transparent模式下,阴影线会使字母变的模糊。图2-10展示了Shapes窗口。 图2-10演示了如何绘制不同填充的图形。

为了使事情简单一些,例子Shapes假设其运行在至少240像素宽的显示器上。这使得Shapes程序在手持式PC和Pocket PC上都能很好的运行。我只是浅显的描述了Windows CE中GWE的GDI部分,本章的目标并不是为GDI编程的各方面提供完整的描述。相反,我想去演示Windows CE下用于基本图画和文本的方法。在本书其它章节里,扩展了本章涉及的技术。通常,在演示如何在代码中使用的时候,我会谈论这些新技术和新引入的函数。为了扩展您的知识,我推荐《Windows 程序设计》(Programming Windows, 5th edition, by Charles Petzold)(Microsoft Press, 1998)一书作为学习Windows GDI的最佳资料。

既然我们已经看了输出,那么是时候把注意力转到系统输出--键盘和触摸板方面了。

本文来源:https://www.bwwdw.com/article/9mgp.html

Top