MFC消息映射机制的剖析,讲述如何运用ClassWizard,,理解发送给窗口的消息是如何被MFC框架通过窗口句柄映射表和消息映射表来用窗口类的函数进行响应的。掌握设备描述表及其封装类CDC的使用,CDC是如何与具体的设备发生关联的,融合具体的画图程序进行分析。如何设置封闭图形的填充刷子(位图画刷与透明画刷的使用)。
主要介绍一些绘图方面的知识。
程序实例:
新建工程:->MFC AppWizard[exe] 文件面为Draw 选择单文档的应用程序 完成之后编译运行。
画直线、画图、画刷画位图、空白画刷操作步骤
l 明确画图的思路:按下鼠标左键画图的原点,拖动鼠标到另外一个位置松开鼠标左键画图的终点。
l 画图需要消息的捕获。捕获的对象是鼠标左键按下WM_LBUTTONDOWN和抬起WM_LBUTTONUP的消息。
l 这两消息的捕获、响应在窗口类中进行,可以在框架类Frame类中也可以在视类View类中。
l 要对消息进行捕获我们需要设置一些额外的函数,这些函数MFC都有提供。如我们在CMainFrame上点击右键选择增加windows消息处理Add windows message handler……,选择WM_LBUTTONDOWN点击“Add Handler”添加而后点击“Edit Existing”退出。CMainFrame message handlers在CMainFrame最底下被创建。具体内容详见下面程序代码。(这段代码添加的位置是在implementation of the CMainFrame class中,文件名为CMainFrame.cpp)
l 为了简单说明每一步的作用这个程序做出的反应,在消息响应函数中添加一个MessageBox程序代码,这个MessageBox要比先前的WinApi中使用的少一个” HWND hWnd,”参数。MessageBox在CWnd中是属于其中的一个成员函数,他不需要句柄因为有一个数据成员保存了和这个窗口相关的句柄。这里只有一个MessageBox函数在Run的时候没有弹出来!具体原因看注意。
l 作为对比我们在CDrawView中也添加一个消息响应函数WM_LbuttonDown,同样设置一个MessageBox函数,这个函数的参数LpszText为”View click!”。Run我们发现这个消息弹出来了。
l 把框架类的的对WM_LBUTTONDOWN响应的OnLButtonDown函数删除。删除方法看注意。
l 查看当我们增加WM_LBUTTONDOWN这个消息响应的时候在头文件和源文件里边都增加了些什么。增加的程序代码详见5 。当我们增加一个消息响应函数的时候会在三个地方增加代码,头文件中消息原型声明、源文件中跟消息响应函数关联的宏、源文件中消息响应函数。这里在构造函数中给成员变量赋初值。
l MFC中的消息响应。粗略的来看MFC中的消息响应跟WinAPI中消息响应不一样没有调用GetMessage函数,也没有调用回调函数进行消息响应,MFC中对消息的处理方法为消息映射。
l 继续画图。我们调用的OnLButtonDown(UINT nFlags, CPoint point)里边的CPoint类,这个类就是代表了一个点。当我们按下鼠标左键这个点的参数已经通过CPP的对象传递进来,所需要做的就是在这个响应函数中把这个点的参数保存。做法:View类中增加一个私有成员变量 CPoint m_ptOrigin ,View构造函数中将这个点初始化为零,在On_LButtonDown中用给这个点的赋值语句” m_ptOrigin=point;”代替MessageBox函数保存点的参数,见程序段8
l 对终点的响应,也就是对鼠标左键弹开的响应。要增加消息响应函数WM_LBUTTONUP.。
l 添加变量HDC hdc ,用hdc保存获取到的View类窗口所表示的窗口的句柄,获取窗口句柄的程序段:hdc=::GetDC(m_hWnd);
l 作图函数:MoveToEx 。 先将点移动到源点然后再到终点,画线用LineTo划一条线。至此用API函数画图就已经结束了。相关的程序段见10
l MFC给我们封装了一个画图的代码。MFC将所有跟窗口相关的封装到了CWND中所有跟左图相关的都封装到了CDC中。用MFC提供的封装函数CDC进行画图
l 第三种做图的方式CClientDC封装函数作图。
l 如果我们想要获得View窗口的父窗口指针即Frame框架窗口指针(句柄),怎么做呢?看注意。
l 第四种作图方式:CWindowDC ,与CClientDC一样在构造的时候自己调用GetWindowDC再析构的时候自己调用ReleaseDC 。不过CWindowDC作图区域范围更广了可以在非客户区域作图。CWindowDC派生于CDC。
l 画彩色的线条,先前画的都是黑色的主要是因为在设备描述表中有一个缺省的画笔颜色为黑色。我们要创建一个新的画笔并放到设备描述表中就可以用新的画笔的颜色画画图。画笔用CPen,将画笔注册到注册表用的是SelectObject
l 用画刷画矩形,画刷CBrush,矩形CRect(Rectangular),填满区域FillRect 。
l 用CBrush( CBitmap* pBitmap )创建一个带位图的画刷;用MSDN查看位图CBitmap函数。这个画刷画的是位图,填出的是位图。
l 空白画刷画图。首先画矩形用的参数Rectangle(CRect(m_ptOrigin,point));(Rectangle有两种调用参数的形式)。这个比较复杂些详见注意中的一些介绍。
l 画连续的线,我们需要加入一个消息响应函数WM_MOUSEMOVE , 增加一个BOOL型View中的变量,这个变量要在构造函数中给这个变量赋初值为FALSE ,在WM_LBUTTONDOWN中定义为TRUE,在WM_LBUTTONUP中定义为FALSE,在WM_MOUSEMOVE中编写程序代码。
l 画不带边框的扇形,需要再定义一个CPoin的变量,并在构造函数中赋初值为零,在WM_LBUTTON中给他赋值,在WM_MOUSEMOVE中编写程序代码。
l 采用绘画的模式SetROP2进行画图。很有趣的。
注意:
l 对于CWnd中的Message因为他的参数规定了很多的缺省值因此可以简写。
l 在框架类中如果只有一个Message函数消息框不能弹出,消息捕获失败;在View类只有一个Message函数但消息框弹出成功,证明消息捕获成功。原因为:View类窗口覆盖在Frame类窗口之上,我们在窗口中操作始终是在View类窗口里,Frame窗口在底层,移动鼠标的动作是感知不到的,因此就无法捕捉消息。
l 增加相应函数还有一种办法:View->MFC ClassWizard -; ClassWizard是MFC的类的向导,这里边除了可以增加消息的响应函数外还可以增加命令的响应函数,也可以重载虚函数;
l 注意函数的删除,如果这个函数是通过添加的方式增加的,不要直接在CPP文件中删除,因为这样删除不能把内部关系删除干净。删除的办法为在该函数上点右键->Delete 删除就OK了。
l 消息映射:方式一:在基类中针对每一种消息设置一个虚函数,子类要对这个消息进行响应只需重写这个虚函数,当我们调用这个函数时根据函数的多态性子类有的调用子类的子类没有的调用基类的,也可以利用这个消息路由到消息执行的函数,如果用虚函数MFC要有100多个虚函数这样会对我们电脑资源造成很大的浪费,MFC有一个关于窗口句柄和窗口指针的对照表(即句柄与CPP类指针的映射),消息结构体中第一个参数就是指向与此消息相关的窗口,根据这个句柄查找对照表找到对应对象的指针,通过这个指针传递给基类由基类来调用这个函数WindowProc(),我们可以通过以下路径来查找这个函数“程序所安装的磁盘名称:/Softs/Microsoft Visual Studio/VC98/MFC/SRC”搜WindowProc,找到“WINCORE.CPP”这个是CWND的核心代码,双击打开后搜索“WindowProc”找到的代码见6,WindowProc调用OnWndMsg函数来完成消息映射的具体处理。OnWndMsg根据消息的结构体相关的参数回到相应的头文件中需找在DECLARE_MESSAGE_MAP()之前有没有消息响应函数原型的声明有则调用无则调用基类,如果有则到源文件中在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之间有没有消息响应映射的宏,找到了这个消息响应函数就会调用这个消息响应函数进行处理,如果没有此类信息就会交由基类进行处理。
l hdc=::GetDC(m_hWnd);的注释两个问题一个是为什么用’::’二是为什么直接用m_hWnd,而不对他进行声明!用’::’因为我们引用的win32的全局函数也就是SDK的函数,这里需要明确GetDC的来源,否者就会认为我们用的是CWnd派生出来的CView中的成员函数,CWnd有自己的GetDC这个成员函数;m_hWnd,因为系统会为派生类自动命名他的参数,自动命名的方式就是在系统提示的行数参数前面增加‘m_’,以’m_*(系统的默认参数名)’作为派生类的参数名称,这个是系统自动生成的;二是:CWND派生出来的类都有一个成员保存跟这个CPP类相关的窗口句柄,CDrawView使用CWND派生出来了,因此CDrawView有一个成员保存了跟CDrawView相关的窗口的句柄,因此可以直接用m_hWnd这个名字。(这里用的都是HDC,HDC不是MFC中而是CWND的,因此调用函数与HDC使用的也都是CWND的,而这些函数在MFC中也有封装但是与CWND的有一定的差别,因此我们要定义好这些函数的源。)
l 关于MoveTo中lpPoint参数,他要求的是旧的当前的点,因为我们用的是划直线,因此这个参数我们现在用不着所以设置这个参数为NULL 。
l 我们在MFC中作图可以用win32的API函数也可以直接用MFC封装的函数。我们用MFC封装的函数的时候,所有的函数都是CWnd的成员函数,不用双冒号’::’来明示函数来源。
l 不论用的是HDC还是用的CDC都不要忘记用ReleaseDC函数来释放,不同的这两个一个要用双冒号,一个粗需要用。
l CClientDC封装函数作图。它定义的是一个对象,虽然MSDN中定义的为一个指针,虽然我们使用指针构造的,但是他是一个对象,既然为对象就用’.’操作符,不用指针操作符。另外这个定义后的对象作用于CView,用this指针就可以指向它本身。自动调用GetDC,在程序周期结束析构的时侯自动掉ReleaseDC。
l 如果在View内想要获得Frame窗口的指针,我们可以根据语义拼写一个函数GetParent ,而后将这个函数名拿到MSDN中查找看看有没有适合的。一般都可以找的合适的。
l 用此绘图发现指针有点不准。可以用程序代码中13的的坐标参数增加值 X+2,Y+25,只是糊涂正好为鼠标的顶端。
l View的客户区就是那个白框,画图不会画到工具栏上,而Frame类的客户区包含了工具栏,所以会画到菜单栏上。View类没有非客户区。而Frame框架类有非客户区域,非客户区域就是标题栏和菜单栏。如果修正了参数的13程序代码那么也解决了画图会超出View类客户区域的问题。作图只能做到客户区。
l 对于CWindowDC如果不改变参数值,可以将直线画到非客户区域。
l 调用windows桌面句柄,有CWindowDC画图,可以画到桌面上。掉用桌面句柄用HWND GetDesktopWindow(VOID);直接输入GetDesktopWindow(VOID);
l 不论获取的句柄是谁的,画图的起点和终点只有都在View窗口区域内鼠标的左键按下和弹起的WM消息才能捕捉到。
l 除了获取初始Origin点之外,大部分的响应都是在获得WM_LBUTTONUP这个消息之后做出的,因此画线的函数都在OnLButtenDown中。
l SelectObject他的意义为指向被替换掉的画笔、画刷等的指针。A pointer to the object being replaced
l PS_DASH、PS_DOT、PS_DASHDOT、PS_DASHDOTDOT这里几种笔画线的时候线宽必须是1。