首页 > 学院 > 开发设计 > 正文

Windows编程基础 - 设备对象属性

2019-11-17 05:18:28
字体:
来源:转载
供稿:网友

  设备对象属性决定着绘图方式,当使用GDI函数绘图时,所绘制的图形和文本的颜色、大小和位置等由设备对象的当前属性决定,应用程序可以使用GDI函数改变设备对象的当前属性。本章介绍设备对象的一些主要属性和用于改变这些属性的有关函数,其它的设备对象属性在后面的章节中陆续介绍。

3.1、图形设备接口
  在第二章,我们一直在使用图形设备接口(GDI),GDI的主要目标之一是支持在输出设备(例如显示器、打印机)上建立与设备无关的图形输出。Windows的图形大部分是由GDI.EXE(Windows系统的一个模块,称GDI模块)中的函数处理的。GDI模块通过调用在不同设备驱动程序模块中的例程来控制输出设备。例如,显示设备驱动程序用于访问与视频显示器有关的硬件。通过GDI,Windows可以确定驱动程序能够进行什么工作,并且由于应用程序只与GDI打交道。这样,GDI通过将应用程序与不同的输出设备隔离,使应用程序可以在支持Windows的任何图形输出设备上工作。
  图形输出设备可以分为两组:光栅设备和矢量设备。光栅设备将图象表示为点(象素)的图案。这类输出设备包括显示设备、点阵打印机和激光打印机。向量设备用线段来绘制图象,例如绘图仪。Windows的GDI是一种隔离了硬件具体特性的图形语言。虽然输出设备用象素来表示图形,但GDI却可以被用作一个高级的向量绘图系统,也可以被用来进行较低级的象素操作。
  在编写Windows应用程序时,程序员不必为颜色过分担心,假如在应用程序中使用的一种颜色不能被该显示器所表示,Windows或为应用程序选择一种最直接的纯颜色(显示设备可以表示的颜色),或通过将几种纯颜色相混合来表示这种颜色。当在彩色显示器上开发的程序运行在单色显示器上时,Windows将使用灰度来表示颜色。应用程序也可以在程序中确定输出设备的有关特性,例如,可表示的颜色数目、设备的显示区的尺寸等,以便最大限度地发挥硬件的能力。

3.2、设备对象属性
  设备对象具有许多决定GDI函数在设备对象上如何工作的当前属性。例如,在使用函数TextOut()时,只需要在函数中说明设备对象的句柄、绘制字符的起始坐标、文本和文本长度,而不用说明字体、文本颜色、背景颜色和字符间距等,因为这些特征由设备对象的属性决定。每种设备对象都赋有缺省的属性,可以使用GDI函数改变这些属性中的某一个。表3-1给出了显示设备各个属性的缺省值,当使用GetDC()和BeginPaint()等函数初次得到一个显示设备对象时,该对象的属性具有缺省值。

属性缺省值视区原点(0, 0)视区范围(1, 1)窗口原点(0, 0)窗口范围(1, 1)背景颜色白色背景方式OPAQUE位图任意值刷子WHITE_BRUSH刷子原点(0, 0)裁剪区用户区/无效矩形区/子窗口区调色板DEFAULT_PALETTE 属性缺省值笔的当前位置(0, 0)笔的颜色BLACK_PEN文本颜色黑设备的原点用户区的左上角绘图方式R2_COPYPEN字体SYSTEM_FONT字符间距0映射方式MM_TEXT多边形填充方式ALTERNATE相对一绝坐标ABSOLUTE缩放方式BLACKONWHITE
  在本章以后的各节中将介绍其中一些设备属性,其它属性在以后章节中介绍。

3.3、设备坐标系
  为在输出设备上定位和绘制图形对象,必须引入一种坐标系。Windows的各种不同类型设备所使用的坐标称为设备坐标。它们使用笛卡尔坐标系,在这些设备坐标系中,单位都以象素的个数表示(称为设备单位)。x轴上的值自左向右增加,y轴上的值自顶向下增加,见图3-1。

Windows编程基础 - 设备对象属性(图一)图3-1 Windows的设备坐标系
  本节以视频显示设备为例介绍Windows的设备坐标系,其中的许多内容也适合用于象打印机等硬拷贝输出设备。
  在Windows环境中,视频显示设备是一个共享设备,即在同一时刻,显示设备上可以同时显示多个应用程序的输出信息。为了保护一个程序显示的信息不被其他程序破坏,Windows通过将显示区看作不同的设备对象来限制应用程序输出信息的范围。
  一个应用程序可获取三种不同的显示设备对象句柄,每种句柄所标识的设备对象代表屏幕上的不同区域。我们可以将这三个句柄所标识的对象视作三个不同的抽象显示设备,都带有如图3-1所示的设备坐标系,但对不同的抽象设备,坐标原点不一样的。这样,当使用相同的起始坐标而使用不同的设备对象句柄调用GetDC函数(例如TextOut())进行绘图时,信息显示的位置不一样。
  第一抽象设备是用户区对象,它的坐标原点在用户区的左上角。定位该区域的设备坐标系称为用户区坐标系。使用函数GetDC()或BeginPaint()得到的句柄是标识用户区的句柄。当使用该句柄标识该区域的句柄作为GDI函数的参数时,GDI函数所使用的坐标值是相对于用户区坐标系。
  第二个抽象设备是全窗口对象。它包括标题栏、选单、滚动杆和窗口框架等。定位这个区域的坐标系称为全窗口坐标系,它的原点的左上角。使用函数GetWindowsDC()可以获得该设备对象的句柄,然后通过该句柄使用全窗口坐标系在该区域中绘图。应用程序一般不在这个区域中绘图。
  第三个抽象设备是整个屏幕对象,其坐标原点在屏幕的左上角,定位该区域的设备坐标系称为屏幕坐标系。使用语句:

  HDC hDC = CreateDC("DISPLAY", NULL, NULL, NULL);

可以获得该设备对象的句柄,使用该句柄的GDI函数所使用的坐标是相对于屏幕坐标系。
  这三种坐标系方便了程序在不同的区域绘制图形的需要。例如,由于使用用户区坐标系,即使窗口在屏幕上被移动到其他位置,但用户区中显示的信息相对于用户坐标系而言其坐标值不变。
  使用坐标系,解决了显示对象的定位的问题的。但以象素单位所建立的坐标系不符合用户(或程序员)的习惯,而且在不同分辨率的输出设备上,一个象素的大小也不相同的,这样,应用程序的输出在不同机器上会得到不同的结果。为解决这个问题,Windows引入了逻辑坐标系。它使得用户可以按照自己习惯使用的尺寸(毫米、英寸等)来描述客体,或绘制图形。用户在程序中使用逻辑坐标系中的坐标值,而由Windows根据映射方式将逻辑坐标系中的坐标值转算成设备坐标系中的坐标值(对于显示设备而言,具体映射到三个坐标系中哪一个取决于GDI函数中所使用的设备对象句柄)。映射方式也同时决定着逻辑坐标值的单位(简称逻辑单位)和坐标轴的正方向。在前面介绍的TextOut和DrawText()使用的是逻辑单位,而函数CreateWindow()使用的是屏幕坐标系中的坐标(设备单位,及象素)。程序员应该清楚什么时候在使用逻辑单位。一般而言,当映射方式起作用时,使用的是逻辑单位,而映射方式起作用的唯一时候是使用了那些需要将设备对象句柄作为参数的GDI函数的时候,但并非总是这样,因此,读者应注重这方面的问题。
  表3-2给出的两个函数用于进行用户区坐标和屏幕坐标(注重是设备单位)之间的转换。

  表3-2-1 ClientToScreen 函数
用 途将一个点的用户区坐标转换为屏幕坐标。原 型VOID ClientToScreen(    HWND hWnd, 与用户区相关联的窗口句柄  LPPOINT lpPoint 指向一个POINT结构类型的变量,其中一个点的用户区坐标值,并被转换后的屏幕坐标值取代。); 
  表3-2-2 ScreenToClient 函数
用 途将给定点的屏幕坐标转换为用户区坐标。原 型VOID ScreenToClient(    HWND hWnd, 与用户区相关联的窗口句柄  LPPOINT lpPoint 所指向的变量包含一个点的屏幕坐标值,并被转换后的用户区坐标值取代); 
3.4、映射方式
  影响在用户区绘图的一个主要设备对象属性是“映射方式”。它定义了Windows如何将GDI函数中使用的逻辑坐标映射为设备坐标。其他四个设备对象属性(窗口原点、视区原点、窗口范围和视区范围)都与映射方式密切相关。当Windows将逻辑单位转换为设备单位或象素时,映射方式、窗口原点和视区原点、窗口范围和视区反问决定着这种转换。映射方式也隐含了x轴坐标和y轴的原点和方向。在介绍各种映射方式之前,我们先介绍窗口与视口关系。

  3.4.1 窗口与视口
  视口是计算机屏幕上一块显示区域,随着在GDI中所使用的设备对象句柄的不同,该区域可是用户区、全窗口区或整个屏幕区,视区中的图形一设备单位定义。与视区中显示的图形相对应的原始图形区域称为窗口。注重,在这里使用的术语“窗口”不是指屏幕上显示的可视窗口对象。这里的“窗口”是从现实世界角度所看到图形,而“视区”是从数据世界角度而言的,是屏幕上的象素形成的图形。在Windows中,视区不是一个裁剪区。图3-2和图3-3说明了窗口和视口的关系。

Windows编程基础 - 设备对象属性(图二)图3-2 营业收入曲线 Windows编程基础 - 设备对象属性(图三)图3-3 视区中显示的曲线
  映射方式指的是从“窗口”(逻辑坐标)到“视区”(设备坐标)的变换。“视区”采用设备坐标(象素数)。“窗口”采用逻辑坐标,它可以是象素数、毫米、英寸或任何其他单位。GDI函数使用逻辑坐标。
  将“窗口”(逻辑)坐标转换为“视区”(设备)坐标,使用如下两个映射方式:

xViewport = (xWindow - xWinOrg) * xViewExt / xWinExt + xViewOrg
yViewport = (yWindow - yWinOrg) * yViewExt / yWinExt + yViewOrg

  该公式将逻辑坐标系中的点(xWindow, yWindow)变换为设备坐标系中的点(xViewport, yViewport),其中,点(xWinOrg, yWinOrg)是以逻辑单位表示的“窗口”原点,而点(xViewOrg, yViewOrg)是以设备坐标表示的“视区”原点。在缺省情况下,这两个点被设置为(0, 0),但它们可以被改变。注重,逻辑点(xWinOrg, yWinOrg)总是被映射为设备点(xViewOrg, yViewOrg)。
  改变上述的公式为:

xWindow = (xViewport - xViewOrg) * xWinExt / xViewExt + xWinOrg
yWindow = (yViewport - yViewOrg) * yWinExt / xViewExt + yWinOrg

  该公式可以将视区坐标转换为窗口坐标。

  表3-3-1 DptoLP 函数
用 途将设备点变换为逻辑点。原 型BOOL DPtoLP(   HDC hDC,设备对象句柄  LPPOINT lpPoints, 指向POINT类型的变量的指针  int nCount要进行变换的点的数目) 返回值若变换成功,返回非零。
  表3-3-2 LPtoDP 函数
用 途将逻辑点变换为设备点。原 型BOOL LPtoDP(   HDC hDC,设备对象句柄  LPPOINT lpPoints, 指向POINT类型的变量的指针  int nCount欲进行变换的点的数目) 返回值若所有的点被变换,返回非零。
  表3-3给出了两个函数,用于进行设备坐标和逻辑坐标之间的相互变换。例如,函数GetClientRect获取的拥护区域的大小总是以设备单位表示的,若想使用逻辑单位表示用户区大小,可以使用函数DPtoLP();

  RECT rect;
  GetClient(hWnd, &rect);
  DPtoLP(hDC, (LPPOINT)&rect, 2);

  3.4.2 Windows的映射方式
  Windows定义了八种映射方式,见表3-4。

  表3-4 Windows的映射方式    映射方式逻辑单位单位x轴方向y轴方向MM_TEXT象素数向右向下MM_LOMETRIC0.1mm向右向上MM_HIMETRIC0.01mm向右向上MM_LOENGLISH0.01英寸向右向上MM_HIENGLISH0.001英寸向右向上MM_TWipS1/1440英寸向右向上MM_ISOTROPIC自定义(x=y)(即x和y的逻辑单位大小一样)由比例因子决定若为正,向右。否则,向左由比例因子决定若为正,向下。否则,向上MM_ANISOTROPIC自定义(x!=y)(即x和y的逻辑单位大小不一样)同上同上

  注:Twips表示“一个点的二十分之一”,在GDI中,一个点的大小定为1/72英寸,所以一个twips是1/1440英寸。
  在缺省时,设备对象使用的映射方式是MM_TEXT,即逻辑单位等于物理单位。例如:

  TextOut(hDC, 4, 6, "Hello", 5);

  在用户区向右偏移4个象素,向下偏移6个象素的位置开始显示信息“Hello”。
  可以使用函数SetMapMode()设置映射方式,或使用函数GetMapMode()获取一个设备对象当前的映射方式,见表3-5,例如,语句:

  SetMapMode(hDC, MM_LOMETRIC);
  TextOut(hDC, 100, -200, "Hello", 5);

  首先设置MM_LOMETRIC映射方式,然后在离用户区原点向右1厘米、向下2厘米的位置显示信息“Hello”。这里使用了负的坐标值,因为根据窗口原点变换到视口原点的原则,只有窗口坐标的y坐标为负值的点才可以变换到用户区中,y坐标为正值的点落在用户区之外。

  表3-5-1 SetMapMode 函数
用 途设置设备对象的映射方式。原 型int SetMapMode(   HDC hDC,设备对象句柄  int nMapMode 所欲设置的映射方式,使用表3-4中常量之一) 返回值先前的映射方式。
  表3-5-1 GetMapMode 函数
用 途检索当前的映射方式。原 型int GetMapMode(   HDC hDC,设备对象句柄) 返回值当前的映射方式。
  Windows函数中指定的所有坐标值必须在-32768到32767之间的值(这是C语言的int类型的数据的值域)。
  在任何映射方式下,逻辑坐标和物理坐标的缺省原点都为(0, 0),即逻辑坐标的点(0, 0)映射到设备坐标的点(0, 0)。

  3.4.3 改变视区和窗口的原点
  表3-6说明的函数可用于改变视区和窗口的原点,但这两个函数不能同时使用。

  表3-6-1 SetViewportOrg 函数
用 途设置视区的坐标原点。原 型DWord SetViewportOrg(   HDC hDC,设备对象句柄  int x,指定视区坐标原点的x坐标值(设备单位)。  int y,指定视区坐标原点的y坐标值(设备单位)。) 返回值返回以前视区坐标原点(设备单位),高位字为y坐标,低位字为x坐标。
  表3-6-2 SetWindowOrg 函数
用 途设置窗口坐标的原点。原 型DWORD SetWindowOrg(   HDC hDC,设备对象句柄  int x,指定窗口坐标原点的x坐标值(逻辑单位)。  int y,指定窗口坐标原点的y坐标值(逻辑单位)。) 返回值返回以前视区坐标原点(设备单位),高位字为y坐标,低位字为x坐标。
  无论怎样改变窗口和视区的原点,Windows都将窗口原点变换到视区原点,并按同样的映射算法(即使用当前给出的映射公式)变换其余的点。
  我们先介绍MM_TEXT映射方式下这两个函数的工作原理。因为这种映射方式的x坐标和y坐标方向与我们阅读文本时的方向一致。因此,MM_TEXT映射方式又称为“文本”映射方式。

Windows编程基础 - 设备对象属性(图四)图3-4
  现假定用户区的宽度为xClient个象素,高为yClient给象素,则语句:

  SetViewportOrg(hDC, xClient/2, yClient/2);

  将逻辑点(0, 0)确定在(映射到)用户区的中心。

Windows编程基础 - 设备对象属性(图五)图3-5
  这时,若使用语句:

  TextOut(hDC, 0, 0, "Hello", 5);

  将在用户区中心位置开始显示信息。而语句:

  TextOut(hDC, -xClient/2, -yClinent/2, "Hello", 5);

  在用户区的左上角(设备点(0, 0))处开始显示文本。
  通过使用函数SetWindowOrg可以得到与使用函数SetViewpostOrg相同的结果,例如:

  SetWindowOrg(hDC, -xClient/2, -yClient/2);

  这使得逻辑点(-xClient/2, -yClient/2)被映射为设备点(0, 0)。
  函数GetViewportOrg和GetWindowOrg可分为别用于获取当前视区和窗口的原点,见表3-7。

  表3-7-1 GetViewportOrg 函数
用 途获取当前的视区原点。原 型DWORD GetViewportOrg(   HDC hDC,设备对象句柄) 返回值视区的原点(设备坐标),y坐标在高位字中,x坐标在低位字中。
  表3-7-2 GetWindowOrg 函数
用 途获取当前的视区原点。原 型DWORD GetWindowOrg(   HDC hDC,设备对象句柄) 返回值窗口的原点,y坐标在高位字中,x坐标在低位字中。
  Windows的映射方式中,MM_LOMETRIC、MM_HIMETRIC、MM_LOENGLISH、MM_HIENGLISH和MM_TWIPS是以物理度量单位表示逻辑坐标的映射方式。这五种映射方式被称为公制映射方式,因为它们在X轴和Y轴上的逻辑坐标被映射为等量的物理单位,所以这些映射方式便于绘制圆或正方形。
  当初次设置这些映射方式之一时,坐标系为:

Windows编程基础 - 设备对象属性(图六)图3-6
  因此,在用户区中显示信息必须使用负的Y坐标值。例如:

  SetMapMode(hDC, MM_LOENGLISH);
  TexOut(hDC, 100, -100, "Hello", 5);

  将在用户区向右偏移1英寸,向下偏移1英寸的位置显示“Hello”。
  下面的语句将逻辑点(0, 0)设置在用户区的左下角:

  SetViewportOrg(hDC, 0, yClient);

Windows编程基础 - 设备对象属性(图七)图3-7
  而下面的语句将逻辑点(0, 0)设置在用户区的中心点。

  SetViewportOrg(hDC, xClient/2, yClient/2);

Windows编程基础 - 设备对象属性(图八)图3-8
  在Windows提供的八种映射方式中,唯有MM_ISOTROPIC和MM_ANISOTROIC答应用户改变窗口和视口的范围(其他六种的范围是固定的,用户不能改变)。表3-7列出了与窗口范围和视区范围有关的函数。

  表3-7-3 SetWindowExt 函数
用 途设置窗口的x和y坐标范围。原 型DWORD SetWindowExt(   HDC hDC,设备对象句柄  int x,窗口的x坐标范围(逻辑单位)  int y窗口的y坐标范围(逻辑单位)) 返回值以前的坐标范围(逻辑单位),y坐标范围在高位字中,x坐标范围在低位字中。
  表3-7-4 GetWindowExt 函数
用 途检索窗口的x和y坐标范围。原 型DWORD GetWindowExt(   HDC hDC,设备对象句柄) 返回值y范围在高位字中,x范围在低位字中。
  表3-7-5 SetViewportExt 函数
用 途设置视区的x和y范围。原 型DWORD SetViewportExt(   HDC hDC,设备对象句柄  int x,视区的x坐标范围(设备单位)  int y视区的y坐标范围(设备单位)) 返回值以前的视区的坐标范围(设备单位),y在高位字中,x在低位字中。
  表3-7-6 GetViewportExt 函数
用 途设置视区的x和y范围。原 型DWORD GetViewportExt(   HDC hDC,设备对象句柄) 返回值视区的X和Y坐标范围(设备单位),y在高位字中,x在低位字中。
  窗口和视区的范围不具有任何裁剪区的意义,它们用于确定将逻辑坐标系中的单位放大或缩小多少以适合设备坐标系中的单位。例如,假如窗口的X坐标范围是2,而视区的X坐标范围是4,则GDI将两个逻辑单位(按X轴计算)映射为一个设备单位。
  坐标范围也同样决定了两个坐标系(窗口和视区)的X轴和Y轴的相对定向关系。假如相关联的窗口和视区范围符号一致,则坐标轴方向相同,否则方向相反。例如,假如窗口和视区的X坐标范围分别是2和4,则GDI将逻辑坐标系的X轴的正半轴映射到该设备坐标系统的X轴正半轴;假如窗口和视区的Y坐标范围分别为2和-1,则GDI将逻辑坐标系统的Y轴的正半轴映射到设备坐标系的Y轴的负半轴。
  对于MM_ISOTROPIC映射方式,词“isotropic”表示在所有的方向都“相等”,即该方式的用途是在两个轴上保持相等的逻辑单位。我们举一例子来说明该方式的设置方法。
  我们设置这样一个坐标系,(0, 0)点位于用户区域的左下角,宽度范围从0到32767,高度范围也从0到32767:

Windows编程基础 - 设备对象属性(图九)图3-9
并使用下列程序段设置映射方式和范围:

  SetMapMode(hDC, MM_ISOTROPIC);
  SetWindowExt(hDC, 32767, 32767);
  SetViewportExt(hDC, xClient, yClient);  // xClient和yClient是用户区的宽度和高度
  SetViewportOrg(hDC, 0, yClient);

  这时,Windows将调整范围,使两个轴上的逻辑单位表示相同的物理距离。假如用户区的宽度比高要长,则Windows调整x范围。这时坐标位置如下图:

Windows编程基础 - 设备对象属性(图十)图3-10
  逻辑窗口将位于用户区左侧,并且由于Windows的坐标值不能大于32767,就无法在用户区的右侧部分显示任何图形。假如用户的高度比宽度要长,则Windows调整y的范围,这时候坐标位置如下图:

Windows编程基础 - 设备对象属性(图十)图3-11
  同样的原因,在用户的顶部也无法显示任何图形,因为这时需要大于32767逻辑单位的y坐标。
  由此可见,对于MM_ISOTROPIC映射方式,程序员可以调用函数SetViewportExt设置范围,Windows通过调整范围,使得两个轴上每一个逻辑单位表示相同的物理距离,这对于绘制圆和正方形等是方便的。
  对函数SetViewportExt的调用必须在对函数SetViewportExt的调用之前进行,这可以最有效地使用用户区中的空间。若不是这样的话,当Windows调整范围时,为了使逻辑窗口置于物理视区内,可能导致用户区域的某一部分在逻辑窗口的外面。
  窗口和视区的范围不表示裁剪区,我们分析下面的程序片断所建立的坐标系:

  SetMapMode(hDC, MM_ISOTROPIC);
  SetWindowExt(hDC, 1000, 1000);
  SetViewportExt(hDC, xClient/2, -yClient/2);
  SetViewportExt(hDC, xClient/2, yClient/2);

  我们分析用户区宽度大于高度的情况:

Windows编程基础 - 设备对象属性(图六)图3-12
  对于上图所示的情况,若点的X坐标小于-1000或大于+1000,则这个点可能落在用户区内,也可能落在用户区之外;但对于Y坐标小于-1000或大于+1000的点,这个点一定落在用户区之外。
  最后,我们介绍MM_ANISOTROPIC映射方式,单词“anisotropic”的含义是“在所有的方向上不相等”。对于MM_ANISOTROPIC,Windows对所设置的范围不进行调整,这表明,对于MM_ANISOTROPIC,两个坐标轴上的逻辑单位具有不同的物理尺寸。例如,对于上面的例子,假如设置为MM_ANISOTROPIC映射方式。

  SetMapMode(hDC, MM_ANISOTROPIC);
  SetWindowExt(hDC, 1000, 1000);
  SetViewportExt(hDC, xClient/2, -yClient/2);
  SetViewportExt(hDC, xClient/2, yClient/2);

  则所建立的坐标系如下图所示:

Windows编程基础 - 设备对象属性(图六)图3-13

  下面的程序实例是使用MM_ANISOTROPIC映射方式重新设计的2.5节的程序。

  // 3-1.c
  #include <stdio.h>
  #include <windows.h>

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

  int PASCAL WinMain(
    HINSTANCE hInstance,   // 应用程序的实例句柄
    HINSTANCE hPrevInstance, // 该应用程序前一个实例的句柄
    LPSTR lpszCmdLine,    // 命令行参数串
    int nCmdShow )      // 程序在初始化时如何显示窗口
  {
     char szAppName[] = "DispText";
    HWND hwnd;
    MSG msg;
    WNDCLASS wndclass;

    if (!hPrevInstance) {
      // 该实例是程序的第一个实例,注册窗口类
      wndclass.style = CS_VREDRAW CS_HREDRAW;
      wndclass.lpfnWndProc = WndProc;
      wndclass.cbClsExtra = 0;
      wndclass.cbWndExtra = 0;
      wndclass.hInstance = hInstance;
      wndclass.hIcon = LoadIcon(hInstance, IDI_application);
      wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
      wndclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
      wndclass.lpszMenuName = NULL;
      wndclass.lpszClassName = szAppName;

      if (!RegisterClass(&wndclass))
        // 假如注册失败
        return FALSE;
    }

    // 对每个实例,创建一个窗口对象
    hwnd = CreateWindow(
      szAppName,
      "Display Text",
      WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, CW_USEDEFAULT,
      CW_USEDEFAULT, CW_USEDEFAULT,
      NULL,
      NULL,
      hInstance,
      NULL );

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

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

    return msg.wParam;
  }

  LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
  {
    static int xChar, yChar;
    int line;
    char szBuffer[256];
    HDC hDC;
    char ch;
    PAINTSTRUCT ps;
    TEXTMETRIC tm;
    FILE *fp;

    switch(message)
    {
      case WM_CREATE:
        hDC = GetDC(hwnd);
        GetTextMetrics(hDC, &tm);
        xChar = tm.tmAveCharWidth;
        yChar = tm.tmHeight + tm.tmExternalLeading;
        ReleaseDC(hwnd, hDC);
        return 0L;

      case WM_PAINT:
        hDC = BeginPaint(hwnd, &ps);
        SetMapMode(hdc, MM_ANISOTROPIC);
        SetWindowExt(hdc, 1, 1);
        SetViewportExt(hdc, xChar, yChar);
        line = 0;
        if((fp = fopen("disptext.cpp", "r")) != NULL)
        {
          while(!feof(fp)) {
            int i = 0;
            while((ch = fgetc(fp)) != '/n' && ch != EOF)
              szBuffer[i++] = (char)ch;
            TextOut(hDC, xChar, line*yChar, szBuffer, i);
            line++;
          }
          fclose(fp);
        }
        EndPaint(hwnd, &ps);
        return 0L;

      case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
  }


3.5、设备信息
  一个设备对象通常与一个物理显示设备(例如显示器和打印机等)相关联,这些设备的有关信息可以通过调用GetDeviceCaps函数来获取。表3-8给出了该函数的一些使用说明,在本指南中,当需要用该函数的其他功能时,再进行有关的讨论。

  表3-8 GetDeviceCaps 函数
用 途获取一个设备的设备信息。原 型int GetDeviceCaps(   HDC hDC,设备对象句柄  int nIndex ); 返回值返回所需要项的值。
  表3-8-1 nIndex 值 常量说明HORZSIZE物理显示器的宽度(mm)VERTSIZE物理显示器的高度(mm)HORZRES物理显示器的宽度(象素)VERTRES物理显示器的高度(象素,严格说是光栅行数)LOGPIEXLSX沿显示宽度方向每逻辑英寸的象素数LOGPIEXLSY沿显示高度方向每逻辑英寸的象素数BITSPIEXL表示每个象素所需要的位数目PLANES色平面数NUMBRUSHES刷子的数目NUMPENS笔的数目NUMFONTS字体的数目NUMCOLORS设备的颜色表中的项目数aspECTX一个象素的相对宽度ASPECTY一个象素的相对高度ASPECTXY象素的相对对角线宽度
  LOGPIXELSX和LOGPIXELSY的值分别是水平和垂直方向每一“逻辑寸”中的象素数。一个逻辑寸大小约是1.5英寸,这取决于显示设备的分辨率。逻辑寸实际上放大显示,以便将所显示的文本放大到一个适当的大小。

3.6、颜色的使用
  关于颜色的定量表示,有许多方法,其中一种方法分别使用0到255的数值来表示一种颜色中红(R)、绿(G)和蓝(B)三基色的相对强度,这中方法被称为RGB颜色模型。Windows用一个无符号长整数来表示颜色,其中最低三字节分别指定0到255范围内的红、绿和蓝三基色的相对强度。在Windows中,使用COLORREF类型(或DWORD类型)的数据表示RGB颜色值。
  在Windows.h中定义的宏RGB将三个表示红、绿、蓝的值组成一个RGB颜色值。例如:RGB(0,0,0)表示黑色,RGB(255,255,255)表示白色,其他常见的RGB颜色如下:

类型说明RGB(255, 0, 0)红色RGB(0, 255, 0)绿色RGB(0, 0, 255)蓝色RGB(255, 0, 255)紫色RGB(255, 255, 0)黄色RGB(0, 255, 255)青色
  宏GetRValue、GetGValue和GetBValue从RGB值中提取无符号单字节的基色值。
  Windows还可以通过灰度比方式来显示附加的颜色,即将不同的单色象素组合成象素图案,不同的红、绿、蓝颜色组成不同的灰度比图案。
  另外,Windows为了对各个显示元素进行着色,还维护着19种系统颜色(见表示表3-9),程序员可以使用函数GetSysColor或SetSysColors来获取或设置这些系统颜色(见表3-10)。这些颜色的缺省值是由设备驱动程序提供的。

  表3-9 Windows的缺省系统颜色 常量说明COLOR_SCROLLBAR滚动杠的灰色区域COLOR_BACKGROUND桌面的背景(屏幕背景)COLOR_ACTIVECAPTION活动窗口的标题栏COLOR_INACTIVECAPTION非活动窗口的标题栏COLOR_MENU选单背景COLOR_MENUTEXT选单文本COLOR_WINDOW窗口的背景COLOR_WINDOWFRAME窗口的框架COLOR_CAPTIONTEXT标题栏中的文本COLOR_ACTIVEBORDER活动窗口的边框COLOR_APPWORKSPACE多文档界面应用程序的背景COLOR_HIGHLIGHT选项COLOR_HIGHLIGHTTEXT选项中的文本COLOR_BTNFACE按钮的边缘COLOR_BTNTEXT按钮上的文本COLOR_GRAYTEXT按钮(灰色文本)
  表3-10-1 SetSysColors 函数
用 途该函数为一个或多个显示元素设置系统颜色(显示元素指的是系统和窗口的各个不同的部分。原 型VOID SetSysColors(    int nChanges, 需改变的系统颜色数目。  LPINT lpSysColor, int类型的指针,所指向的变量中指定了将要改变的元素,可用的值见表3-9。  DWORD FAR *lpColorValue 所指向的变量中包含了每个元素新的RGB颜色值。); 注 意该函数将改变当前正运行的所有应用程序的窗口颜色。该函数将各窗口发送WM_SYSCOLORCHANG消息,通知颜色的变化。
  表3-10-2 GetSysColor 函数
用 途检索指定的显示元素的当前色。原 型VOID GetSysColors(    int nIndex, 指定要检索其颜色的显示元素,见表3-9。); 返回值所指定的显示元素的RGB值,对单色显示器,系统颜色可解释为灰度图案。
3.7、使用刷子
  刷子用于擦除用户区中显示的内容,也用于填充GDI图形函数建立的封闭图形。Windows提供的可供程序员使用的库存刷子有:

常量说明WHITE_BRUSH白色刷子LTGRAY_BRUSH浅灰色刷子GRAY_BRUSH灰色刷子DKGRAY_BRUSH深灰色刷子BLACK_BRUSH黑色刷子HOLLOW(or NULL)_BRUSH空刷子
  一个刷子由HBRUSH类型的句柄标识。库存刷子的句柄可由函数GetStockObject函数获得。例如,下面的语句获得一个灰色刷子,并将其句柄存于hBrush中。

  HBRUSH hBrush = GetStockObject(GRAY_BRUSH);

  使用函数SelectObject将一个刷子选入一个设备对象中,这样,在以后填充封闭图形时,使用选入到设备对象中的刷子。例如,将上面的语句所获得的库存刷子选入hDC标识的设备对象中,使用语句:

  hOldBrush = SelectObject(hDC, hBrush);

  函数SelectObject返回设备对象中先前已存在的刷子,在上例中,将它保存在HBRUSH类型的变量hOldBrush中。程序员可以使用函数CreateSolidBrush和CreateHatchBrush创建自己的刷子,表3-11给出了这两个函数的说明。

  表3-11-1 CreateSolidBrush 函数
用 途创建一个指定颜色的逻辑刷子。原 型HBRUSH CreateSolidBrush   COLORREF crColorRGB颜色值。); 注 意假如调用成功,返回一个刷子句柄,否则返回NULL。
  表3-11-2 CreateHatchBrush 函数
用 途创建一个具有指定阴影图案和颜色的逻辑刷子。原 型HBRUSH CreateHatchBrush   int nIndex, 刷子的阴影类型,见后面的说明。  COLORREF crColor刷子影线的颜色。); 返回值假如调用成功,返回一个刷子句柄,否则返回NULL。
常量说明HS_HORIZONTAL- - - -  水平影线HS_VERTICAL   垂直影线HS_FDIAGONAL/ / / /  45度向上影线HS_BDIAGONAL/ / / /  45度向下影线HS_CROSS+ + + +  水平和垂直交叉影线HS_DIAGCROSSX X X X  45度交叉影线
  例如,下列程序段分别创建一个绿色刷子和一个红色影线刷子。

  hGreenBrush = CreateSolidBrush(RGB(0,255,0));
  hRedHatchedBrush = CreateHatchBrush(HS_CROSS, RGB(255,0,0));

  程序员创建的刷子在不再使用时一定要删除(使用函数DeleteObject)。删除库存刷子是非法的。

  DeleteObject(hGreenBrush);
  DeleteObject(hRedHatchedBrush);

  下面的程序建立一个背景色为交叉影线的窗口。

  LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
  {
    static HBRUSH OrgBrush, hBrush;
    HDC hDC;
    char msg[256] = "Hello, Welcome to cgd.gamedoor.net!";
    PAINTSTRUCT ps;

    switch(message)
    {
      case WM_CREATE:
        OrgBrush = GetClassWord(hwnd, GCW_HBRBACKGROUND);
        hBrush = CreateHatchBrush(HS_DIAGCROSS, RGB(255,255,0));
        SetClassWord(hwnd, GCW_HBRBACKGROUND, hBrush);
        InvalidateRect(hwnd, NULL, TRUE);
        UpdateWidnow(hwnd);
        return 0;

      case WM_PAINT:
        hDC = BeginPaint(hwnd, &ps);
        SetBkMode(hDC, TRANSPARENT);
        TextOut(hDC, 0, 0, msg, sizeof(msg)-1);
        ValidateRect(hwnd, NULL);
        EndPaint(hwnd, &ps);
        return 0;

      case WM_DESTROY:
        SetClassWord(hwnd, GCW_HBRBACKGROUND, OrgBrush);
        DeleteObject(hBrush);
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
  }

  在程序中,语句:

  GetClassWord(hwnd, GCW_HBRBACKGROUND);

  获取在类所指定的刷子的句柄,语句:

  SetClassWord(hwnd, GCW_HBRBACKGROUND, hBrush);

  重新将类刷子设置为程序员所创建的刷子。为了用新刷子清楚用户区,语句:

  InvalidateRect(hwnd, NULL, TRUE);

  使整个用户区无效。程序中使用的函数SetBkMode见3.9节。

3.8、使用笔
  GDI函数使用笔画线或绘制图形的轮廓。Windows库存的笔包括:

常量说明WHITE_PEN白色笔BLACK_PEN黑色笔NULL_PEN空笔
  笔有类型为HPEN的句柄来标识,库存的笔可用函数GetStockObject来获取:

  HPEN hPen = GetStockObject(WHITE_PEN);

  它返回标识一个库存笔的句柄。设备对象中缺省的笔是BLACK_PEN。程序员也可以使用函数GreatePen创建定制的笔,如表3-12。

  表3-12-1 GreatePen 函数
用 途创建一支具有指定类型、宽度和颜色的逻辑笔。原 型HPEN CreatePen(    int nPenStyle,笔的类型,见后面的说明  int nWidth,以逻辑单位定义的笔的宽度  COLORREF crColor RGB颜色值) 返回值假如函数成功,则返回值是标识一个逻辑笔对象的句柄,否则为NULL。
  表3-12-2 GreatePenIndirect 函数
用 途创建一支逻辑笔。原 型HPEN CreatePenIndirect(    LOGPEN FAR *lpLogPen指向包含逻辑笔类型、宽度和颜色的一个LOGPEN类型的变量) 返回值返回一个逻辑笔对象的句柄,假如不成功,返回NULL。
  类型LOGPEN的说明为:

  typedef struct tagLOGPEN {
    WORD lopnStyle;   // 笔的类型,见后表的说明。
    POINT lopnWidth;   // 笔的宽度(逻辑单位),若要为0,则宽度为一个象素宽。
    COLORREF lopnColor; // 笔的颜色,为RGB颜色值。
  } LOGPEN;
     常量说明PS_SOLID_______PS_DASH-------PS_DOT.......PS_DASHDOT_._._._PS_DASHDOTDOT_.._.._.._PS_NULL空笔PS_INSIDEFRAME________
笔的类型由上列常量定义

  注重:假如笔的宽大于1,并且笔的类型为PS_INSIDEFRAME,则线条被画在多边形基本框架里边;假如笔的宽度大小或等于1,则类型PS_INSIDEFRAME与类型PS_SOLIDE完全相同;假如笔的颜色不能与可利用的RGB值相匹配,则PS_INSIDEFRAME类型的笔的颜色使用逻辑颜色,它通过将几种纯颜色混合形成所需要的颜色。物理宽度大于1象素的笔总为空或实类型的笔。
  下面的程序片段使用函数CreatePen创建一支红色笔,并选入到设备对象中:

  HPEN hPen = CreatePen(PS_SOLID, 2, RGB(255,0,0));
  hPen = SelectObject(hDC, hPen);

  当程序创建的笔不再使用时,应使用函数DeletObject将其删除:

  DeleteObject(hPen);

  但删除库存笔是非法的。库存笔和程序员创建的任何宽度为0的笔的实际宽度为1个象素,这在高分辨率的显示器上表示为极细的线。假如当前的映射方式是MM_TEXT方式,最好通过调用带有SM_CXBORDER和SM_CYBORDER索引的GetSystemMetrics(见表3-13)来获得单线窗口边框的宽度,将这些值用作笔宽。也可使用其他映射方式为程序中创建的笔设置特定的物理宽度。

  表3-13 GetSystemMetrics 函数
用 途检索系统度量。系统度量是Windows显示的各种元素的宽和高。原 型int GetSystemMetrics(    int nIndex指定要检索的系统度量,见后面的说明) 返回值返回所要求的以象素给出的系统度量。
常量说明SM_CXSCREEN屏幕的宽度SM_CYSCREEN屏幕的高度SM_CXFRAME能缩放窗口的边框的宽度SM_CYFRAME能缩放窗口的边框的高度SM_CXVSCROLL垂直滚动杠箭头的位图的宽度SM_CYVSCROLL垂直滚动杠箭头的位图的高度SM_CXHSCROLL水平滚动杠箭头的位图的宽度SM_CYHSCROLL水平滚动杠箭头的位图的高度SM_CXBORDER不能缩放的窗口的边框的宽度SM_CYBORDER不能缩放的窗口的边框的高度SM_CXDLGFRAME具有WS_DLGFRAME窗口的边框的宽度SM_CYDLGFRAME具有WS_DLGFRAME窗口的边框的高度SM_CXICON图标的宽度SM_CYICON图标的高度SM_CXCURSOR光标的宽度SM_CYCURSOR光标的高度SM_CXFULLSCREEN全屏幕窗口的窗口区宽度SM_CYFULLSCREEN全屏幕窗口的窗口区高度

 3.9、填充空隙
  在点划线、短划线笔或影线刷子的使用中,点和短划线之间的空隙以及刷子中的阴影间隙的着色取决于设备对象中定义的背景方式和背景颜色。缺省的背景方式是OPAQUE,即Windows用背景色填充空隙,缺省的背景颜色是白色,即与大多数程序在窗口类别中用作清除窗口背景的WHITE_BRUSH型刷子相一致。可以通过调用函数SetBkColor设背景色,使用函数GetBkColor获得设备对象中定义的当前背景色(见表3-14)。

  表3-14-1 SetBkColor 函数
用 途设置当前的背景色。原 型DWORD SetBkColor(    HDC hDC,设备对象句柄  COLORREF crColor 新的背景色,RG颜色值) 返回值返回作为一种颜色值的当前的背景色。若小于0,则出错。
  注释:假如背景方式为OPAQUE,那么GDI就用背景色填充设计的行距、笔、刷子中阴影间隙,以及各字符中的间隙。假如设备不能表示由crColor参数指定的RGB颜色值,此函数将当前背景色设置为最接近的物理色(纯颜色),并返回之。

  表3-14-2 GetBkColor 函数
用 途返回指定设备的当前背景色。原 型DWORD GetBkColor(    HDC hDC设备对象句柄) 返回值当前背景色的RGB颜色值。
  当前背景方式决定Windows是否填充空隙。函数SetBkMode及GetBkMode用于设置和获取背景方式,如表3-15。

  表3-15-1 SetBkMode 函数
用 途设置与文本和线型一起使用的背景方式。背景方式决定在显示文本、刷子或非实线的普通笔型之前,GDI是否要将设备显示表面已有显示内容用背景色清除。原 型int SetBkMode(    HDC hDC设备对象句柄  int nBkMode背景方式。见后面说明) 返回值返回OPAQUE或TRANSPARENT,以说明先前的背景方式。
常量说明OPAQUE在文本、刷子或笔绘图之前,用当前背景色填充设备显示表面TRANSPARENT背景保留不变
  表3-15-2 GetBkMode 函数
用 途返回设备的背景方式。原 型int GetBkMode(    HDC hDC设备对象句柄) 返回值返回当前背景方式(OPAQUE或TRANSPARENT)。
3.10、设置文本属性
  有一些设备对象属性影响文本。缺省时设备对象所绘制的文本颜色为黑色。可以使用函数SetTextColor或函数GetTextColor设置或获取设备对象中的文本颜色,如表3-17。

  表3-16-1 SetTextColor 函数
用 途设置文本颜色。原 型DWORD SetTextColor(    HDC hDC设备对象句柄  COLORREF crColor 指定的文本颜色(RGB值)) 返回值返回先前的文本颜色(RGB值)。
  注释:假如设备不支持指定的颜色,则设置成最接近的物理颜色。

  表3-16-2 GetTextColor 函数
用 途设置当前的文本颜色。原 型DWORD GetTextColor(    HDC hDC设备对象句柄) 返回值当前使用的文本颜色(RGB值)。
  下面的程序是对3.4及3.7节的程序修改后所形成的一个示例程序,这个程序演示了在程序中设置文本属性方法。

  // 3-10.c
  #include <stdio.h>
  #include <windows.h>

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

  int PASCAL WinMain(
    HINSTANCE hInstance,   // 应用程序的实例句柄
    HINSTANCE hPrevInstance, // 该应用程序前一个实例的句柄
    LPSTR lpszCmdLine,    // 命令行参数串
    int nCmdShow )      // 程序在初始化时如何显示窗口
  {
    ... ...
  }

  LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
  {
    static HBRUSH OrgBrush, hBrush;
    static int xChar, yChar;
    int line;
    char szBuffer[256];
    HDC hDC;
    char ch;
    PAINTSTRUCT ps;
    TEXTMETRIC tm;
    FILE *fp;

    switch(message)
    {
      case WM_CREATE:
        OrgBrush = GetClassWord(hwnd, GCW_HBRBACKGROUND);
        hBrush = CreateHatchBrush(HS_DIAGCROSS, RGB(255, 255, 0)
        SetClassWord(hwnd, GCW_HBRBACKGROUND, hBrush);
        InvalidateRect(hwnd, NULL, TRUE);
        UpdateWindow(hwnd);
        
        hDC = GetDC(hwnd);
        GetTextMetrics(hDC, &tm);
        xChar = tm.tmAveCharWidth;
        yChar = tm.tmHeight + tm.tmExternalLeading;
        ReleaseDC(hwnd, hDC);
        return 0;

      case WM_PAINT:
        hDC = BeginPaint(hwnd, &ps);
        SetBkMode(hDC, OPAQUE);
        SetTextColor(hDC, RGB(255, 0, 0));
        SetBkColor(hDC, RGB(0, 255, 0));

        SetMapMode(hdc, MM_ANISOTROPIC);
        SetWindowExt(hdc, 1, 1);
        SetViewportExt(hdc, xChar, yChar);

        line = 0;
        if((fp = fopen("3-10.c", "r")) != NULL)
        {
          while(!feof(fp)) {
            int i = 0;
            while((ch = fgetc(fp)) != '/n' && ch != EOF)
              szBuffer[i++] = (char)ch;
            TextOut(hDC, xChar, line*yChar, szBuffer, i);
            line++;
          }
          fclose(fp);
        }
        EndPaint(hwnd, &ps);
        return 0L;

      case WM_DESTROY:
        SetClassWord(hwnd, GCW_HBRBACKGROUND, OrgBrush);
        DeleteObject(hBrush);
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
  }

  修改这个程序,将背景方式设置为TRANSPARENT,观察程序的运行结果。

3.11、公用和私用显示设备
  在前面,我们曾介绍说,Windows维护着五个公用的显示设备对象,当窗口对象需要绘制其用户区时,使用函数GetDC()或BeginPaint()从Windows那里借用。一般而言,函数GetDC()和BeginPaint()所借用来的公用显示设备对象的初始属性为表3-1中所给出的缺省属性,窗口对象可以使用有关的函数改变这些属性,以满足其绘制要求。在绘制工作完成以后,窗口对象应将显示设备对象归还给Windows。但注重,当调用ReleaseDC()或EndPaint()函数将公用显示设备对象归还Windows后,对显示设备对象的属性所做的改变也就丢失了。这样,当窗口对象再次借用显示设备对象时,显示设备对象的初始属性仍为缺省属性,窗口对象在每次使用非缺省属性进行绘制之前,都必须使用有关的函数设置显示设备对象的属性。
  另外的一种情况是当窗口对象在一段程序中改变了一些设备对象属性,在使用改变后的设备对象属性工作之后,窗口对象又要使用原先的设备对象属性继续工作,对这种情况,一种比较方便的方法是通过使用函数SaveDC()和RestoreDC来简化对设备对象的治理,这两个函数的使用说明见表3-17。

  表3-16-1 SaveDC 函数

用 途该函数用于保存其参数所标识的设备对象的当前属性信息,它通过将这些属性信息拷贝到一个设备对象环境栈中来实现。原 型int SaveDC(    HDC hDC设备对象句柄) 返回值返回一个序数值来反映被保存的设备对象,假如该函数在执行中出错,则返回0。
  注释:该函数可被调用多次来分别保存多个设备对象的多个属性信息。

  表3-16-2 RestoreDC 函数
用 途恢复设备对象的属性信息。原 型BOOL RestoreDC(    HDC hDC设备对象句柄  int nSaveDC指定要被恢复的属性信息,可以是函数SaveDC()返回的值或为-1,若为-1,则设备对象被恢复为设备对象环境栈顶所保存的属性信息。) 返回值非零表示恢复成功,零表示失败。
  注释:该函数通过拷贝设备对象环境栈中保存的属性信息来恢复指定的设备对象属性。假如由参数nSaveDC指定的设备对象信息不处于环境栈的栈顶,那以函数RestoreDC()会不断地删除栈顶信息,直到找到由参数nSaveDC所指定的设备对象信息为止,这些被删除的信息永远被丢失了。
  下面的程序片段用于演示函数SaveDC()和RestoreDC()使用方法:

  // ......
  int nSaveID = SaveDC(hDC);
  SetBkMode(hDC, OPAQUE);
  SetTextColor(hDC, RGB(255, 0, 0));
  SetBkColor(hDC, RGB(0, 255, 0));

  SetMapMode(hDC, MM_ANISOTROPIC);
  SetWindowsExt(hDC, 1, 1);
  SetViewportExt(hDC, xChar, yChar);
  // ......
  RestoreDC(nSaveID);
  // ......

  使用公用显示设备对象的优点是,这些显示设备对象为系统中的所有应用程序共享。但不足之处在于存在与借用、设置和归还显示设备对象有关的许多工作,对一些复杂的绘图程序,或使用非缺省属性的应用程序,使用公用显示设备对象会增加程序的编码量,并有可能影响到应用程序的效率。对这些情况,程序员可以为一个或多个窗口对象指定使用私用显示设备对象,当在注册窗口类时,为该窗口类指定CS_OWNDC风格参数,这时,由这种窗口类所创建的窗口对象都拥有自己的私用显示设备对象。
  使用私用显示设备对象的优点在于,由于私用的显示设备对象仅供一个窗口对象专用,这样,窗口对象在一条消息处理完备以后从窗口函数返回之前也不需要归还显示设备对象。窗口对象可以将显示设备对象的句柄保存在一个静态生存期的变量中,供自己使用。例如,窗口对象在处理WM_CREATE消息时设置设备的属性,而在处理WM_PAINT或其他消息时直接使用所设置对象属性。

  // 3-11.c
  #include <stdio.h>
  #include <windows.h>

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

  int PASCAL WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine,
    int nCmdShow)
  {
     char szAppName[] = "DispText";
    HWND hwnd;
    MSG msg;
    WNDCLASS wndclass;
    HBRUSH hBrush;
    HDC hDC;
    int xChar, yChar;
    TEXTMETRIC tm;

    hBrush = CreateHatchBrush(HS_DIAGCROSS, RGB(255, 255, 0));
    if (!hPrevInstance) {
      wndclass.style = CS_VREDRAW CS_HREDRAW CS_OWNDC;
      wndclass.lpfnWndProc = WndProc;
      wndclass.cbClsExtra = 0;
      wndclass.cbWndExtra = 0;
      wndclass.hInstance = hInstance;
      wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
      wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
      wndclass.hbrBackground = hBrush;
      wndclass.lpszMenuName = NULL;
      wndclass.lpszClassName = szAppName;

      if (!RegisterClass(&wndclass))
        return FALSE;
    }

    hwnd = CreateWindow(
      szAppName,
      "Display Text",
      WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, CW_USEDEFAULT,
      CW_USEDEFAULT, CW_USEDEFAULT,
      NULL,
      NULL,
      hInstance,
      NULL );

    hDC = GetDC(hwnd);
    GetTextMetricse(hDC, &tm);
    xChar = tm.tmAveCharWidth;
    yChar = tm.tmHeight + tm.tmExternalLeading;

    SetBkMode(hDC, OPAQUE):
    SetTextColor(hDC, RGB(255, 0, 0));
    SetBkColor(hDC, RGB(0, 255, 0));
    
    SetMapMode(hDC, MM_ANISOTROPIC);
    SetWindowExt(hDC, 1, 1)
    SetViewportExt(hDC, xChar, yChar);
    
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while( GetMessage(&msg, NULL, 0, 0)) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
    ReleaseDC(hwnd, hDC);
    DeleteObject(hBrush);

    return msg.wParam;
  }

  LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
  {
    int line;
    char szBuffer[256];
    HDC hDC;
    char ch;
    PAINTSTRUCT ps;
    FILE *fp;

    switch(message)
    {
      case WM_PAINT:
        hDC = BeginPaint(hwnd, &ps);

        line = 0;
        if((fp = fopen("3-11.c", "r")) != NULL)
        {
          while(!feof(fp)) {
            int i = 0;
            while((ch = fgetc(fp)) != '/n' && ch != EOF)
              szBuffer[i++] = (char)ch;
            TextOut(hDC, xChar, line*yChar, szBuffer, i);
            line++;
          }
          fclose(fp);
        }
        EndPaint(hwnd, &ps);
        return 0;

      case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
  }

  在一个窗口对象使用私用显示设备对象的情况下,函数GetDC()和BeginPaint()返回的句柄标识的设备对象是窗口的私用显示设备对象。从这个程序还可以看出,私用的显示设备对象保持当前设置的设备属性,对私用的显示设备对象,不存在“借用”和“归还”之说。但使用CS_OWNDC只影响到从GetDC和BeginPaint检索到的设备对象,而不会影响到使用别的函数(例如GetWindowDC()等函数)检索到的设备对象。
  一个私用显示设备对象大约占用200字节,所有的字节都从GDI模块的局部堆中分配,而这个堆由系统中当前正运行的所有应用程序共享。假如系统中的所有应用程序都使用私用显示设备对象,那将会用完GDI的局部堆,导致系统崩溃。因此,除非必要,应用程序一般不使用私用显示设备对象。
  介于使用公用和私用显示设备对象之间的是使用类显示设备对象。若在注册窗口类时指定了CS_CLASSDC风格参数,则该窗口类拥有一个可供该类所有窗口对象(包括在该应用程序的其它实例中所创建的窗口对象)共用的显示设备对象。这种类型的显示设备对象被称为类显示设备对象。类显示设备对象由于被该类的所有窗口对象共享,因此,一个窗口对象在使用类显示设备对象时需要从类中借用,并在使用完之后归还给类,否则,该类的其它对象就无法使用类显示设备对象。但有一点应引起注重,类显示设备对象同私用显示设备对象一样,也保持当前设置的属性,即使在归还类的情况下也是如此。这就是说,假如该类的一个窗口对象改变了该显示设备对象的属性,那么,也要影响到基于该窗口类所创建的所有窗口对象(包括在该应用程序的其它实例中所创建的窗口对象)。因此,类显示设备对象较难使用,但它相对于私用显示设备对象而言,可以节省一些内存空间。


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表