第4章 简单绘图
1.MFC消息映射机制
通过MFC提供的向导添加消息响应函数后,ClassWizard会在所选类的头文件和源文件中添加三处信息:
以在CDrawView视类中通过类向导添加鼠标左键按下这一消息为例:
①消息响应函数原型
在CDrawView类的头文件中添加如下代码:
class CDrawView : public CView
{
// Generated message map functions
protected:
//{{AFX_MSG(CDrawView)
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
在两个AFX_MSG注释宏之间添加一个函数原型OnLButtonDown,该函数声明的前部有一个afx_msg限定符,这也是一个宏,该宏表明这个函数是一个消息响应函数的声明。
②消息映射宏
在CDrawView类的源文件中添加ON_WM_LBUTTONDOWN消息映射宏:
BEGIN_MESSAGE_MAP(CDrawView, CView)
//{{AFX_MSG_MAP(CDrawView)
ON_WM_LBUTTONDOWN()
//}}AFX_MSG_MAP
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
END_MESSAGE_MAP()
BEGIN_MESSAGE_MAP和END_MESSAGE_MAP这两个宏之间定义了CDrawView类的消息映射表,其中有一个ON_WM_LBUTTONDOWN消息映射宏,这个宏的作用就是把鼠标左键按下的消息(WM_LBUTTONDOWN)与一个消息响应函数关联起来
③消息响应函数的定义
在CDrawView类的源文件中可以看到OnLButtonDown函数的定义:
void CDrawView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CView::OnLButtonDown(nFlags, point);
}
只要遵照上述步骤定义了与消息有关的三处信息后,就可以实现消息的响应处理。MFC中采用的这种消息处理机制称为MFC消息映射机制。
MFC消息映射机制的具体实现方法是:在每个能接收和处理消息的类中定义一个消息和消息函数静态对照表,即消息映射表。在消息映射表中,消息与对应的消息处理函数指针是成对出现的。某个类能处理的所有消息及其对应的消息处理函数的地址都列在这个类所对应的静态表中。当有消息需要处理时,程序只要搜素该消息静态表,查看表中是否含有该消息,就可以知道该类能否处理此消息。如果能处理该消息,则同样依照静态表能很容易找到并调用对应的消息处理函数。
2.绘制线条
定义一个CPoint类型的成员变量m_ptOrigin,在OnLButtonDown消息响应函数中对其赋值,保存下鼠标按下点的信息:
void CDrawView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
m_ptOrigin = point;
CView::OnLButtonDown(nFlags, point);
}
在OnLButtonUp消息响应函数中实现绘制:
①利用SDK全局函数实现画线功能
void CDrawView::OnLButtonUp(UINT nFlags, CPoint point)
{
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);
}
②利用MFC的CDC类实现画线功能
CDC *pDC = GetDC();//此处调用的是CWnd的GetDC成员函数
pDC->MoveTo(m_ptOrigin);
pDC->LineTo(point);
ReleaseDC(pDC);
CDC类封装了所有与绘图相关的操作,该类提供了一个数据成员m_hDc用来保存与CDC类相关的DC句柄。
③利用MFC的CClientDC类实现画线功能
CClientDC dc(this);
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);
CClientDC类派生于CDC类,并且在构造时调用GetDC函数,在析构时调用ReleaseDC函数。
若将上述代码中构造CClientDC对象的部分替换为如下代码:CClientDC dc(this);即构造了一个与视图类的父窗口,也就是框架窗口相关的CClientDC对象,这是就可以在框架窗口的客户区进行绘图了,也就是说这时可以在工具栏上进行绘图了。因为工具栏属于框架窗口的客户区而非视图类的客户区,因此,与视图类相关的CClientDC不能在工具栏上绘图,同时,因为菜单栏是框架窗口的非客户区,所以即使是与框架窗口相关的CClientDC也无法实现在框架菜单栏上进行绘图。
④利用MFC的CWindowDC类实现画线功能
CWindowDC dc(GetParent());
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);
CWindowDC也派生于CDC类,使用CWindowDC对象可以访问整个窗口区域,包括框架窗口的非客户区和客户区。通常都是在客户区中绘图,但是如果利用CWindowDC类,就可以实现在工具栏和菜单上绘图。
⑤在桌面窗口中画线
CWnd类的GetDesktopWindow成员函数可以获得Windows桌面窗口的句柄,使用如下代码可以实现在桌面窗口中画线:
CWindowDC dc(GetDesktopWindow());
⑥绘制彩色线条
可以利用MFC提供的CPen来创建画笔对象,该类封装了与画笔相关的操作。在程序中,当构造一个GDI对象后,该对象并不会立即生效,必须通过SelectObject函数将其选入设备描述表,它才会在以后的绘制操作中生效,并且该函数返回先前被选对象的指针。
CPen pen(PS_SOLID,1,RGB(25,23,142));
CClientDC dc(this);
CPen *pOldPen=dc.SelectObject(&pen);
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);
dc.SelectObject(pOldPen);
3.使用画刷绘图
MFC提供了一个CBrush类,可以用来创建画刷对象,画刷通常用来填充一块区域。
①简单画刷
CBrush brush(RGB(255,0,0)); //创建一个红色画刷
CClientDC dc(this); //创建并获得设备描述表
dc.FillRect(CRect(m_ptOrigin,point),&brush); //利用红色画刷填充鼠标拖曳过程中形成的矩形区域
FillRect函数将用指定的画刷填充全部的矩形,包括矩形的左边和上部边界,但不填充右边和底部边界。
②位图画刷
CBrush类有如下构造函数CBrush(CBitmap* pBitmap);CBitmap类是位图类,仅调用CBitmap的构造函数并不能得到一个有用的位图对象,还需要调用一个初始化函数来初始化这个位图对象。CBitmap类提供了多个初始化函数,如LoadBitmap、CreateBitmap、CreateBitmapIndirect等。LoadBitmap函数有如下声明:
BOOL LoadBitmap( LPCTSTR lpszResourceName ); //
BOOL LoadBitmap( UINT nIDResource );
lpszResourceName:Points to a null-terminated string that contains the name of the bitmap resource.
nIDResource:Specifies the resource ID number of the bitmap resource.
假设已经创建了一个名为IDB_BITMAP1的位图资源,则可如下使用位图画刷:
CBitmap bitmap;
bitmap.LoadBitmap(IDB_BITMAP1);
CBrush brush(&bitmap);
CClientDC dc(this);
dc.FillRect( CRect(m_ptOrigin,point),&brush);
③透明画刷
设备描述表中有一个默认的白色画刷,在绘图时它会利用这个画刷来填充矩形内部,所以当位置存在重叠时,后绘制的矩形就会把先前绘制的矩形遮挡住。
CBrush类并没有创建透明画刷的方法,可以通过将GetStockObject函数的参数取值为NULL_BRUSH来获取一个空画刷的句柄,然后使用CBrush类提供的静态函数FromHandle来实现画刷句柄至画刷对象的转换,FromHandle函数的声明如下:static CBrush* PASCAL FromHandle( HBRUSH hBrush );
CClientDC dc(this);
CBrush *pBrush=CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
CBrush *pOldBrush=dc.SelectObject(pBrush);
4.绘制连续线条
void CDrawView::OnMouseMove(UINT nFlags, CPoint point)
{
CClientDC dc(this);
if (m_bDraw==true){ // m_bDraw成员变量在鼠标按下时为true,在鼠标弹起时为false
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);
m_ptOrigin = point; //绘制连续线条,此刻的终点便是下一刻的起点
}
CView::OnMouseMove(nFlags, point);
}