MFC自绘框架窗口客户区

      利用MFC开发用户界面往往需要需要根据要求进行界面美化,界面的美化包括很多内容,比如说界面各功能模块空间布局,控件位置选择,各功能模块区域的字体、背景颜色选择、添加位图,标题栏、菜单栏、状态栏等的重绘等等。总的来说,界面美化包括客户区和非客户区,本文主要结合本人的第一个MFC软件界面开发项目的经验教训,简要介绍MFC单文档应用程序界面非客户区的重绘,主要包括标题栏和菜单栏。

       重绘标题栏和菜单栏可以从以下几方面考虑:1. 自行绘制的标题栏和菜单栏覆盖默认的标题栏和菜单栏;2.相应的控件(最大、最小、关闭)和菜单命令响应函数需要在新的标题栏和菜单栏得以实现; 3.自绘标题栏和菜单栏需要在界面上稳定显示,避免闪烁问题。后文主要从以下几方面介绍重绘标题栏和菜单栏功能的具体实现:

一、获取原有标题栏和菜单栏的大小

GetSystemMetrics()函数用于获取窗口的像素尺寸等参数信息,这里主要用到的参数包括SM_CXFRAME(X方向可变边框厚度)、SM_CYFRAME(Y方向可变边框厚度)、SM_CXBORDER(X方向不可变边框厚度)、SM_CYFRAME(Y方向不可变边框厚度)SM_CYCAPTION(窗口标题的像素高度)、SM_CXSIZE(标题栏按钮的X方向像素尺寸)、SM_CYSIZE(标题栏按钮的Y方向像素尺寸)、SM_CXICON(以像素计算的标题栏图标X方向尺寸)、SM_CYICON(以像素计算的标题栏图标Y方向尺寸)、SM_CXMENUSIZE(菜单栏按钮的X方向尺寸)、SM_CYMENUSIZE(菜单栏按钮的Y方向尺寸)、SM_CYMENU(以像素计算的菜单栏高度)。利用以上获取的参数信息,结合界面窗口尺寸(用GetWindowRect()函数获取,用ScreenToClient()函数调整)即可分别计算得到标题栏和菜单栏的矩形尺寸区域位置和大小。

二、双缓冲绘图,避免闪烁

 双缓冲绘图利用内存缓冲区解决多重绘制操作带来的闪烁问题。启用双缓冲绘图时,所有的绘制操作首先在内存缓冲区完成,而非界面上的绘图区域,所有绘图操作完成后,将内存缓冲区中完成的图像直接复制到界面上的相应绘图区域。由于在屏幕上只执行的速度极快的图形复制操作,因而解决了由复杂绘制操作造成的闪烁。

 1         CDC* pDisplayMemDC = new CDC; 
 2         CBitmap memBitmap; // 定义兼容位图
 3         int cxClient = rtWnd.Width();
 4         int cyClient = rtWnd.Height();
 5         if(!pDisplayMemDC->m_hDC)
 6         {
 7             pDisplayMemDC->CreateCompatibleDC(pDC);
 8             memBitmap.CreateCompatibleBitmap(pDC,cxClient,cyClient); // 定义并创建兼容位图对象
 9             pDisplayMemDC->SelectObject(&memBitmap); // 将兼容位图对象选入兼容设备描述表
10             pDisplayMemDC->BitBlt(0,0,cxClient,cyClient,pDC,0,0,SRCCOPY);
11             
12         }

以上代码之后通过定义需要的绘图工具对象,如字体、画刷、画笔等,即可在绘图画布上执行所需的各种绘图操作,完成后,即可将内存缓冲区图像复制到界面上相关联的绘图区域。最后,还要释放相关的GDI对象等,避免内存溢出。

                pDC->BitBlt(rtWnd.left,rtWnd.top,cxClient,cyClient,pDisplayMemDC,rtWnd.left,rtWnd.top,SRCCOPY); 
		memBitmap.DeleteObject();  
		font.DeleteObject();
		ReleaseDC(pDisplayMemDC);
		delete pDisplayMemDC;

  

三、定义Window窗口消息,确定重绘时机

LRESULT DefWindowProc(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam)是默认的窗口消息处理函数,它用来为应用程序没有处理的任何窗口消息提供缺省的处理,从而确保每一个消息都得到处理。

 

 1 // 自定义窗口消息处理函数
 2 LRESULT CMainFrame::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam) 
 3 {
 4     // TODO: Add your specialized code here and/or call the base class
 5     LRESULT lrst=CFrameWnd::DefWindowProc(message, wParam, lParam);  
 6     if(!::IsWindow(m_hWnd))  
 7         return lrst;  
 8     
 9     if(    message == WM_NCPAINT
10         || message == WM_NCACTIVATE 
11         || message == WM_INITMENU)  
12     {  
13         CDC* pWinDC = GetWindowDC();  
14         if(pWinDC) 
15         {
16             DrawTitleBar(pWinDC);  
17             DrawMenuBar(pWinDC);
18         }
19         ReleaseDC(pWinDC);  
20         return TRUE;
21     }
22     else
23         return lrst; 
24 }

 

示例代码中定义的消息只有三个,WM_NCPAINT(重绘框架窗口非客户区),WM_NCACTIVATE(非客户区得到或者失去焦点时的程序操作),WM_INITMENU(A WM_INITMENU message is sent only when a menu is first accessed; only one WM_INITMENU message is generated for each access).

 

四、菜单栏的绘制和菜单命令选择

 菜单栏的绘制包括主菜单的绘制和各菜单项下拉弹出菜单的绘制。窗口获取到WM_INITMENU消息时,重绘主菜单栏,当鼠标在菜单栏条目上单击按下鼠标左键时,窗口获取到菜单追踪消息WM_MENUSELECT。

void CMainFrame::OnMenuSelect(UINT nItemID, UINT nFlags, HMENU hSysMenu) 
{	
	// TODO: Add your message handler code here
	if (nFlags & MF_POPUP)
	{
		CDC* pWinDC = GetWindowDC();  
		if (pWinDC) 
		{
			if (nFlags != 0xFFFF)
				DrawMenuBar(pWinDC,CPoint(0,0),nItemID);
			else
				DrawMenuBar(pWinDC);
		}
		ReleaseDC(pWinDC); 
		TRACE("nItemID == %d\tn Flags == %d\r\n", nItemID, nFlags);
	}
	else
		CFrameWnd::OnMenuSelect(nItemID, nFlags, hSysMenu);
}

  

五、非客户区重绘的尺寸选择和调整

 WM_NCCALCSIZE消息在客户区尺寸和位置发生变化时发送,用于计算和调整非客户区尺寸的大小。

afx_msg void OnNcCalcSize(
   BOOL bCalcValidRects,
   NCCALCSIZE_PARAMS* lpncsp 
);

  参数含义:

bCalcValidRects

Specifies whether the application should specify which part of the client area contains valid information. Windows will copy the valid information to the specified area within the new client area. If this parameter is TRUE, the application should specify which part of the client area is valid.

lpncsp

Points to a NCCALCSIZE_PARAMS data structure that contains information an application can use to calculate the new size and position of theCWnd rectangle (including client area, borders, caption, scroll bars, and so on).

 NCCALCSIZE_PARAMS data structure如下所示:

typedef struct tagNCCALCSIZE_PARAMS {
   RECT rgrc[3];
   PWINDOWPOS lppos;
} NCCALCSIZE_PARAMS;

 相关参数含义:

rgrc

Specifies an array of rectangles. The first contains the new coordinates of a window that has been moved or resized. The second contains the coordinates of the window before it was moved or resized. The third contains the coordinates of the client area of a window before it was moved or resized. If the window is a child window, the coordinates are relative to the client area of the parent window. If the window is a top-level window, the coordinates are relative to the screen.

lppos

Points to a WINDOWPOS structure that contains the size and position values specified in the operation that caused the window to be moved or resized.

 

六、非客户区的焦点响应函数调整

 在DefWindowProc()函数中定义了对WM_NCACTIVATE消息的处理,如果对其默认消息响应函数OnNcActivate(BOOL bActive) 不加更改,尽管采取了双缓冲绘图,在执行菜单命令时,菜单栏区域总会闪烁一次,因此,对其作以下更改:

BOOL CMainFrame::OnNcActivate(BOOL bActive) 
{
	// TODO: Add your message handler code here and/or call default
	return TRUE;
    //return CFrameWnd::OnNcActivate(bActive);
}

 OnNcActivate()函数返回值始终为TRUE使得Windows必须进行缺省处理。

七、各种非客户区消息响应函数的处理

一些非客户区消息响应函数需要加以处理,以实现所需功能,比如OnNcMouseMove()、OnNcHitTest() 、OnNcLButtonDown() 、OnNcPaint()等。

void CMainFrame::OnNcMouseMove(UINT nHitTest, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default
	CRect rcWindow;
    GetWindowRect(&rcWindow);
    point.Offset(-rcWindow.left,-rcWindow.top);
	CDC* pDC = GetWindowDC();
	if(pDC)
	{
		if (nHitTest== HTCLOSE)
		{	
			CRect rtWnd,rtButtons;
			GetWindowRect(rtWnd);
			rtButtons = m_rtButtExit;
			rtButtons.OffsetRect(-rtWnd.TopLeft().x,-rtWnd.TopLeft().y);
			DrawExitButton(pDC,rtButtons,!m_rtButtExit.PtInRect(point));
		}
		else if (nHitTest!=HTCLIENT)
		{
			DrawTitleBar(pDC);
			DrawMenuBar(pDC,point);
		}
		else
		{
			CFrameWnd::OnNcMouseMove(nHitTest,point);
		}
	}
	ReleaseDC(pDC);
}

  

void CMainFrame::OnNcLButtonDown(UINT nHitTest, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default
	if (m_rtButtExit.PtInRect(point))
		SendMessage(WM_CLOSE);
	else if (nHitTest == HTMENU)
	{
		SendMessage(WM_SYSCOMMAND,SC_MOUSEMENU, MAKELPARAM(point.x, point.y));
	}
	else
		CFrameWnd::OnNcLButtonDown(nHitTest, point);
}

  

八、相关绘图命令的使用

 与绘图操作相关的工具包括设备描述表对象、画布、画刷、画笔、字体等,此外,绘图函数、绘图矩形区域描述、背景颜色、前景颜色等颜色设定、内存缓冲区图形复制函数PatBlt()、BitBlt()等函数的使用,绘图工具对象的选入、使用和销毁,一般绘图和双缓冲绘图的步骤、绘图区与坐标的选择与设定控制等。

常用的绘图类包括CPen,CBrush、CFont、CBitmap、CDC、CRect、CPoint、CString、COLORREF等

常用的绘图相关函数包括ScreenToClient()、ClientToScreen()、GetSystemMetrics()、CreateCompatibleDC()、CreateCompatibleBitmap()、SelectObject()、BitBlt()、PatBlt()、DrawIconEx()、SetBkMode()、SetTextColor()、CreatePointFont()、DrawText()、OffsetRect()、DeflateRect()、DeleteObject()、CreateSolidBrush()、CreatePen()、Rectangle()、Polyline()、GetWindowRect()、GetClientRect()、GetMenu()、GetMenuItemCount()、GetMenuString()、PtInRect()、FillRect()、TextOut()、Ellipse()、ReleaseDC()、InvalidateRect()、SetWindowText()、GetWindowText()等。

经验总结

 本人项目中非客户区重绘实现的功能相对比较简单,而且实现效果并不完美,只是初步实现所需功能。根据网上资料和个人实际编程体会,重绘非客户区主要注意以下几点:

(1)总结提炼重绘非客户区所需的功能,比如背景贴图、颜色设定、字体设定等;

(2)计算非客户区尺寸,如标题栏和菜单栏高度、按钮、图标位置等,获取相应的矩形区域;

(3)利用双缓冲绘图技术简单重绘标题栏和菜单栏、图标、按钮等;

(4)屏蔽原有的按钮或菜单区域消息或命令响应,让相应的消息或命令响应函数对应新的按钮或者菜单区域;

(5)对标题栏和菜单栏的字体和背景颜色、图标、甚至位图等加以适当修饰,以实现所需功能

(6)针对(1)中提炼的功能一一调试,看看程序是否满足功能要求,能够长时间运行,尤其检查GDI句柄资源的释放是否完整等。

 

     本人是MFC编程新手,整个项目的完成过程中各种搜索,整合了网络上的很多程序等资源,勉强实现了所要求的功能,但是对MFC的机制还不具备很深的认识,还需项目的历练。在此衷心感谢在网络上共享资源、编程心得的大牛们!

posted on 2016-11-03 10:13  xzcfightingup  阅读(3925)  评论(0编辑  收藏  举报

导航