一、关于两个窗口的消息处理
要在一个MFC的项目中画图,我们知道任何windows程序都是靠消息来进行处理的。所以在新建一个单文档窗口的时候,我们会得到两个窗口,一个框架窗口,一个视图窗口,视图窗口会覆盖在框架窗口上面。这也就是说,我们一切对框架窗口的操作都只能够被视图窗口捕获。
二、MFC消息映射机制
在任何一个类中如果要添加一个消息响应函数之后,都会在三个地方产生代码。首先会在相应类的头文件中添加函数原型,函数原型前面有afx_msg关键字修饰,表明这是一个消息处理函数。
其次,会在该类的源文件中BEGIN_MESSAGE_MAP宏之间定义的消息映射表中出现wm_lbuttondown,该类中的这个消息映射表,就是把一些消息与一些相应的消息处理函数关联起来,通过这种机制,一旦有消息映射表中的消息出现,那么就立刻调用相应的消息处理函数来进行处理。第三处,就是消息函数在该类的源文件中的定义了。具体的实现也在那里。
MFC的消息映射机制和WIN32程序的消息映射机制有所不同,win32程序中,产生一个消息时,首先这个消息被操作系统放置到程序的消息队列中,之后程序通过getmessage这个函数在消息队列中依次取出消息,然后通过dispatchmessage函数交给操作系统处理,操作系统会调用相应的wndproc窗口过程函数对消息进行分类处理。
每一个类都有一个消息处理函数映射表,里面有该类处理的消息已经对应的消息处理函数的指针。比如视图类,与视图类对象相关的,肯定有一个窗口(视图类窗口),这个窗口的句柄与这个对象的指针一一对应。MFC在后台会维护一个C++对象指针和窗口句柄的对照表。当我们收到某一个消息的时候,消息的第一个参数为我们提供了消息产生的窗口句柄,然后通过这个窗口句柄可以找到相应的C++指针,然后这个指针会被传递到窗口类的基类Cwnd里面,调用一个WindowPorc函数,这个函数里面有一个叫做onwndmsg的函数,他会现在对应的子类的头文件中找,是否有消息处理函数的原型,然后在源文件的消息映射表中找看是否有相应的处理函数,找到之后就会接着调用该消息处理函数。
三、绘图
想要在MFC程序中进行绘图,就必须要用到DC也就是设备描述表。它是一个包含设备信息的结构体。DC也是一种资源,为了屏蔽底层物理绘图设备的不同,给我们一个绘图的标准。
绘图的操作应该在我们抬起鼠标按键的那个消息的相应函数中,首先要得到一个HDC dc句柄dc用于画图,之后要将当前位置移动到起点的位置,之后用lineto函数画图,完成之后记得释放掉hdc的资源。
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
HDC hdc;
hdc = ::GetDC(m_hWnd);
MoveToEx(hdc,m_ptOrigin.x, m_ptOrigin.y,NULL);
LineTo(hdc, point.x, point.y);
::ReleaseDC(m_hWnd,hdc);
CView::OnLButtonUp(nFlags, point);
}
CDC *pDC = GetDC();
pDC->MoveTo(m_ptOrigin);
pDC->LineTo(point);
ReleaseDC(pDC);
CView::OnLButtonUp(nFlags, point);
所以,这时,如果我想在框架窗口上划线的时候,只需要改变构造函数传递进去的类对象的指针,就能够实现在框架窗口上划线。因为框架窗口是视图窗口的父类,所以利用getparent函数就能够得到父类对象的指针。然后获得父类窗口上面的dc资源,这样就可以吧线划到父类的框架窗口中。所以说,在哪绘图主要是看得到了哪个窗口的dc资源。一般来说我们绘图都是在客户区中进行,非客户区是访问不到的。
画线还有一个类叫做CWindowDC类,他也是CDC的派生类,他有一个好处就是,画图的功能不但能够访问到客户区还能够访问到非客户区。
四、获取画笔
设备描述表里面都有一个默认的黑色画笔,如果想要用其他颜色或者其他种类的画笔来进行绘图的话uart,那么要重新创建一个画笔。
创建画笔的类是CPen。里面有三种构造函数可以供我们使用。
在进行画图的时候,即使创建了新的画笔,也不能立刻进行使用需要将新的画笔加入到设备描述表中才可以。
所以,会利用selectobject函数,来把画笔加入到设备描述表中。因为有的时候,我们在局部和全局所需要的画笔是不同的,当局部的绘图任务完成之后,我们需要将画笔还原到更改之前的样式,这就需要在一开始 更改画笔之前保存上一次的画笔信息,恰好selectobject这个函数的返回值就是返回更改之前的画笔信息,所以要先利用一个变量将这个老的画笔信息保存起来,等到局部的绘图人物完成之后,再利用selectobject函数将画笔复原即可。
CWindowDC dc(this);
CPen pen(PS_SOLID, 10, RGB(255,0,0));
CPen *ptr = dc.SelectObject(&pen);
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);
dc.SelectObject(ptr);
CView::OnLButtonUp(nFlags, point);
画刷通常是用来填充一定的区域。画刷的类是CBrush,然后利用一些成员函数可以完成填充工作。
CBrush brsh(RGB(255,8,9));
CWindowDC dc(this);
dc.FillRect(CRect(m_ptOrigin, point), &brsh);
CView::OnLButtonUp(nFlags, point);
六、位图画刷
对于位图来说,有它专用的画刷。
刚才说画刷的构造函数有三种形式,其中一种就是创建位图画刷的。画刷只是一个工具,那么实际画的时候需要一个位图。
位图的创建有一个CBit类,创建这个对象之后还不能够使用这个位图,需要在资源中创建一个位图资源,然后将这个资源与位图对象关联起来,才能够利用这个位图对象操作真正的位图资源。
七、透明画刷
Rectangle可以利用设备描述表中的这个函数来画一个矩形,但是在画两个矩形重叠的时候,新的矩形会覆盖住一部分老的矩形,这时因为设备描述表中还有一个默认的 白色的画刷用于填充我们所画矩形的内部。如果我们希望矩形内部是透明的,也就是能够看到遮挡的部分,这就需要透明画刷来实现。
透明画刷是的获取是靠一个返回空画刷句柄的函数叫做GetStockObject.绘图的时候用的是画刷对象,所以说要把这个函数返回的画刷句柄转化为画刷对象,需要借助的是CBrush的FromHandle函数来实现。
八、连续线条
画直线的方法我们都知道了,只要找到起点和终点就可以利用lineto方法来画。如果要是画连续的线条,就要在鼠标拖动的过程中不断获取到终点然后每移动到一个点就划线。这样就需要来响应鼠标的移动消息。并且,鼠标按下的消息开始视作开始画图,鼠标按键抬起的消息响应为停止画图,为了在相应鼠标移动消息的函数中得知是否进行画图操作,可以在全局设置一个bool变量,在其他两个消息函数中通过改动这个变量来和画图的函数进行交互即可。
void CDrawView::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CPen pens(PS_SOLID,1,RGB(255,7,100));
CClientDC dc(this);
CPen *ptr = dc.SelectObject(&pens);
if(m_bDraw == true)
{
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);
m_ptOrigin = point;
}
dc.SelectObject(pens);
CView::OnMouseMove(nFlags, point);
}