⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 04.3.3 透明画刷.txt

📁 网上第一本以TXT格式的VC++深入详解孙鑫的书.全文全以TXT格式,并每一章节都分了目录,清晰易读
💻 TXT
字号:
4.3.3 透明画刷
下面我们利用CDC的 Rectangle函数绘制一个矩形,代码如例4-17所示。
........‘ I 125 

第4

例 4-17 
void 	CDrawView : :OnLButtonUp(UINT nFlags , CPo工nt point) 
//创建并获得设备描述表 
CClientDC dc(th工 s) ; 
1/绘制一个矩形 

dc . Rectangle(.CRect(m-ptOrigin, point)) ; 
CView 	:: OnLButtonUp(nFlags , point); 
Build	并运行 Draw程序,然后拖动鼠标即可
在程序窗口中绘制矩形。但是,当我们绘制两个相	. 
互重叠的矩形时,后绘制的矩形就会遮盖住先前绘制的矩形,如图 4.25所示。这是因为设备描述表中有一个默认的白色画刷,在绘图时,它会利用这个画刷来填充矩形内部。所以,当位置存在重叠时,后绘制的矩形就会把先前绘制的矩形遮挡住。也蜻 厂寸「一1M

如果希望矩形内部是透明的,能够看到被遮挡的图形,那么就要创建一个透明画刷。此时,我们图 4.25存在遮盖情况的矩形会立即想到 CBrush类是否有创建透明画刷的方法。但很遗憾, CBrush类井没有提供这样的函数,我们只能另想其他的办法了。
在第一章中曾介绍过 GetStockObject这个函数,利用该函数可以获取一个黑色或白色的画刷旬柄。这个函数是否能够获得一个透明画刷的句柄呢?从 MSDN提供的帮助信息中,可以看到该函数的参数取值之一可以是 NULL_BRUSH,以获取一个空画刷。那么,这个空画刷是否就是我们所需要的透明画刷呢?我们可以试试看。
但这时存在一个问题,利用 GetStockObject函数获取的是一个画刷句柄。而我们在进行绘制操作时需要的是一个画刷对象。如何从画刷句柄转换为画刷对象呢? CBrush类提供了一个 FromHandle函数用来实现这样的功能。该函数的声明如下所示。 
static CDC* PASCAL FromHandle( HDC hDC ); 
如例 4-18所示就是具体的创建透明画刷并进行相应绘制操作的实现代码。
例 4-18 

void 	CDrawView : :OnLButtonUp(UINT nFlags , CPo工nt point) 
//创建并获得设备描述表 

CClientDC dc(this); 
//创建一个空画刷 
CBrush *pBrush = CBrush: :FromHandle((HBRUSH)GetStockObject(NULL_ 
BRUSH) ) ; 
/1将空画刷选入设备描述表 
CBrush *pOldBrush = dc.SelectObject(pBrush); 
126 I ~胁' 

//绘制一个矩形 
dc 	.Rectangle(CRect(m-ptOrigin, point}} ; 

//恢复先前的画刷 
dc .SelectObject(pOldBrush}; CView: :OnLButtonUp(nFlags , point}; 
上述例 4-18所示代码中有以下几处需要注意的地方。 
. 	FromHandle函数的调用方式,这是调用类的静态成员函数的方式。
·由于 
GetStockObject函数返回的类型是 HGDIOBJECT,需要进行一个强制类型转换,将其转换为 HBRUSH类型。

·这里创建的 
pBrush变量本身就是一个指针类型,因此在调用 SelectObject函数时,该变量前面不用再加上取址符(&)。 


·	另外,我们可以比较一下前面使用的 FillRect函数和这里的 Rectangle函数。首先,二者都能绘制矩形,但前者在参数中提供了绘制使用的画刷,因此它就直接利用此画刷来填充矩形,并不需要先把需要的画刷选入设备描述表中。而后者并没有提供画刷这个参数,因此先要把需要的画刷选入设备描述表中,然后再调用此函数来绘制矩形。 
Build并运行 Draw程序,然后拖动鼠标在窗口中任意绘制多个相互重叠的矩形。此时可以发现,能够看到被覆盖的矩形线条了,如图 4.26所示。由此可见,利用参数 NULL_BRUSH调用 GetStockObject函数可以实现透明画刷这一功能。
恒生丝丝旦旦些	叫坷圄



图 4.26透明画刷运行结果

1.静态成员函数
为了更好地理解上述代码中 FromHandle函数的调用方式,我们再创建一个 Win32控制台应用程序 Test,并给此程序新增一个 C++源文件 Test.cpp,然后在其中添加如例 4-19所示代码。
例 4-19 

#include <iostream.h> class Point 

"‘I 127 

public: void output () 
static void init() 
} 
void rnain ( ) 
POlnt pt ; 
pt.init() ; 
pt . output() ; 

上述例4-19所示代码中,首先包含了标准输入输出流头文件iostream.h,接着定义了一个Point类。该类有两个public类型的函数:一个是成员函数output,另一个是静态成员函数init。在程序的主函数 (m出n)中,首先定义了一个Point对象,然后依次调用该对象的两个成员函数。代码非常简单, Build这个程序,发现成功通过。
现在把mm函数中的代码改成如例4-20所示内容。 
~IJ 4-20 

void rnain () 

11  point pt;  
11  pt . init ( ) ;  
11  pt . output();  

point: : init ( ) ; point : : output () ; 
. 
也就是说,我们不像刚才那样先定义一个Point对象,然后才调用该对象的成员函数。而是直接利用Point类名,加上作用域标识符(::),再加上函数名这样的形式来调用CPoint类的成员函数。 Build程序,系统会报告如下错误。 
D:\VC++深入编程\CHAPTER4\Test\Test.cpp(29) : error C2352: 'Point: :output' : illegal call of non-static rnernber function 

D:\VC++深入编程\CHAPTER4\Test\Test.cpp(6) : see declaration of 'output' Error executing cl.exe. 


该错误信息提示:非法调用非静态成员函数Point::output。但为什么init函数的调用没有出错呢?我们可以看到它的定义前面有一个((static"限定符,表明该函数是一个静态函数。静态成员函数和静态成员变量属于类本身,在类加载的时候,即为它们分配了空间,
所以可以通过类名::画数名或类名:变量名来访问。而非静态成员画数和非静态成员属于

128 I ~~~ 


对象的方法和数据,也就是应该首先产生类的对象,然后通过类的对象去引用。
下面我们再次修改上述例4-19和例 4-20所示代码,给Point类添加两个具有private访问权限的int类型变量x和y,井在init函数中对它们进行赋值,同时将刚才出错的output调用注释起来。修改后的程序代码如例4-21所示。
例4-21

件include <iostream .h> 
class point 
public : void output ( ) { 
static void init() 
x=ü; 
Y=O ; 
private : lnt x , y; 
void ma工n ( ) 
Point :: init() ; // Point : :output () ; 
利用 Build命令生成Draw程序,系统会报告以下两个错误。 
0:\VC++深入编程\CHAPTER4\Test\Test .cpp(13) : error C2597 : illegal reference to data member 'Point ::x' in a static rnember function 

0 : \VC++深入编程\CHAPTER4\Test\Test. cpp (14) : error C2597 : illegal reference to data rnember 'Point::y' in a static rnember function 


这些错误提示是说:在静态成员中非法引用 Point对象的x和y数据成员。为什么会出现这样的错误呢?上面己经讲过, init是一个静态函数,不属于某个具体的对象,也就是说在还没有产生 Point类的任一个具体对象时,该函数就已经存在于程序的代码区了。但这时, Point类的数据成员x和y还没有分配内存空间,这样,在init函数中对它们进行赋值操作怎么会成功呢?也就是说,在静态成员函数中是不能调用非静态成员的,包括非静态成员函数和非静态成员变量。
如果把上述例4-21所示代码中对x和y的赋值操作放到一个非静态成员函数中,比如本例中的output函数中,程序将会成功生成执行文件。因为当程序构造一个 Point类型的对象时,就会给成员变量x和y分配内存空间,给output成员函数分配代码区空间。这样,在output函数中访问x和y时,它们都己经存在于内存空间中,所以可以成功赋值。
~~~ I 129 

第4

这时,读者也许会有这样的疑问,在非静态成员函数中是否可以调用静态成员函数呢?我们可以试试看。在上述例4-21所示的output函数中加一条调用 init方法的语句,结果如例4-22所示。
例4-22 

void output ( ) 

工nit() ; 
利用 Build命令生成Draw执行程序,此时该命令将会成功实现。我们可以分析一下这种情况。因为静态成员函数属于类本身,在类的对象产生之前就已经存在了,所以在非静态成员函数中是可以调用静态成员函数的。
实际上,关于函数之间的引用许可,可以从内存模型这一角度来考虑。也就是说,无论采用什么样的操作,程序代码都是在内存中运行的,只有在内存中占有了一席之地,我们才能够访问它。如果一个成员函数或成员变量还未在内存中产生,结果是无法访问它的。读者只要把握了这一内存模型,就可以知道函数之间的调用许可了。
根据上面的内容,可以推导出,静态成员函数只能访问静态成员变量。我们可以在上述例4-21所示例子中,将Point类的成员变量x和y的定义之前加上static限定符,使它们成为静态成员变量。 

static int x , y; , 
然后利用Build命令生成Draw执行程序,此时将出现以下错误提示信息。 
--------------------Configuration: Test -wi口32 Debug----------------叩---
. Compiling. . . 
Test.cpp 
Linking. . . 
Test.obj : error LNK2001: unresolved external symbol .private: static int point: :y" (?y@Point@@OHA) Test.obj : error LNK2001: unresolved external symbol .private: static int Point: :x" 
( ?x@Point@@OHA) 
Debug/Test.exe : fatal error LNKl120: 2 unresolved externals 
Error executing link.exe. 
Test.exe -3 error(s) , 0 warning(s) 
我们发现上述错误并不是编译错误,而是链接时发生的错误。之所以会出现这样的错
误,是因为对于静态成员变量,必须对它进行初始化,并且应在类的定义之外进行此操作。我们在Point类的定义之外,加上如下两条初始化语句。 
int Point:: x=O; 

int Point: :y=O; 

130 I胁"
详解

再次利用 Build命令生成 Draw执行程序,将会发现成功实现。
这里,还有一种情况需要读者注意,如果没有初始化静态成员变量,同时在 main程序中也没有访问这些静态变量,那么程序 Build时也不会报错。读者可以试试这种情况。
让我们再返回到前面的 Draw工程中 (例 4-18所示代码),因为 FromHandle是 CBrush类的一个静态成员函数,因此在调用时,可以直接写上 CBrush类名,接着加上域作用符 C :: ) ,再加上 FromHandle函数名。 

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -