利用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;
CBitmap memBitmap; // 定义兼容位图
int cxClient = rtWnd.Width();
int cyClient = rtWnd.Height();
if(!pDisplayMemDC->m_hDC)
{
pDisplayMemDC->CreateCompatibleDC(pDC);
memBitmap.CreateCompatibleBitmap(pDC,cxClient,cyClient); // 定义并创建兼容位图对象
pDisplayMemDC->SelectObject(&memBitmap); // 将兼容位图对象选入兼容设备描述表
pDisplayMemDC->BitBlt(,,cxClient,cyClient,pDC,,,SRCCOPY); }

以上代码之后通过定义需要的绘图工具对象,如字体、画刷、画笔等,即可在绘图画布上执行所需的各种绘图操作,完成后,即可将内存缓冲区图像复制到界面上相关联的绘图区域。最后,还要释放相关的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)是默认的窗口消息处理函数,它用来为应用程序没有处理的任何窗口消息提供缺省的处理,从而确保每一个消息都得到处理。

 // 自定义窗口消息处理函数
LRESULT CMainFrame::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
// TODO: Add your specialized code here and/or call the base class
LRESULT lrst=CFrameWnd::DefWindowProc(message, wParam, lParam);
if(!::IsWindow(m_hWnd))
return lrst; if( message == WM_NCPAINT
|| message == WM_NCACTIVATE
|| message == WM_INITMENU)
{
CDC* pWinDC = GetWindowDC();
if(pWinDC)
{
DrawTitleBar(pWinDC);
DrawMenuBar(pWinDC);
}
ReleaseDC(pWinDC);
return TRUE;
}
else
return lrst;
}

示例代码中定义的消息只有三个,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的机制还不具备很深的认识,还需项目的历练。在此衷心感谢在网络上共享资源、编程心得的大牛们!

MFC自绘框架窗口客户区的更多相关文章

  1. 在MFC中添加OpenGL窗口

    虽然MFC已经落伍好多年,而且用来做界面非常的不好用...但是我既不会C#也不会QT,又需要使用OpenGL,就只能将就用了...   一.首先介绍Windows图像程序设计中几个重要的概念:   G ...

  2. MFC中无标题栏窗口的移动

    原文链接: http://blog.sina.com.cn/s/blog_6288219501015dwa.html   移动标准窗口是通过用鼠标单击窗口标题条来实现的,但对于没有标题条的窗口,就需要 ...

  3. MFC中无边框窗口的拖动

      void CXXXXDialog::OnLButtonDown(UINT nFlags, CPoint point) { PostMessage(WM_NCLBUTTONDOWN, HTCAPTI ...

  4. MFC自绘控件学习总结

    前言:从这学期开始就一直在学习自绘控件(mfc),目标是做出一款播放器界面,主要是为了打好基础,因为我基础实在是很烂....说说我自己心得体会以及自绘控件的方法吧,算是吐槽吧,说的不对和不全的地方,或 ...

  5. MFC基础,MFC自绘控件学习总结.---转

    前言:从这学期开始就一直在学习自绘控件(mfc),目标是做出一款播放器界面,主要是为了打好基础,因为我基础实在是很烂....说说我自己心得体会以及自绘控件的方法吧,算是吐槽吧,说的不对和不全的地方,或 ...

  6. VS2010/MFC编程入门之四(MFC应用程序框架分析)

    VS2010/MFC编程入门之四(MFC应用程序框架分析)-软件开发-鸡啄米 http://www.jizhuomi.com/software/145.html   上一讲鸡啄米讲的是VS2010应用 ...

  7. VS2010-MFC(MFC应用程序框架分析)

    转自:http://www.jizhuomi.com/software/145.html 一.SDK应用程序与MFC应用程序运行过程的对比 程序运行都要有入口函数,在之前的C++教程中都是main函数 ...

  8. MFC自绘菜单

    自绘控件问题多多.本文以菜单为例. ①当要使用顶层菜单资源.对话框资源.状态栏资源等这3种资源的任何一种.那么CWinApp::InitInstance函数内部必须使用LoadFrame函数来加载资源 ...

  9. WTL之手动编写框架窗口

    新版博客已经搭建好了,有问题请访问 htt://www.crazydebug.com 本人是一个实践主义者,不罗嗦上一篇工程搭建好以后,这一篇就开始写代码,写之前再说几句,如果你熟悉MFC分析过MFC ...

随机推荐

  1. 转:java中数组与List相互转换的方法

    1.List转换成为数组.(这里的List是实体是ArrayList) 调用ArrayList的toArray方法. toArray public <T> T[] toArray(T[] ...

  2. mui ajax方法

    mui ajax方法详解: mui提供了mui.ajax,在此基础上有分装出mui.get()/mui.getJSON()/mui.post()三个方法. mui.ajax( url [,settin ...

  3. ZK框架笔记5、事件

            事件是org.zkoss.zk.ui.event.Event类,它通知应用程序发生了什么事情.每一种类型的事件都由一个特定的类来表示.         要响应一个事件,应用程序必须为事 ...

  4. openerp js调用Python类方法

    转自:http://blog.csdn.net/kuaileboy1989/article/details/42875497 js调用.py文件中定义的类 形式如下: //创建product.prod ...

  5. Linux-Nginx-关闭进程

    当然就仅仅是介绍一条命令了,就这么简单. nginx默认创建一个工作进程 root 2713 1 0 07:56 ? 00:00:00 nginx: master process ../sbin/ng ...

  6. Sphinx-PHP使用Sphinx搜索技术

    Sphinx继承到PHP程序中, 有两种方式: Sphinx PHP模块: 编译生成PHP扩展模块 Sphinx API类: 直接使用Sphinx提供的类即可 首先我们应该使用Sphinx做以下几件事 ...

  7. 阿里云web播放器

    原文地址:https://help.aliyun.com/document_detail/51991.html?spm=5176.doc61109.6.703.ZTCYoi 一.概念说明 1. pla ...

  8. java基础讲解09-----接口,继承,多态

    还有什么包装类,数字类,这些简单的我就不想过去介绍,前面也大概的介绍了下,继承,多态 1.类的继承 继承的思想:基于某个父类的扩展,制定一个新的子类.子类可以继承父类原有的属性,方法,也可以重写父类的 ...

  9. oracle 某一字段取反

    --某一位取反select id ,flag,(flag + 1) - BITAND(flag, 1) * 2 from SYS_INFO t UPDATE SYS__INFO SET FLAG=(( ...

  10. 参数数组(params)的用法

    使用参数数组的注意事项: 1. 只能在一维数组上使用params关键字. 2. 不能重载一个只基于params关键字的方法.params关键字不构成方法的签名的一部分. 如: //编译时错误:重复访问 ...