📄 专业tc--图形篇4.htm
字号:
dottedLine; <BR>} <BR>然后,在OnDraw函数中画椭圆和踪迹矩形: <BR>void
CSampleView:: OnDraw (CDC* pDC) <BR>{ kk1}<BR>CSampleDoc* pDoc=GetDocument
(); <BR>ASSERT_VALID (pDoc); <BR>//Select blue brush into device
context. CBrush brush (RGB (0, 0, 255)); <BR>CBrush*
pOldBrush=pDC->SelectObject (&brush); <BR>//draw ellipse in tracking
rectangle. <BR>Crect rcEllipse; <BR>pDoc->m_tracker.GetTrueRect
(rcEllipse); <BR>pDC->Ellipse (rcEllipse); <BR>//Draw tracking
rectangle. <BR>pDoc->m_tracker.Draw (pDC); <BR>//Select blue brush
out of device context. <BR>pDC->Selectobject
(pOldBrush); <BR>} <BR>最后,使用ClassWizard处理WM_LBUTTONDOWN消息,并增加下述代码。该段代码根据鼠标击键情况可以拖放、移动或者重置椭圆的大小。 <BR>void
CSampleView::OnLButtonDown (UINT nFlags, CPoint point) <BR>{ kk1}<BR>//Get
pointer to document. <BR>CSampleDoc* pDoc=GetDocument
(); <BR>ASSERT_VALID (pDoc); <BR>//If clicked on ellipse, drag or
resize it. Otherwise create a rectangle<BR>//rubber-band rectangle nd create a
new ellipse. BOOL bResult=pDoc->m_tracker.HitTest
(point)!= <BR>CRectTracker::hitNothing; <BR>//Tracker rectangle
changed so update views. <BR>if (bResult) <BR>{
kk1}<BR>pDoc->m_tracker.Track (this,point,TRue);<BR>pDoc->SetModifiedFlag
(); <BR>pDoc->UpdateAllViews (NULL); <BR>} <BR>else
pDoc->m-tracker.TrackRubberBand (this,point,TRUE); <BR>CView::
onLButtonDown
(nFlags,point); <BR>} <BR><BR>在计算机显示系统中,只有高档的显示系统才可以在屏幕每一个位置上同时显示各种可能的颜色,大多数显示卡只能显示有限数量的颜色。标准VGA能同屏显示16色,每像素占4位存储空间。内存足够的SVGA适配器最多每像素占8位,可同屏幕显示262144种不同颜色中的256色。普通使用的SVGA显示适配器通过将像素值解释为一个表格的索引,根据索引值将每个像素的内容转变为一种颜色。这个表格就是硬件调色板,其条目就是RGB值。<BR>Windows
系统在支持硬件调色板的时候,遇到了些困难。如果 Windows 系统允许任何一个程序改变视频硬件调色板中RGB
颜色的设置,那么在系统中运行的每一个应用程序都将受影响。例如硬件调色板中的黑色被改为蓝色,那么每一个窗口中的所有黑色像素立即就会被改为蓝色。这就违反了
Windows 应用程序作为独立窗口运行,彼此互不干扰的基本原则。另一个问题是 Windows 程序可以在任何系统运行,而大多数系统中显示的颜色和 Super
VGA
系统中的颜色数量不同。<BR>为了解决上述问题,从3.0版开始Windows提供了一个调色板管理器,用作用户开发的应用程序和输出设备。<BR>调色板管理器由系统调色板和逻辑调色板构成,它们用于管理实际设备的“硬件调色板”。系统调色板是相应于显示器的硬件调色板,它是一个有二十种保留颜色的数组,它的项数与显示器实际能显示的颜色数相等,这些保留颜色被用来绘制系统的菜单、按钮控制、屏幕桌面颜色和抖动的画刷等。 <BR>一般情况下,这二十种保留颜色不能被改变。逻辑调色板是模拟显示卡中硬件调色板的一块内存区域,逻辑调色板中每一项包含一个TGB值,这个RGB值供应用程序创建彩色的画笔、字体、画刷和位图。当逻辑调色板包括的项比硬件设备实际支持的要多时,“扩充的”逻辑调色板就匹配硬件调色板的相近颜色,如果逻辑调色板所包括的项少于硬件调色板,硬件调色板中的一些颜色将不使用。<BR>为了更加深入的了解Windows系统中的调色板,这里我们再来看一下调色板管理器的工作原理:<BR>如果Windows显示器处理的是24位彩色,调色板管理器没有太大意义,因为可以显示的颜色(约有一千六百万种)能表示DIB中所有颜色。即使显示器处理的只是3位或4位颜色,调色板管理器也没有什么用处,这时系统本身储存有20种系统颜色,已超过了设备能显示的颜色数组。只有对八位(256种颜色)彩色显示器,调色板管理器才真正起作用。为了使调色板管理正常工作,当Windows应用程序使用逻辑调色板时,首先必须检查显示驱动器是否支持逻辑调色板,然后根据需要创建一个逻辑调色板,在使用逻辑调色板之前还必须将其先安装到系统调色板上。具体过程如下:<BR>1)
检查显示驱动程序是否支持逻辑调色板<BR>if(GetDeviceCaps(hDC,DRIVERSION)==0x300)<BR>{kk1}<BR>}<BR>else{kk1}<BR>//不支持逻辑调色板<BR>}<BR>2)逻辑调色板的创建<BR>首先根据Windows规定的逻辑调色板的格式,在内存中分配一块相应大小的区域。内存块的大小根据需要显示的颜色数所定。<BR>typedefstructtagLOGPALETTE{kk1}<BR>WORDpalVersion;<BR>/*windows的版本号*/<BR>WORDpalNumEntries;<BR>/*调色板的项数*/<BR>PALETTEENTRYpalPalEntry[1];<BR>}LOGPALETTE;<BR>typedefstructtagPALETTEENTRY{kk1}<BR>BYTEpeRed;<BR>BYTEpeGreen;<BR>BYTEpeBlue;<BR>BYTEpeFlags;<BR>}PALETTEENTRY;<BR>公式如下:<BR>Sizeof(LOGPALETTE)*numcolor*Sizeof(PALETTEENTRY)<BR>numcolor-----------显示的颜色数;<BR>然后将Windows系统的版本号,及调色板要显示的颜色数填入LOGPALETTE结构中;再将所需要显示的颜色的RGB值依次填入PALETTEENTRY结构中。最后,利用Windows的API中的CreatePalette()函数创建一个调色板,并利用函数SelectPalette()把创建的调色板选入设备描述表。这样一个逻辑调色板就创建了。<BR><BR><BR><BR><BR><BR>(3)逻辑调色板映射到系统调色板<BR>API中RealizePalette()函数可以使Windows的调色板管理器工作。RealizePalette()函数通知Windows调色板管理器真正地改变系统调色板,使其与设备描述表中的逻辑调色板相匹配并按要求映射颜色。这个映射过程不是简单的将逻辑调色板前面的颜色放入系统调色板二十中保留颜色之后,逻辑调色板中剩余的颜色就抛弃不管。它的映射规则如下:<BR>如果逻辑调色板中某种颜色在系统调色板中已存在,该颜色将映射到系统调色板中相应颜色上。<BR>如果逻辑调色板中某种颜色在系统调色板中无相应颜色,只要系统调色板有空间,该颜色就加到系统调色板上。<BR>当系统调色板已满时,逻辑调色板中颜色映射到系统调色板中最相近的颜色上。根据以上规则映射就形成了Windows用来正确显示图象的调色板。<BR>通常情况下,Windows系统保留二十种供所有窗口使用的系统颜色不能被改变,但是系统还是提供了函数SetSystemPaletteUse()允许应用程序去改变系统调色板,或者恢复正常状态。在使用一个16到64色的设备时,改变系统调色板是扩展颜色选择项的一种方法。对于64色以上的设备来说,具有足够的颜色。<BR>因此,保留系统的颜色与增加需要的颜色并不矛盾。<BR>(4)在退出程序前,释放逻辑调色板分配的内存,恢复原来的调色板,删除当前调色板。<BR><BR>LocalUnlock(hLocPal);<BR>LocalFree(hLocPal);<BR>SeletPalette(hDC,hOldPal,FALSE);<BR>DeleteObject(hNewPal);<BR><BR>三、逻辑调色板冲突的解决方法<BR>当几个程序都使用逻辑调色板的时候,Windows系统把当前优先级最高窗口的逻辑调色板送给系统调色板。非活动窗口的颜色取决于还有哪些颜色未被采用,这些非活动窗口首先使用系统调色板中剩余不用的项,然后才为那些无法得到原色的颜色使用调色板上相近的颜色。当要在同一窗口显示具有不同逻辑调色板的设备无关位图时,Windows系统就无法支持。我们解决这个问题的办法是:合并所有的逻辑调色板。当逻辑调色板的总项数不超过256时,合并操作只需将所有的逻辑调色板连接在一起;当总项数超过256时,可采用以下方法来合并逻辑调色板:<BR>从不同逻辑调色板中选出最常用的颜色,组成一个256项逻辑调色板。<BR>反复地合并不同逻辑调色板中相邻颜色并用其平均值来取它们。这样就把所有逻辑调色板简化成一个相似的256色逻辑调色板。<BR><BR><BR><BR>与内存一样,键盘和鼠标输入也是Windows的共享资源,Windows下的C程序不能通过调用getchar函数直接从键盘上读取字符。在Windows中,应用程序不直接说明是从键盘还是从鼠标器读取输入消息,而是从被称作系统队列的键盘、鼠标和时钟等接受所有的输入消息,然后再由此队列负责把输入消息重新分配到各个应用程序中,即把系统队列中的消息复制到应用程序的相应队列中,也就是把输入重定向到相应的应用程序,此时如应用程序准备处理输入,它就从自己的队列中读取消息。<BR>键盘、鼠标和时钟的所有信息都具有同一格式,并且用同样的方式进行处理,另外,Windows给每个消息提供一个与设备无关的虚拟键码来标识该键,键盘产生与设备有关的扫描码。作为共享资源的键盘和鼠标,为Windows下运行的所有应用程序提供输入,从键盘输入的所有消息都直接送到当前处于激活状态的窗口,而与鼠标有关的消息则送到鼠标光标所指的窗口中。这里的消息具有特定的含义,下面简要介绍。<BR>Windows的消息系统是多任务执行环境的基础之一,它是发送消息的基本结构。从编程者的角度看来,一条消息可被视为某一事件的发生(如按下或移动鼠标、键盘击键),这些事件既可以由用户引发,也可以由应用程序产生,甚至Windows本身也能发出消息。<BR>Windows中的消息主要有两个作用:
(l)Windows通过它的消息系统实现多任务功能,支持多个任务同时并发执行。这一消息系统使得多个应用程序有可能共享一个物理处理器,每次Windows向应用程序传送消息的同时也分配该程序一段CPU时间。(2)使应用程序对该环境中的事件发生响应。每当一个事件发生时,Windows都记录一次,并把一个适当的消息发送给对此事件感兴趣的各个应用程序。所以从最基本的层次看,Windows应用程序的一个主要任务是处理消息。<BR><BR>Windows的第三个主要特点是设备独立性。一个应用程序可能要与多种硬件设备接口,例如显示器有多种类型,怎样才能使用户开发的应用程序可以不受设备变更的影响呢?<BR>非Windws环境(如DOS)下开发的应用程序必须为每种叶能用到的设备写驱动程序。假定现在要编写一个适用于各种打印机的图形打印程序,程序员必须为每种打印机编写不同的驱动程序。我们都知道,打印机的种类很多,如Epson
LQ-l500、Epson LQ-1600、HP LaserJet、HP
Deskjet等,显然这样做工作量很大。<BR>在Windows环境下,每种设备驱动程序——不管是显示器,还是打印机、键盘、鼠标——只需写一次。这样就不需要每个软件开发人员去编写自己的设备驱动程序,只要硬件生产厂商为其产品编写一个设备驱动程序,由他们把这些设备驱动程序连同设备一起提供给用户即可。Windows在安装时,已包括了系统现有的每一个设备的驱动程序,每当应用程序发出打印或画图命令时,Windows就通过适当的驱动程序输出打印数据或图形。把用户所需的设备驱动程序直接装入系统,从而省去许多重复琐碎的编程工作。<BR>该功能使Windwos应用的开发人员很容易开发自己的应用程序。应用程序只要与Windows打交道,而不必管任何具体的输入/输出设备,不必知道所用的打印机或图形显示器的型号规格。Windows为实现这种与设备无关的特性,规定与Windons接口的硬件须具备几种规定功能,它们是使用软件开发工具包(SDK)时保证相应的例程能正常工作的最基本功能。对于每类设备有一组含义不同的功能定义,你可参阅相应的Windows手册。<BR><BR>Windows的许多功能是由被称作是DLL的动态链接库提供的。DLL提供一个强大且灵活的图形用户口,从而增强了基本的操作系统。DLL中的预定义函数不是当.EXE文件生成时静态产生的,而是当应用程序动态装入时才与之相连的。这样做显然节省了内存,不管有多少应用模序在运行,同一时间RAM中只有程序库的一个备份。<BR>Windows改变了程序库的格式,因而有更强的通用性,它们不仅保留了其他DOS执行文件的同样格式,而且可包含任何程序可包含的内容。除了函数,程序库也可以编码数据甚至并入图形资源(如光标形状和位图)。Windows程序库扩展了共享资源的范围,并且为程序员节省了更多时间。<BR>从技术角度讲,当一个Windows应用程序调用一个Windows函数时,编译器必须为此函数产生一个远程调用该函数的机器码,这个函数位于Windows程序库的代码段。于是出现了这样一个问题:程序在Windows中真正运行之前,Windows函数的地址是未知的。为解决这一问题,Windows提供了延迟联编(也称动态链接)技术。使用MSC6.0版中的LINK或Pascal的链接程序即可完成这个任务。包含在SDK中的是特殊的“入口程序库”,用于为Windows程序的动态链接作准备。许多Windows的小模式程序都要用到LIBW.LIB和SLIBW.LIB这两个入口程序库。SLIBW.LIB程序为包含程序中可能调用的所有Windows函数的记录,这个记录定义了各个函数所在的Windows模块。<BR>假设有一个Windows应用程序要调用Windows中的PostMessage函数,当链接该程序时,链接程序在SLIBW.LIB中的表中找到PostMessage函数,取出该函数的序号并把这一信息嵌人程序的.EXE文件中。程序运行时,Windows再把程序的函数调用与真正的Post
Message函数链接起来。<BR><BR>旧的MS―DOS可执行文件的头标格式中没有地方可用来嵌入附加的动态链接信息,在Windows中采用的是一种新的.EXE文件格式,这种格式有一新文件头标。<BR>Windows程序模块包括KERNEL、USER和GDI,其中KERNEL完成内存管理、程序的装人与执行和任务调度等功能,它需要调用原MS―DOS中的文件管理、磁盘输入输出和程序执行等功能;USER是一个程序库,它用来对声音、时钟、鼠标器及键盘输入等操作进行管理;GDI是一功能十分丰富的子程序库,它提供了图形与文字输出、图象操作和窗口管理等各种与显示和打印有关的功能。Windows程序模块中的例程可帮助Windows中运行的程序执行多种操作,如发送和接收消息。上述KERNEL、USER和GDI模块中的库函数可被应用程序调用,也可被其他程序模块调用。把包含库函数的模块称为输出者(export)。新的可执行格式中有一个入口表用于指明模块内每个输出函数的地址。<BR>从应用程序的角度看,用到的库函数被认为是入口
(import)函数。应用程序对一个入口函数发出的远程调用可用不同的重定位表来确定。几乎所有的应用程序都至少包含一个入口库函数,或者称为被外部调用的函数。该Windows库函数一般来自某个程序库模块,用于从Windows
接收消息,该函数的使用标志必须是“export”,这才能使Windows允许它被一个外部模块正常调用。<BR>新的可执行格式还为程序或程序中每个代码段和数据段提供了附加的信息。一般说来,代码段标记成“可移动的”和“可丢弃的”,而数据段标记成“可移动的”。这就允许Windows在内存中移动代码段和数据段,当内存不够用时可以把代码段丢弃掉(因为代码段被标成是“可丢弃的”)。如果在后面又需要一个已被丢弃掉的代码段,只需简单地从初始的.EXE文件中把该代码段重新装入。<BR>Windows中还有一种执行格式为“调用时装入”。在这种格式下,它把程序或程序库定义为:如果没有其他代码调用该函数,则不把它的代码段装入内存。这一完善的内存管理技术允许Windows在正常情况下只够运行一个程序的内存空间中同时运行多个应用程序。<BR><BR>Windows应用程序是事件驱动(或称作消息驱动)的应用程序。Windows是一个多任务的操作系统,也就是说,在同一时刻,在Windows中有着多个应用程序的实例正在运行,在这样的一个操作系统中,不可能像过去的DOS那样,由一个应用程序来享用所有的系统资源,这些资源是由Windows统一管理的。<BR>那么,特定的应用程序如何获得用户输入的信息呢?事实上,Windows时刻监视着用户的一举一动,并分析用户的动作与哪一个应用程序相关,然后,将用户的动作以消息的形式发送给该应用程序,应用程序时刻等待着消息的到来,一但发现它的消息队列中有未处理的消息,就获取并分析该消息,最后,应用程序根据消息所包含的内容采取适当的动作来响应用户所作的操作。<BR>举一个例子来说明上面的这个问题,假设我们编了一个程序,该程序有一个File菜单,那么,在运行该应用程序的时候,如果用户单击了File菜单,这个动作将被Windows
(而不是应用程序本身!)所捕获,Windows经过分析得知这个动作应该由上面所说的那个应用程序去处理,既然是这样,Windows就发送了个叫做WM_COMMAND的消息给应用程序,该消息所包含信息告诉应用程序:“用户单击了File菜单”,应用程序得知这一消息之后,采取相应的动作来响应它,这个过程称为消息处理。Windows为每一个应用程序(确切地说是每一个线程)维护了相应的消息队列,应用程序的任务就是不停的从它的消息队列中获取消息,分析消息和处理消息,直到一条接到叫做WM_QUIT消息为止,这个过程通常是由一种叫做消息循环的程序结构来实现的。<BR><BR>一个典型的Windows应用程序的包括四个普通段—WinMain(
)函数、消息循环、窗口过程及窗口消息。<BR>WinMain(
)函数是所有Windows程序的入口点、出口点WinMain函数包含三个基本部分:过程说明、程序初始化及消息循环。<BR>WinMain(
)函数和过程说明如下:<BR>int PASCAL WinMain(HANDLE hInstance,HANDLE hPrevInstance, LPSTR
lpszCmdLine, int cmdShow);<BR>其中hInstance是标识程序的句柄(handle)。
hPrevInstance指明和哪一个实例(hInstance)相关——如果有的话。若程序具有同样的模块名,则认为程序是相关的。指明用于程序的命令行参数,cmdShow指明第一次打开窗口时主窗口的状态。<BR>程序初始化部分是winMain(
)函数的主体,利用winMain( )函数的程序初始化部分创建一个窗口。在创建窗口的过程中,必须定义窗口类。<BR>WinMain(
)函数的初始化部分的第一段定义WndClass数据结构的各个成员。在这部分代码段中,定义:<BR>用于窗口的光标<BR>用于填充窗口背景的刷子<BR>窗口过程的名字<BR>窗口类的风格,窗口主莱单<BR>类名<BR><BR>在开始介绍Windows图形程序设计之前,这里我想先介绍一些Visual
C++图形编程常见的问题和一些基本方法和技巧:<BR>一、在用户环境中确定系统显示元素的颜色 <BR>调用SDK函数GetSysColor可以获取一个特定显示元素的颜色。下例说明了如何在MFC函数CMainFrameWnd::
OnNcPaint中调用该函数设置窗口标题颜色。 <BR>void CMiniFrameWnd:: OnNcPaint () <BR>{
kk1}<BR>… <BR>dc.SetTextColor (:: GetSysColor (m_bActive
? <BR>OLOR_CAPTIONTEXT :
COLOR_INACTIVECAPTIONTEXT)); <BR>… <BR>} <BR>二、访问预定义的GDI对象 <BR>可以通过调用CDC::
SlectStockObject使用Windows的几个预定义的对象,诸
如刷子、笔以及字体。下例使用了Windows预定义的笔和刷子GDI对象在视窗中画一个椭圆。 <BR>//Draw ellipse using
stock black pen and gray brush. <BR>void CSampleView:: OnDraw (CDC*
pDC) <BR>{ kk1}<BR>//Determine size of view. <BR>CRect
rcView; <BR>GetClientRect (rcView); <BR>//Use stock black pen and
stock gray brush to draw ellipse. <BR>pDC->SelectStockObject
(BLACK_PEN); <BR>pDC->SelectStockObject (GRAY_BRUSH) <BR>//Draw the
ellipse. <BR>pDC->Ellipse
(reView); <BR>} <BR>也可以调用新的SDK函数GetSysColorBrush获取一个系统颜色刷子,下例用背景色在视窗中画一个椭圆:<BR>void
CsampleView:: OnDraw (CDC* pDC) <BR>{kk1} <BR>//Determine size of
view. <BR>CRect rcView; <BR>GetClientRect (rcView); <BR>//Use
background color for tooltips brush. <BR>CBrush *
pOrgBrush=pDC->SelectObject<BR>(CBrush::FromHandle(::GetSysColorBrush
(COLOR_INFOBK))); <BR>//Draw the ellipse. <BR>pDC->Ellipse
(rcView); <BR>//Restore original brush. <BR>pDC->SelectObject
(pOrgBrush); <BR>} <BR>三、确定GDI对象的属性信息 <BR>可以调用GDIObject::
GetObject。这个函数将指定图表设备的消息写入到 <BR>缓冲区。下例创建了几个有用的辅助函数。 <BR>//Determine if
font is bold. <BR>BOOL IsFontBold (const
CFont&font) <BR>{kk1} <BR>LOGFONT stFont; <BR>font.GetObject
(sizeof (LOGFONT), &stFont); <BR>return (stFont.lfBold)? TRUE:
FALSE; <BR>} <BR><BR>//Return the size of a bitmap. <BR>CSize
GetBitmapSize (const CBitmap&bitmap) <BR>{ kk1}<BR>BITMAP
stBitmap; <BR>bitmap.GetObject (sizeof (BITMAP),
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -