# WINDOWS编程基础

[ 主页 ] [上一层]

随着PC机上图象处理软件及显示卡技术的发展，使苹果Mac（麦金托什）机独霸图象处理的局面被打破。如今在PC机上处理高质量图象已不再是梦想，丰富的图象处理软件如Photo Styler、Corel Draw、Adobe Photoshop、Paintbrush、Freehand、Imagepal、Picture Publisher等等，其处理图象的能力和水平已直追一些专业图形图象工作站。加上多媒体技术的推广发展，使图象处理成为当今多媒体应用中的一项重要内容。
Windows提供的三大主要功能中，标准的图形用户界面最引人注目，而且对用户也是最重要的。在它所提供的用户界面中使用图标来代表驱动器、文件、子目录和许多操作系统的命令及操作。
因为所有的Windows程序都具有类似的结构和相同的“外观”，所以用户不需花费很长的时间来学习新程序的用法。对于编程人员而言，具有这种一致的用户界面是因为直接使用Windows的内部子模序来创建用户界面的基本元素（菜单、对话框、滚动条等）。所有的菜单都具有同样的键盘和鼠标接口，这是因为Windows自己处理这项工作，而不是把它交给应用程序去完成。
Windows的多任务操作环境允许用户同时运行多个应用程序，或在同一个程序中同时“并行”完成几件事情。一个典型的例子是在Windows中运行四个应用程序，每个程序在屏幕上占一个矩形区域。用户可在任何时间移动屏幕上的窗口、改变窗口大小、从一个窗口转换到另一个窗口和修改窗口内的信息。
虽然例程里的这个例子有四个并发执行的进程，但是在任何时候只有一个进程在实际使用处理器。并发运行的任务可以有任意多个，由Windows负责把处理器时间分配给多个任务，Windows根据排好的输入队列及其他信息来控制微处理器时间的分配。
在多任务操作系统未出现之前，应用程序独自控制计算机的所有资源（输入／输出设备、内存、显示器和CPU）。而在Windows中，所有这些资源都是共享的。
内存是Windows中最重要的共享资源之一，当多个应用程序在同一时间运行时，这些应用程序共享内存资源，如果程序运行结束，则归还其所占用的内存区域。为了提供大块内存区域，Windwos能在内存中搬移整块代码和数据，把存储碎片收集起来以形成大块连续的内存区域。Windows在内存管理方面的一个最明显特征是它把软件开发者从DOS的内存限制中解放出来。
在用户并发运行的几个程序都调用同一个程序时，为了节省空间，Windows共享那一段相同的代码，在Windows中运行的程序甚至可以共享其他．EXE文件中的例程。在Windows中把包含这种共享的例程的库文件称为动态链接库（DLL），Windows在运行时可把程序与动态链接库中的例程链接起来。为此，Windows程序使用了一种新的．EXE文件格式，这种文件包括了Windows执行时所需要的信息，如管理代码段和数据段以及用于动态链接的必要信息。
在开始介绍Windows图形程序设计之前，这里我想先介绍一些Visual C++图形编程常见的问题和一些基本方法和技巧：
一、在用户环境中确定系统显示元素的颜色
调用SDK函数GetSysColor可以获取一个特定显示元素的颜色。下例说明了如何在MFC函数CMainFrameWnd:: OnNcPaint中调用该函数设置窗口标题颜色。
void CMiniFrameWnd:: OnNcPaint ()
{ kk1}
…
dc.SetTextColor (:: GetSysColor (m_bActive ?
OLOR_CAPTIONTEXT : COLOR_INACTIVECAPTIONTEXT));
…
}
二、访问预定义的GDI对象
可以通过调用CDC:: SlectStockObject使用Windows的几个预定义的对象，诸 如刷子、笔以及字体。下例使用了Windows预定义的笔和刷子GDI对象在视窗中画一个椭圆。
//Draw ellipse using stock black pen and gray brush.
void CSampleView:: OnDraw (CDC* pDC)
{ kk1}
//Determine size of view.
CRect rcView;
GetClientRect (rcView);
//Use stock black pen and stock gray brush to draw ellipse.
pDC->SelectStockObject (BLACK_PEN);
pDC->SelectStockObject (GRAY_BRUSH)
//Draw the ellipse.
pDC->Ellipse (reView);
}
也可以调用新的SDK函数GetSysColorBrush获取一个系统颜色刷子，下例用背景色在视窗中画一个椭圆：
void CsampleView:: OnDraw (CDC* pDC)
{kk1}
//Determine size of view.
CRect rcView;
GetClientRect (rcView);
//Use background color for tooltips brush.
CBrush * pOrgBrush=pDC->SelectObject
(CBrush::FromHandle(::GetSysColorBrush (COLOR_INFOBK)));
//Draw the ellipse.
pDC->Ellipse (rcView);
//Restore original brush.
pDC->SelectObject (pOrgBrush);
}
三、确定GDI对象的属性信息
可以调用GDIObject:: GetObject。这个函数将指定图表设备的消息写入到
缓冲区。下例创建了几个有用的辅助函数。
//Determine if font is bold.
BOOL IsFontBold (const CFont&font)
{kk1}
LOGFONT stFont;
font.GetObject (sizeof (LOGFONT), &stFont);
return (stFont.lfBold)? TRUE: FALSE;
}
//Return the size of a bitmap.
CSize GetBitmapSize (const CBitmap&bitmap)
{ kk1}
BITMAP stBitmap;
bitmap.GetObject (sizeof (BITMAP), &stBitmap);
return CSize (stBitmap.bmWidth, stBitmap. bmHeight);
}
//Create a pen with the same color as a brush. BOOL CreatePenFromBrush (Cpen&pen, cost Cbrush&brush)
{kk1}
LOGBRUSH stBrush;
brush.Getobject (sizeof (LOGBRUSH), &stBrush);
return pen. Createpen (PS_SOLID, 0, stBrush.ibColor);
}
四、实现一个橡皮区矩形
CRectTracker是一个很有用的类，可以通过调用CRectTracker::TrackRubberBand
响应WM_LBUTTONDOWN消息来创建一个橡皮区矩形。下例表明使用CRectTracker移动和重置视窗中的蓝色椭圆的大小是很容易的事情。
首先，在文件档中声明一个CRectTracker数据成员： class CSampleView : Public CView
{ kk1}
…
public :
CrectTracker m_tracker;
…
};
其次，在文档类构造函数中初始化CRectTracker对象： CSampleDoc:: CSampleDOC ()
{ kk1}
//Initialize tracker position, size and style. m_tracker.m_rect.SetRect (0, 0, 10, 10);
m_tracker.m_nStyle=CRectTracker:: resizeInside | CRectTracker:: dottedLine;
}
然后，在OnDraw函数中画椭圆和踪迹矩形：
void CSampleView:: OnDraw (CDC* pDC)
{ kk1}
CSampleDoc* pDoc=GetDocument ();
ASSERT_VALID (pDoc);
//Select blue brush into device context. CBrush brush (RGB (0, 0, 255));
CBrush* pOldBrush=pDC->SelectObject (&brush);
//draw ellipse in tracking rectangle.
Crect rcEllipse;
pDoc->m_tracker.GetTrueRect (rcEllipse);
pDC->Ellipse (rcEllipse);
//Draw tracking rectangle.
pDoc->m_tracker.Draw (pDC);
//Select blue brush out of device context.
pDC->Selectobject (pOldBrush);
}
最后，使用ClassWizard处理WM_LBUTTONDOWN消息，并增加下述代码。该段代码根据鼠标击键情况可以拖放、移动或者重置椭圆的大小。
void CSampleView::OnLButtonDown (UINT nFlags, CPoint point)
{ kk1}
//Get pointer to document.
CSampleDoc* pDoc=GetDocument ();
ASSERT_VALID (pDoc);
//If clicked on ellipse, drag or resize it. Otherwise create a rectangle
//rubber-band rectangle nd create a new ellipse. BOOL bResult=pDoc->m_tracker.HitTest (point)!=
CRectTracker::hitNothing;
//Tracker rectangle changed so update views.
if (bResult)
{ kk1}
pDoc->m_tracker.Track (this,point,TRue);
pDoc->SetModifiedFlag ();
pDoc->UpdateAllViews (NULL);
}
else pDoc->m-tracker.TrackRubberBand (this,point,TRUE);
CView:: onLButtonDown (nFlags,point);
}
在计算机显示系统中，只有高档的显示系统才可以在屏幕每一个位置上同时显示各种可能的颜色，大多数显示卡只能显示有限数量的颜色。标准VGA能同屏显示16色，每像素占4位存储空间。内存足够的SVGA适配器最多每像素占8位，可同屏幕显示262144种不同颜色中的256色。普通使用的SVGA显示适配器通过将像素值解释为一个表格的索引，根据索引值将每个像素的内容转变为一种颜色。这个表格就是硬件调色板，其条目就是RGB值。
Windows 系统在支持硬件调色板的时候，遇到了些困难。如果 Windows 系统允许任何一个程序改变视频硬件调色板中RGB 颜色的设置，那么在系统中运行的每一个应用程序都将受影响。例如硬件调色板中的黑色被改为蓝色，那么每一个窗口中的所有黑色像素立即就会被改为蓝色。这就违反了 Windows 应用程序作为独立窗口运行，彼此互不干扰的基本原则。另一个问题是 Windows 程序可以在任何系统运行，而大多数系统中显示的颜色和 Super VGA 系统中的颜色数量不同。
为了解决上述问题，从3.0版开始Windows提供了一个调色板管理器，用作用户开发的应用程序和输出设备。
调色板管理器由系统调色板和逻辑调色板构成，它们用于管理实际设备的“硬件调色板”。系统调色板是相应于显示器的硬件调色板，它是一个有二十种保留颜色的数组，它的项数与显示器实际能显示的颜色数相等，这些保留颜色被用来绘制系统的菜单、按钮控制、屏幕桌面颜色和抖动的画刷等。
一般情况下，这二十种保留颜色不能被改变。逻辑调色板是模拟显示卡中硬件调色板的一块内存区域，逻辑调色板中每一项包含一个TGB值，这个RGB值供应用程序创建彩色的画笔、字体、画刷和位图。当逻辑调色板包括的项比硬件设备实际支持的要多时，“扩充的”逻辑调色板就匹配硬件调色板的相近颜色，如果逻辑调色板所包括的项少于硬件调色板，硬件调色板中的一些颜色将不使用。
为了更加深入的了解Windows系统中的调色板，这里我们再来看一下调色板管理器的工作原理：
如果Windows显示器处理的是24位彩色，调色板管理器没有太大意义，因为可以显示的颜色(约有一千六百万种)能表示DIB中所有颜色。即使显示器处理的只是3位或4位颜色，调色板管理器也没有什么用处，这时系统本身储存有20种系统颜色，已超过了设备能显示的颜色数组。只有对八位(256种颜色)彩色显示器，调色板管理器才真正起作用。为了使调色板管理正常工作，当Windows应用程序使用逻辑调色板时，首先必须检查显示驱动器是否支持逻辑调色板，然后根据需要创建一个逻辑调色板，在使用逻辑调色板之前还必须将其先安装到系统调色板上。具体过程如下：
1) 检查显示驱动程序是否支持逻辑调色板
if(GetDeviceCaps(hDC,DRIVERSION)==0x300)
{kk1}
}
else{kk1}
//不支持逻辑调色板
}
2)逻辑调色板的创建
首先根据Windows规定的逻辑调色板的格式，在内存中分配一块相应大小的区域。内存块的大小根据需要显示的颜色数所定。
typedefstructtagLOGPALETTE{kk1}
WORDpalVersion;
/*windows的版本号*/
WORDpalNumEntries;
/*调色板的项数*/
PALETTEENTRYpalPalEntry[1]；
}LOGPALETTE;
typedefstructtagPALETTEENTRY{kk1}
BYTEpeRed；
BYTEpeGreen；
BYTEpeBlue;
BYTEpeFlags;
}PALETTEENTRY；
公式如下：
Sizeof(LOGPALETTE)*numcolor*Sizeof(PALETTEENTRY)
numcolor－－－－－－－－－－－显示的颜色数；
然后将Windows系统的版本号，及调色板要显示的颜色数填入LOGPALETTE结构中；再将所需要显示的颜色的RGB值依次填入PALETTEENTRY结构中。最后，利用Windows的API中的CreatePalette()函数创建一个调色板，并利用函数SelectPalette()把创建的调色板选入设备描述表。这样一个逻辑调色板就创建了。
(3)逻辑调色板映射到系统调色板
API中RealizePalette()函数可以使Windows的调色板管理器工作。RealizePalette()函数通知Windows调色板管理器真正地改变系统调色板，使其与设备描述表中的逻辑调色板相匹配并按要求映射颜色。这个映射过程不是简单的将逻辑调色板前面的颜色放入系统调色板二十中保留颜色之后，逻辑调色板中剩余的颜色就抛弃不管。它的映射规则如下：
如果逻辑调色板中某种颜色在系统调色板中已存在，该颜色将映射到系统调色板中相应颜色上。
如果逻辑调色板中某种颜色在系统调色板中无相应颜色，只要系统调色板有空间，该颜色就加到系统调色板上。
当系统调色板已满时，逻辑调色板中颜色映射到系统调色板中最相近的颜色上。根据以上规则映射就形成了Windows用来正确显示图象的调色板。
通常情况下，Windows系统保留二十种供所有窗口使用的系统颜色不能被改变，但是系统还是提供了函数SetSystemPaletteUse()允许应用程序去改变系统调色板，或者恢复正常状态。在使用一个16到64色的设备时，改变系统调色板是扩展颜色选择项的一种方法。对于64色以上的设备来说，具有足够的颜色。
因此，保留系统的颜色与增加需要的颜色并不矛盾。
(4)在退出程序前，释放逻辑调色板分配的内存，恢复原来的调色板，删除当前调色板。
LocalUnlock(hLocPal)；
LocalFree(hLocPal)；
SeletPalette(hDC,hOldPal,FALSE)；
DeleteObject(hNewPal)；
三、逻辑调色板冲突的解决方法
当几个程序都使用逻辑调色板的时候，Windows系统把当前优先级最高窗口的逻辑调色板送给系统调色板。非活动窗口的颜色取决于还有哪些颜色未被采用，这些非活动窗口首先使用系统调色板中剩余不用的项，然后才为那些无法得到原色的颜色使用调色板上相近的颜色。当要在同一窗口显示具有不同逻辑调色板的设备无关位图时，Windows系统就无法支持。我们解决这个问题的办法是：合并所有的逻辑调色板。当逻辑调色板的总项数不超过256时，合并操作只需将所有的逻辑调色板连接在一起；当总项数超过256时，可采用以下方法来合并逻辑调色板：
从不同逻辑调色板中选出最常用的颜色，组成一个256项逻辑调色板。
反复地合并不同逻辑调色板中相邻颜色并用其平均值来取它们。这样就把所有逻辑调色板简化成一个相似的256色逻辑调色板。
与内存一样，键盘和鼠标输入也是Windows的共享资源，Windows下的C程序不能通过调用getchar函数直接从键盘上读取字符。在Windows中，应用程序不直接说明是从键盘还是从鼠标器读取输入消息，而是从被称作系统队列的键盘、鼠标和时钟等接受所有的输入消息，然后再由此队列负责把输入消息重新分配到各个应用程序中，即把系统队列中的消息复制到应用程序的相应队列中，也就是把输入重定向到相应的应用程序，此时如应用程序准备处理输入，它就从自己的队列中读取消息。
键盘、鼠标和时钟的所有信息都具有同一格式，并且用同样的方式进行处理，另外，Windows给每个消息提供一个与设备无关的虚拟键码来标识该键，键盘产生与设备有关的扫描码。作为共享资源的键盘和鼠标，为Windows下运行的所有应用程序提供输入，从键盘输入的所有消息都直接送到当前处于激活状态的窗口，而与鼠标有关的消息则送到鼠标光标所指的窗口中。这里的消息具有特定的含义，下面简要介绍。
Windows的消息系统是多任务执行环境的基础之一，它是发送消息的基本结构。从编程者的角度看来，一条消息可被视为某一事件的发生（如按下或移动鼠标、键盘击键），这些事件既可以由用户引发，也可以由应用程序产生，甚至Windows本身也能发出消息。
Windows中的消息主要有两个作用： （l）Windows通过它的消息系统实现多任务功能，支持多个任务同时并发执行。这一消息系统使得多个应用程序有可能共享一个物理处理器，每次Windows向应用程序传送消息的同时也分配该程序一段CPU时间。（2）使应用程序对该环境中的事件发生响应。每当一个事件发生时，Windows都记录一次，并把一个适当的消息发送给对此事件感兴趣的各个应用程序。所以从最基本的层次看，Windows应用程序的一个主要任务是处理消息。
Windows的第三个主要特点是设备独立性。一个应用程序可能要与多种硬件设备接口，例如显示器有多种类型，怎样才能使用户开发的应用程序可以不受设备变更的影响呢？
非Windws环境（如DOS）下开发的应用程序必须为每种叶能用到的设备写驱动程序。假定现在要编写一个适用于各种打印机的图形打印程序，程序员必须为每种打印机编写不同的驱动程序。我们都知道，打印机的种类很多，如Epson LQ－l500、Epson LQ－1600、HP LaserJet、HP Deskjet等，显然这样做工作量很大。
在Windows环境下，每种设备驱动程序——不管是显示器，还是打印机、键盘、鼠标——只需写一次。这样就不需要每个软件开发人员去编写自己的设备驱动程序，只要硬件生产厂商为其产品编写一个设备驱动程序，由他们把这些设备驱动程序连同设备一起提供给用户即可。Windows在安装时，已包括了系统现有的每一个设备的驱动程序，每当应用程序发出打印或画图命令时，Windows就通过适当的驱动程序输出打印数据或图形。把用户所需的设备驱动程序直接装入系统，从而省去许多重复琐碎的编程工作。
该功能使Windwos应用的开发人员很容易开发自己的应用程序。应用程序只要与Windows打交道，而不必管任何具体的输入／输出设备，不必知道所用的打印机或图形显示器的型号规格。Windows为实现这种与设备无关的特性，规定与Windons接口的硬件须具备几种规定功能，它们是使用软件开发工具包（SDK）时保证相应的例程能正常工作的最基本功能。对于每类设备有一组含义不同的功能定义，你可参阅相应的Windows手册。
Windows的许多功能是由被称作是DLL的动态链接库提供的。DLL提供一个强大且灵活的图形用户口，从而增强了基本的操作系统。DLL中的预定义函数不是当．EXE文件生成时静态产生的，而是当应用程序动态装入时才与之相连的。这样做显然节省了内存，不管有多少应用模序在运行，同一时间RAM中只有程序库的一个备份。
Windows改变了程序库的格式，因而有更强的通用性，它们不仅保留了其他DOS执行文件的同样格式，而且可包含任何程序可包含的内容。除了函数，程序库也可以编码数据甚至并入图形资源（如光标形状和位图）。Windows程序库扩展了共享资源的范围，并且为程序员节省了更多时间。
从技术角度讲，当一个Windows应用程序调用一个Windows函数时，编译器必须为此函数产生一个远程调用该函数的机器码，这个函数位于Windows程序库的代码段。于是出现了这样一个问题：程序在Windows中真正运行之前，Windows函数的地址是未知的。为解决这一问题，Windows提供了延迟联编（也称动态链接）技术。使用MSC6．0版中的LINK或Pascal的链接程序即可完成这个任务。包含在SDK中的是特殊的“入口程序库”，用于为Windows程序的动态链接作准备。许多Windows的小模式程序都要用到LIBW．LIB和SLIBW．LIB这两个入口程序库。SLIBW．LIB程序为包含程序中可能调用的所有Windows函数的记录，这个记录定义了各个函数所在的Windows模块。
假设有一个Windows应用程序要调用Windows中的PostMessage函数，当链接该程序时，链接程序在SLIBW．LIB中的表中找到PostMessage函数，取出该函数的序号并把这一信息嵌人程序的．EXE文件中。程序运行时，Windows再把程序的函数调用与真正的Post Message函数链接起来。
旧的MS―DOS可执行文件的头标格式中没有地方可用来嵌入附加的动态链接信息，在Windows中采用的是一种新的．EXE文件格式，这种格式有一新文件头标。
Windows程序模块包括KERNEL、USER和GDI，其中KERNEL完成内存管理、程序的装人与执行和任务调度等功能，它需要调用原MS―DOS中的文件管理、磁盘输入输出和程序执行等功能；USER是一个程序库，它用来对声音、时钟、鼠标器及键盘输入等操作进行管理；GDI是一功能十分丰富的子程序库，它提供了图形与文字输出、图象操作和窗口管理等各种与显示和打印有关的功能。Windows程序模块中的例程可帮助Windows中运行的程序执行多种操作，如发送和接收消息。上述KERNEL、USER和GDI模块中的库函数可被应用程序调用，也可被其他程序模块调用。把包含库函数的模块称为输出者（export）。新的可执行格式中有一个入口表用于指明模块内每个输出函数的地址。
从应用程序的角度看，用到的库函数被认为是入口 （import）函数。应用程序对一个入口函数发出的远程调用可用不同的重定位表来确定。几乎所有的应用程序都至少包含一个入口库函数，或者称为被外部调用的函数。该Windows库函数一般来自某个程序库模块，用于从Windows 接收消息，该函数的使用标志必须是“export”，这才能使Windows允许它被一个外部模块正常调用。
新的可执行格式还为程序或程序中每个代码段和数据段提供了附加的信息。一般说来，代码段标记成“可移动的”和“可丢弃的”，而数据段标记成“可移动的”。这就允许Windows在内存中移动代码段和数据段，当内存不够用时可以把代码段丢弃掉（因为代码段被标成是“可丢弃的”）。如果在后面又需要一个已被丢弃掉的代码段，只需简单地从初始的．EXE文件中把该代码段重新装入。
Windows中还有一种执行格式为“调用时装入”。在这种格式下，它把程序或程序库定义为：如果没有其他代码调用该函数，则不把它的代码段装入内存。这一完善的内存管理技术允许Windows在正常情况下只够运行一个程序的内存空间中同时运行多个应用程序。
Windows应用程序是事件驱动(或称作消息驱动)的应用程序。Windows是一个多任务的操作系统，也就是说，在同一时刻，在Windows中有着多个应用程序的实例正在运行，在这样的一个操作系统中，不可能像过去的DOS那样，由一个应用程序来享用所有的系统资源，这些资源是由Windows统一管理的。
那么，特定的应用程序如何获得用户输入的信息呢？事实上，Windows时刻监视着用户的一举一动，并分析用户的动作与哪一个应用程序相关，然后，将用户的动作以消息的形式发送给该应用程序，应用程序时刻等待着消息的到来，一但发现它的消息队列中有未处理的消息，就获取并分析该消息，最后，应用程序根据消息所包含的内容采取适当的动作来响应用户所作的操作。
举一个例子来说明上面的这个问题，假设我们编了一个程序，该程序有一个File菜单，那么，在运行该应用程序的时候，如果用户单击了File菜单，这个动作将被Windows (而不是应用程序本身！)所捕获，Windows经过分析得知这个动作应该由上面所说的那个应用程序去处理，既然是这样，Windows就发送了个叫做WM_COMMAND的消息给应用程序，该消息所包含信息告诉应用程序：“用户单击了File菜单”，应用程序得知这一消息之后，采取相应的动作来响应它，这个过程称为消息处理。Windows为每一个应用程序(确切地说是每一个线程)维护了相应的消息队列，应用程序的任务就是不停的从它的消息队列中获取消息，分析消息和处理消息，直到一条接到叫做WM_QUIT消息为止，这个过程通常是由一种叫做消息循环的程序结构来实现的。
一个典型的Windows应用程序的包括四个普通段—WinMain( )函数、消息循环、窗口过程及窗口消息。
WinMain( )函数是所有Windows程序的入口点、出口点WinMain函数包含三个基本部分：过程说明、程序初始化及消息循环。
WinMain( )函数和过程说明如下：
int PASCAL WinMain(HANDLE hInstance，HANDLE hPrevInstance， LPSTR lpszCmdLine， int cmdShow）；
其中hInstance是标识程序的句柄（handle）。 hPrevInstance指明和哪一个实例（hInstance）相关——如果有的话。若程序具有同样的模块名，则认为程序是相关的。指明用于程序的命令行参数，cmdShow指明第一次打开窗口时主窗口的状态。
程序初始化部分是winMain( )函数的主体，利用winMain( )函数的程序初始化部分创建一个窗口。在创建窗口的过程中，必须定义窗口类。
WinMain( )函数的初始化部分的第一段定义WndClass数据结构的各个成员。在这部分代码段中，定义：
用于窗口的光标
用于填充窗口背景的刷子
窗口过程的名字
窗口类的风格，窗口主莱单
类名
在开始介绍Windows图形程序设计之前，这里我想先介绍一些Visual C++图形编程常见的问题和一些基本方法和技巧：
一、在用户环境中确定系统显示元素的颜色
调用SDK函数GetSysColor可以获取一个特定显示元素的颜色。下例说明了如何在MFC函数CMainFrameWnd:: OnNcPaint中调用该函数设置窗口标题颜色。
void CMiniFrameWnd:: OnNcPaint ()
{ kk1}
…
dc.SetTextColor (:: GetSysColor (m_bActive ?
OLOR_CAPTIONTEXT : COLOR_INACTIVECAPTIONTEXT));
…
}
二、访问预定义的GDI对象
可以通过调用CDC:: SlectStockObject使用Windows的几个预定义的对象，诸 如刷子、笔以及字体。下例使用了Windows预定义的笔和刷子GDI对象在视窗中画一个椭圆。
//Draw ellipse using stock black pen and gray brush.
void CSampleView:: OnDraw (CDC* pDC)
{ kk1}
//Determine size of view.
CRect rcView;
GetClientRect (rcView);
//Use stock black pen and stock gray brush to draw ellipse.
pDC->SelectStockObject (BLACK_PEN);
pDC->SelectStockObject (GRAY_BRUSH)
//Draw the ellipse.
pDC->Ellipse (reView);
}
也可以调用新的SDK函数GetSysColorBrush获取一个系统颜色刷子，下例用背景色在视窗中画一个椭圆：
void CsampleView:: OnDraw (CDC* pDC)
{kk1}
//Determine size of view.
CRect rcView;
GetClientRect (rcView);
//Use background color for tooltips brush.
CBrush * pOrgBrush=pDC->SelectObject
(CBrush::FromHandle(::GetSysColorBrush (COLOR_INFOBK)));
//Draw the ellipse.
pDC->Ellipse (rcView);
//Restore original brush.
pDC->SelectObject (pOrgBrush);
}
三、确定GDI对象的属性信息
可以调用GDIObject:: GetObject。这个函数将指定图表设备的消息写入到
缓冲区。下例创建了几个有用的辅助函数。
//Determine if font is bold.
BOOL IsFontBold (const CFont&font)
{kk1}
LOGFONT stFont;
font.GetObject (sizeof (LOGFONT), &stFont);
return (stFont.lfBold)? TRUE: FALSE;
}
//Return the size of a bitmap.
CSize GetBitmapSize (const CBitmap&bitmap)
{ kk1}
BITMAP stBitmap;
bitmap.GetObject (sizeof (BITMAP), &stBitmap);
return CSize (stBitmap.bmWidth, stBitmap. bmHeight);
}
//Create a pen with the same color as a brush. BOOL CreatePenFromBrush (Cpen&pen, cost Cbrush&brush)
{kk1}
LOGBRUSH stBrush;
brush.Getobject (sizeof (LOGBRUSH), &stBrush);
return pen. Createpen (PS_SOLID, 0, stBrush.ibColor);
}
四、实现一个橡皮区矩形
CRectTracker是一个很有用的类，可以通过调用CRectTracker::TrackRubberBand
响应WM_LBUTTONDOWN消息来创建一个橡皮区矩形。下例表明使用CRectTracker移动和重置视窗中的蓝色椭圆的大小是很容易的事情。
首先，在文件档中声明一个CRectTracker数据成员： class CSampleView : Public CView
{ kk1}
…
public :
CrectTracker m_tracker;
…
};
其次，在文档类构造函数中初始化CRectTracker对象： CSampleDoc:: CSampleDOC ()
{ kk1}
//Initialize tracker position, size and style. m_tracker.m_rect.SetRect (0, 0, 10, 10);
m_tracker.m_nStyle=CRectTracker:: resizeInside | CRectTracker:: dottedLine;
}
然后，在OnDraw函数中画椭圆和踪迹矩形：
void CSampleView:: OnDraw (CDC* pDC)
{ kk1}
CSampleDoc* pDoc=GetDocument ();
ASSERT_VALID (pDoc);
//Select blue brush into device context. CBrush brush (RGB (0, 0, 255));
CBrush* pOldBrush=pDC->SelectObject (&brush);
//draw ellipse in tracking rectangle.
Crect rcEllipse;
pDoc->m_tracker.GetTrueRect (rcEllipse);
pDC->Ellipse (rcEllipse);
//Draw tracking rectangle.
pDoc->m_tracker.Draw (pDC);
//Select blue brush out of device context.
pDC->Selectobject (pOldBrush);
}
最后，使用ClassWizard处理WM_LBUTTONDOWN消息，并增加下述代码。该段代码根据鼠标击键情况可以拖放、移动或者重置椭圆的大小。
void CSampleView::OnLButtonDown (UINT nFlags, CPoint point)
{ kk1}
//Get pointer to document.
CSampleDoc* pDoc=GetDocument ();
ASSERT_VALID (pDoc);
//If clicked on ellipse, drag or resize it. Otherwise create a rectangle
//rubber-band rectangle nd create a new ellipse. BOOL bResult=pDoc->m_tracker.HitTest (point)!=
CRectTracker::hitNothing;
//Tracker rectangle changed so update views.
if (bResult)
{ kk1}
pDoc->m_tracker.Track (this,point,TRue);
pDoc->SetModifiedFlag ();
pDoc->UpdateAllViews (NULL);
}
else pDoc->m-tracker.TrackRubberBand (this,point,TRUE);
CView:: onLButtonDown (nFlags,point);
}
在Delphi中，专门定义了一组对象和部件用以绘制图形，完成一些简单的图像功能。利用这些对象、部件的方法，可以方便地绘制各种常用图形；通过设置它们的属性，能得到不同风格的图形。另外，通过对鼠标事件的定义，可以方便的设计图形绘制程序。
TCanvas Object(画布对象)
TCanvas对象是一个用于绘图的表面，在这个区域上，程序可实现各种绘图功能，很多部件(如TIMage,TMemo)的Canvas属性就是TCanvas对象。在部件上绘制图形就是在部件的画布上绘制。TCanvas的Brush,Pen,Font属性分别是TBrush,TPen,TFont对象，它们用于定义绘制图形的风格。
画布的笔的位置定义在PenPos属性中，可用MoveTo方法来移动笔。如果要在画布上输出文本，可用Textout方法。
Tpen Object(画笔对象)
应用程序常用TPen对象在画布上绘制各种线段，笔的颜色在Color属性中定义。线段宽度在Width属性中定义。Style属性定义了线段的各种类型,这些类型的定义与Visual C++中的定义类似。
Mode属性定义线段的颜色。可结合当前的颜色、屏幕在Delphi中，专门定义了一组对象和部件用以绘制图形，完成一些简单的图像功能。利用这些对象、部件的方法，可以方便地绘制各种常用图形；通过设置它们的属性，能得到不同风格的图形。另外，通过对鼠标事件的定义，可以方便的设计图形绘制程序。
TCanvas Object(画布对象)
TCanvas对象是一个用于绘图的表面，在这个区域上，程序可实现各种绘图功能，很多部件(如TIMage,TMemo)的Canvas属性就是TCanvas对象。在部件上绘制图形就是在部件的画布上绘制。TCanvas的Brush,Pen,Font属性分别是TBrush,TPen,TFont对象，它们用于定义绘制图形的风格。
画布的笔的位置定义在PenPos属性中，可用MoveTo方法来移动笔。如果要在画布上输出文本，可用Textout方法。
Tpen Object(画笔对象)
应用程序常用TPen对象在画布上绘制各种线段，笔的颜色在Color属性中定义。线段宽度在Width属性中定义。Style属性定义了线段的各种类型,这些类型的定义与Visual C++中的定义类似。
Mode属性定义线段的颜色。可结合当前的颜色、屏幕TColor类型
TColor类型用于定义一个对象颜色。很多部件的颜色属性就是TColor 类型， 在Graphics单元中TColor定义如下：
TColor = -(COLOR_ENDCOLORS + 1)..$02FFFFF;
这是一个32位二进制数据。Graphic单元中还定义了一些常用的颜色常量，这些常量或直接映射成系统调色板中最相近的颜色，或映射成Wondows 控制面板中颜色部分的系统视频颜色。
直接映射成系统调色板中的颜色有：
ClAqua,CLBlack,ClBlue,ClbkGrray,ClFuchsoa...ClYellow
映射程序用4字节的二进制码来定义颜色，低3 位字节代表RGB 相应的颜色，如$00FF0000表示纯蓝，$0000FF00表示纯绿，$000000FF表示纯红，$00000000表示黑色，$00FFFFFF表示白色。如果最高位字节是$00,则表示用系统调色板中最相近的颜色；最高位字节是$01，则表示用当前调色板中最相近的颜色匹配；最高位字节是$02，则用当前设备描述表中逻辑调色板的次相近颜色匹配。
用Windows API的SelectPalette函数可创建逻辑调色板，要实现逻辑调色板到硬件调色板的映射，需用函数RealizePalett。
图像对象概述
TGraphic对象
TGraphic对象是TBitmap ,TIcon,Tmetafile对象的基类。如果知道图像的具体类型( 如位图， 图标元文件) ， 则应将图像贮存在相应类型的对象中( 如TBitmap,TIcon,Tmetafile)，否则应该使用可贮存任何图像类型的TPicture对象。
TPicture对象
TPicture对象可以保存位图、图标或元文件。Graphic属性中包括图像的类型；图像的高度和宽度分别定义在Height,Width属性中；调用LoadFromFile方法，可以从文件中装载一幅图像：
procedure TForm1.FormCreate(Sender: TObject);
begin
BitBtn1.Glyph.LoadFromFile('TARTAN.BMP');
end;
要保存一个位图，则要用SaveToFile方法；要把图像复制到剪切板，可以调用TClipboard对象的Assign方法。
TImage部件
TImage部件用以在窗体中显示图像，它的Picture 属性保存着要显示的图像， 这是一个TPicture对象。AutoSize,Stretch属性是用来调节部件与图像的大小的。当AutoSize 为真值时，TImage部件将根据它所包含的图像的大小来调整自身的大小；当AutoSize为假值时，不论图像有多大，部件将保持设计时的大小。如果部件比图像小， 那么只有一部分图像是可见的。当Stretch为真值时，位图像将根据部件的大小调整自身的大小，当部件大小改变时，元文件也做相应变化。Stretch属性对图标没有作用。
TBitmap Object(位图对象)
位图对象包含一个位图图像，有HBITMAP,HPALETE句柄，可自动管理调色板。位图对象也有画布属性。位图的Palette属性用来控制位图的颜色映射，它包括256种可显示的颜色。 如果应用程序用前景色绘制位图，Palette 属性的颜色将被加入Windows系统调色板，其它颜色被映射到系统调色板已存在的颜色。如果应用程序用自己的颜色绘制位图，而其它程序已占有系统调色板，位图的颜色将被映射到系统调色板中。
如果Monochrome属性设置成假，位图将显示成彩色，反之显示成黑白色。
调用Draw和StretchDraw方法可在画布上绘制位图。
这里是一个用Canvas对象实现的小画图程序，这里看到的是程序运行的一个瞬间，源程序在例程中——Canvas文件夹中，读者可以自行参照开发应用程序。
在介绍用编程的方法来生成位图图形和进行图像处理前，我们先来看一下如何用Windows图形工具来生成和编辑位图文件。
在Windows程序生成工具软件Visual C++中,提供了生成、编辑位图图片和图标的位图编辑器。通过位图编辑器，你可以很方便的画出一张图片加入所编译的应用程序中；或是给你的程序设计一个美观的图标。你还可以将你喜欢的位图图片和图标引入到你的程序中。
你可以在程序源码中资源栏中直接新建或引入图片、图标。在已编译过的程序中你也可以用“插入”菜单中的“插入资源”选项，来完成上述这些处理操作。
使用位图编辑器，你可以很方便的生成和编辑图片和图标，但位图编辑器只能用于简单的图形处理，在下一章里我们还将为您介绍如何用程序来实现。

返回到上面