自绘之----对话框
本实例使用MFC工程,WTL与此类似。开发环境VS2010
首先是改变对话框的背景色
可以响应WM_ERASEBKGND消息,每当窗口响应WM_PAINT消息之前会先处理这个消息来擦除窗口背景。
BOOL COwnerDrawDlg::OnEraseBkgnd(CDC* pDC) { //return CDialogEx::OnEraseBkgnd(pDC); CRect rcWnd; GetClientRect(rcWnd); pDC->FillSolidRect(rcWnd,RGB(255,0,0)); return TRUE; }
这里两点需要说明:
1)MSDN上关于WM_ERASEBKGND介绍说如果我们已经自己处理了背景,应该返回非0值,否则返回0。但是MFC封装之后框架中返回值对应该没有任何影响(这块应该涉及到消息流程,暂时留疑)。
2)这个函数中我自己把背景填充成红色,如果仅仅返回一个值而不做任何处理,会发现窗口刚显示出来是白色背景,这个现象很奇怪,按道理说我们没有绘制也阻止的系统的默认绘制行为,应该显示为黑色背景的啊,如果有知道的道友请留言说明,不胜感激。但是拖动窗口边框改变窗口尺寸的话会发现需要绘制部分的背景是黑色的。
去掉对话框的边框
可以想到窗口样式中去掉BORDER和CAPTION
ModifyStyle(WS_BORDER|WS_CAPTION,0);
但是发现结果成这样了:
左边是初始显示,右边是拖动窗口大小后的显示。
这说明仅删除窗口边框样式没有彻底去掉边框,边框属于非客户区,所以我们需要重载WN_NC一类的函数。由于这类函数有多个,而且处理很简单,就是阻止默认的行为。每个消息都生成一个处理函数的话会使代码编的臃肿,可以直接重载WindowProc
LRESULT COwnerDrawDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { if(WM_NCCALCSIZE == message || WM_NCPAINT == message || WM_NCACTIVATE==message){ return TRUE; } return CDialogEx::WindowProc(message, wParam, lParam); }
看起来不错,现在就需要添加拖动窗口和修改尺寸问题了。
响应NCHITTEST
LRESULT COwnerDrawDlg::OnNcHitTest(CPoint point) { LRESULT lr = 0; const int CX_BORDER = 6; const int CY_BORDER = 6; const int CAPTION_HEIGHT = 35; POINT pt = point; CRect rcWnd; GetWindowRect(rcWnd); if(pt.y<rcWnd.top+CY_BORDER){ if(pt.x<rcWnd.left+CX_BORDER){ lr = HTTOPLEFT; } else if(pt.x>rcWnd.right-CX_BORDER){ lr = HTTOPRIGHT; } else{ lr = HTTOP; } } else if(pt.y>rcWnd.bottom-CY_BORDER){ if(pt.x<rcWnd.left+CX_BORDER){ lr = HTBOTTOMLEFT; } else if(pt.x>rcWnd.right-CX_BORDER){ lr = HTBOTTOMRIGHT; } else{ lr = HTBOTTOM; } } else if(pt.x<rcWnd.left+CX_BORDER){ lr = HTLEFT; } else if(pt.x>rcWnd.right-CX_BORDER){ lr = HTRIGHT; } else if(pt.y<rcWnd.top+CAPTION_HEIGHT){ lr = HTCAPTION; } else{ lr = HTCLIENT; } return lr; }
定义窗口大小
我们不希望自己的窗口界面变得这么小,最好自己定义窗口最小(或最大)尺寸
我们只需要处理WM_GETMINMAXINFO消息。
void COwnerDrawDlg::OnGetMinMaxInfo(MINMAXINFO* lpMMI) { lpMMI->ptMinTrackSize.x = 400; lpMMI->ptMinTrackSize.y = 300; CDialogEx::OnGetMinMaxInfo(lpMMI); }
还可以在这里定义窗口的最大尺寸,而且这里有个技巧。
第一想法是GetSystemMetrics获取屏幕大小,如果这样设置窗口最大尺寸在双屏幕下无法适应,而且最大化的时候有可能会偏移。
最好的方法是获取当前屏幕的工作区域(参考DUILIB的实现)
void COwnerDrawDlg::OnGetMinMaxInfo(MINMAXINFO* lpMMI) { lpMMI->ptMinTrackSize.x = 400; lpMMI->ptMinTrackSize.y = 300; MONITORINFO mi = {sizeof(mi)}; GetMonitorInfo(::MonitorFromWindow(m_hWnd,MONITOR_DEFAULTTONEAREST),&mi); CRect rcWnd = mi.rcWork; rcWnd.OffsetRect(-mi.rcMonitor.left,-mi.rcMonitor.top); lpMMI->ptMaxPosition.x = rcWnd.left; lpMMI->ptMaxPosition.y = rcWnd.top; lpMMI->ptMaxTrackSize.x = rcWnd.Width(); lpMMI->ptMaxTrackSize.y = rcWnd.Height(); CDialogEx::OnGetMinMaxInfo(lpMMI); }
对话框现在已经被我们改装的完全没有对话框模样了,但是要成为一个纯粹的窗口还有一点怪异,对话框接受ESC键响应OnCancel,接受Enter键响应OnOk,需要把这两个按键消息屏蔽掉。
屏蔽ESC和Enter键
BOOL COwnerDrawDlg::PreTranslateMessage(MSG* pMsg) { if(pMsg->message == WM_KEYDOWN && (pMsg->wParam==VK_ESCAPE||pMsg->wParam==VK_RETURN)){ return TRUE; } return CDialogEx::PreTranslateMessage(pMsg); }