上节笔者向大家介绍了Duilib的界面布局并在最后编写了一个仿QQ旋风的界面,但是由于我们屏蔽了系统的标题栏,读者可能已经发现,我们的窗口没办法移动,同样也不能通过拖动来改变窗口的大小。
这就需要我们对WM_NCHITTEST消息进行处理,该消息的LPARAM参数存放鼠标的x坐标和y坐标,在程序中需要对x/y坐标位置进行判断,当坐标落在下图红色线框位置时,我们向窗口过程函数返回HTCAPTION,这样操作系统就会把红色线框区域当成系统的标题栏。
我们可以使用类似的方法告诉Windows操作系统哪里是窗口的右下角,哪里是窗口的边框等等,关于这个消息的更多介绍读者可以参考MSDN。对该消息进行处理算是比较麻烦的,而且对于初学者来说不太好理解,幸运的是Duilib官方为我们封装好了一个WindowImplBase类,该类已经对一些Window消息进行了处理,我们只需要继承这个类就可以了。
笔者对该类稍微进行了修改,类的声明写在win_impl_base.hpp文件中:
#ifndef WIN_IMPL_BASE_HPP#define WIN_IMPL_BASE_HPP#include "../DuiLib/StdAfx.h" #include#include typedef std::basic_string tString;using namespace DuiLib;namespace DuiLib { class CWindowWnd; class INotifyUI; class IMessageFilterUI; class IDialogBuilderCallback;}class WindowImplBase : public CWindowWnd, public INotifyUI, public IMessageFilterUI, public IDialogBuilderCallback{public: WindowImplBase(); virtual ~WindowImplBase(); virtual void OnFinalMessage(HWND hWnd); virtual UINT GetClassStyle() const; virtual void Init(); virtual CControlUI* CreateControl(LPCTSTR pstrClass); virtual LRESULT MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, bool& bHandled); virtual LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); virtual LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);#if defined(WIN32) && !defined(UNDER_CE) virtual LRESULT OnNcActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); virtual LRESULT OnNcCalcSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); virtual LRESULT OnNcPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); virtual LRESULT OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); virtual LRESULT OnGetMinMaxInfo(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); virtual LRESULT OnMouseWheel(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);#endif virtual LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); virtual LRESULT OnSysCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); virtual LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); virtual LRESULT OnKeyDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); virtual LRESULT OnKillFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); virtual LRESULT OnSetFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); virtual LRESULT OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); virtual LRESULT OnLButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); virtual LRESULT OnMouseMove(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam); virtual LRESULT HandleCustomMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);protected: virtual tString GetSkinFolder(); virtual tString GetSkinFile() = 0; virtual LRESULT ResponseDefaultKeyEvent(WPARAM wParam);protected: CPaintManagerUI paint_manager_;};#endif // WIN_IMPL_BASE_HPP
下面是一些成员函数的实现部分,写在win_impl_base.cpp文件中:
#include "win_impl_base.hpp"WindowImplBase::WindowImplBase(){}WindowImplBase::~WindowImplBase(){}UINT WindowImplBase::GetClassStyle() const{ return CS_DBLCLKS;}CControlUI* WindowImplBase::CreateControl(LPCTSTR pstrClass){ return NULL;}void WindowImplBase::OnFinalMessage(HWND /*hWnd*/){ paint_manager_.RemovePreMessageFilter(this); paint_manager_.RemoveNotifier(this); paint_manager_.ReapObjects(paint_manager_.GetRoot());}void WindowImplBase::Init(){}LRESULT WindowImplBase::OnClose(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled){ bHandled = FALSE; return 0;}LRESULT WindowImplBase::OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled){ bHandled = FALSE; return 0;}#if defined(WIN32) && !defined(UNDER_CE)LRESULT WindowImplBase::OnNcActivate(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled){ if( ::IsIconic(*this) ) bHandled = FALSE; return (wParam == 0) ? TRUE : FALSE;}LRESULT WindowImplBase::OnNcCalcSize(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/){ return 0;}LRESULT WindowImplBase::OnNcPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/){ return 0;}LRESULT WindowImplBase::OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled){ POINT pt; pt.x = GET_X_LPARAM(lParam); pt.y = GET_Y_LPARAM(lParam); ::ScreenToClient(*this, &pt); RECT rcClient; ::GetClientRect(*this, &rcClient); if( !::IsZoomed(*this) ) { RECT rcSizeBox = paint_manager_.GetSizeBox(); if( pt.y < rcClient.top + rcSizeBox.top ) { if( pt.x < rcClient.left + rcSizeBox.left ) return HTTOPLEFT; if( pt.x > rcClient.right - rcSizeBox.right ) return HTTOPRIGHT; return HTTOP; } else if( pt.y > rcClient.bottom - rcSizeBox.bottom ) { if( pt.x < rcClient.left + rcSizeBox.left ) return HTBOTTOMLEFT; if( pt.x > rcClient.right - rcSizeBox.right ) return HTBOTTOMRIGHT; return HTBOTTOM; } if( pt.x < rcClient.left + rcSizeBox.left ) return HTLEFT; if( pt.x > rcClient.right - rcSizeBox.right ) return HTRIGHT; } RECT rcCaption = paint_manager_.GetCaptionRect(); if( pt.x >= rcClient.left + rcCaption.left && pt.x < rcClient.right - rcCaption.right \ && pt.y >= rcCaption.top && pt.y < rcCaption.bottom ) { CControlUI* pControl = static_cast(paint_manager_.FindControl(pt)); if( pControl && _tcsicmp(pControl->GetClass(), _T("ButtonUI")) != 0 && _tcsicmp(pControl->GetClass(), _T("OptionUI")) != 0 /*&& _tcsicmp(pControl->GetClass(), _T("TextUI")) != 0 */) return HTCAPTION; } return HTCLIENT;}LRESULT WindowImplBase::OnGetMinMaxInfo(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled){ MONITORINFO oMonitor = {}; oMonitor.cbSize = sizeof(oMonitor); ::GetMonitorInfo(::MonitorFromWindow(*this, MONITOR_DEFAULTTOPRIMARY), &oMonitor); CRect rcWork = oMonitor.rcWork; rcWork.Offset(-rcWork.left, -rcWork.top); LPMINMAXINFO lpMMI = (LPMINMAXINFO) lParam; lpMMI->ptMaxPosition.x = rcWork.left; lpMMI->ptMaxPosition.y = rcWork.top; lpMMI->ptMaxSize.x = rcWork.right; lpMMI->ptMaxSize.y = rcWork.bottom; bHandled = FALSE; return 0;}LRESULT WindowImplBase::OnMouseWheel(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled){ bHandled = FALSE; return 0;}#endifLRESULT WindowImplBase::OnSetFocus(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled){ bHandled = FALSE; return 0;}LRESULT WindowImplBase::OnLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled){ bHandled = FALSE; return 0;}LRESULT WindowImplBase::OnMouseMove(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled){ bHandled = FALSE; return 0;}LRESULT WindowImplBase::OnLButtonUp(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled){ bHandled = FALSE; return 0;}LRESULT WindowImplBase::OnKeyDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled){ bHandled = FALSE; return 0;}LRESULT WindowImplBase::OnKillFocus(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled){ bHandled = FALSE; return 0;}LRESULT WindowImplBase::OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled){ SIZE szRoundCorner = paint_manager_.GetRoundCorner();#if defined(WIN32) && !defined(UNDER_CE) if( !::IsIconic(*this) && (szRoundCorner.cx != 0 || szRoundCorner.cy != 0) ) { CRect rcWnd; ::GetWindowRect(*this, &rcWnd); rcWnd.Offset(-rcWnd.left, -rcWnd.top); rcWnd.right++; rcWnd.bottom++; HRGN hRgn = ::CreateRoundRectRgn(rcWnd.left, rcWnd.top, rcWnd.right, rcWnd.bottom, szRoundCorner.cx, szRoundCorner.cy); ::SetWindowRgn(*this, hRgn, TRUE); ::DeleteObject(hRgn); }#endif bHandled = FALSE; return 0;}LRESULT WindowImplBase::OnSysCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled){ if (wParam == SC_CLOSE){ bHandled = TRUE; return 0; }#if defined(WIN32) && !defined(UNDER_CE) BOOL bZoomed = ::IsZoomed(*this); LRESULT lRes = CWindowWnd::HandleMessage(uMsg, wParam, lParam); if( ::IsZoomed(*this) != bZoomed ){ }#else LRESULT lRes = CWindowWnd::HandleMessage(uMsg, wParam, lParam);#endif return lRes;}tString WindowImplBase::GetSkinFolder(){ return tString(CPaintManagerUI::GetInstancePath()); //+ _T("skin\\");}LRESULT WindowImplBase::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled){ LONG styleValue = ::GetWindowLong(*this, GWL_STYLE); styleValue &= ~WS_CAPTION; ::SetWindowLong(*this, GWL_STYLE, styleValue | WS_CLIPSIBLINGS | WS_CLIPCHILDREN); RECT rcClient; ::GetClientRect(*this, &rcClient); ::SetWindowPos(*this, NULL, rcClient.left, rcClient.top, rcClient.right - rcClient.left, \ rcClient.bottom - rcClient.top, SWP_FRAMECHANGED); paint_manager_.Init(m_hWnd); paint_manager_.AddPreMessageFilter(this); CDialogBuilder builder; //paint_manager_.SetResourcePath(GetSkinFolder().c_str()); //tString tstrSkin = paint_manager_.GetResourcePath(); tString tstrSkin = GetSkinFile(); CControlUI* pRoot = builder.Create(tstrSkin.c_str(), (UINT)0, this, &paint_manager_); paint_manager_.AttachDialog(pRoot); paint_manager_.AddNotifier(this); Init(); return 0;}LRESULT WindowImplBase::HandleCustomMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled){ bHandled = FALSE; return 0;}LRESULT WindowImplBase::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam){ LRESULT lRes = 0; BOOL bHandled = TRUE; switch (uMsg) { case WM_CREATE: lRes = OnCreate(uMsg, wParam, lParam, bHandled); break; case WM_CLOSE: lRes = OnClose(uMsg, wParam, lParam, bHandled); break; case WM_DESTROY: lRes = OnDestroy(uMsg, wParam, lParam, bHandled); break;#if defined(WIN32) && !defined(UNDER_CE) case WM_NCACTIVATE: lRes = OnNcActivate(uMsg, wParam, lParam, bHandled); break; case WM_NCCALCSIZE: lRes = OnNcCalcSize(uMsg, wParam, lParam, bHandled); break; case WM_NCPAINT: lRes = OnNcPaint(uMsg, wParam, lParam, bHandled); break; case WM_NCHITTEST: lRes = OnNcHitTest(uMsg, wParam, lParam, bHandled); break; case WM_GETMINMAXINFO: lRes = OnGetMinMaxInfo(uMsg, wParam, lParam, bHandled); break; case WM_MOUSEWHEEL: lRes = OnMouseWheel(uMsg, wParam, lParam, bHandled); break;#endif case WM_SIZE: lRes = OnSize(uMsg, wParam, lParam, bHandled); break; case WM_SYSCOMMAND: lRes = OnSysCommand(uMsg, wParam, lParam, bHandled); break; case WM_KEYDOWN: lRes = OnKeyDown(uMsg, wParam, lParam, bHandled); break; case WM_KILLFOCUS: lRes = OnKillFocus(uMsg, wParam, lParam, bHandled); break; case WM_SETFOCUS: lRes = OnSetFocus(uMsg, wParam, lParam, bHandled); break; case WM_LBUTTONUP: lRes = OnLButtonUp(uMsg, wParam, lParam, bHandled); break; case WM_LBUTTONDOWN: lRes = OnLButtonDown(uMsg, wParam, lParam, bHandled); break; case WM_MOUSEMOVE: lRes = OnMouseMove(uMsg, wParam, lParam, bHandled); break; default: bHandled = FALSE; break; } if (bHandled) return lRes; lRes = HandleCustomMessage(uMsg, wParam, lParam, bHandled); if (bHandled) return lRes; if (paint_manager_.MessageHandler(uMsg, wParam, lParam, lRes)) return lRes; return CWindowWnd::HandleMessage(uMsg, wParam, lParam);}LRESULT WindowImplBase::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM /*lParam*/, bool& /*bHandled*/){ if (uMsg == WM_KEYDOWN) { switch (wParam) { case VK_RETURN: case VK_ESCAPE: return ResponseDefaultKeyEvent(wParam); default: break; } } return FALSE;}LRESULT WindowImplBase::ResponseDefaultKeyEvent(WPARAM wParam){ if (wParam == VK_RETURN) { return FALSE; } else if (wParam == VK_ESCAPE) { Close(); return TRUE; } return FALSE;}
我们需要將这两个文件添加到自己的工程中,项目结构如下图所示:
在我们的程序中不再继承CWindowWnd和INotifyUI这两个类,而是直接继承WindowImplBase,重写父类的GetWindowClassName、GetClassStyle、GetSkinFile、Notify四个函数即可:
#include "win_impl_base.hpp"#includeclass MyWnd : public WindowImplBase{ LPCTSTR GetWindowClassName() const { return L"MyWnd"; } UINT GetClassStyle() const{ return UI_CLASSSTYLE_FRAME|CS_DBLCLKS; } tString GetSkinFile(){ return L"tutorial5.xml"; } void Notify(TNotifyUI& msg) { if(msg.sType == L"click") { if(msg.pSender->GetName() == L"CloseBtn") { ::PostQuitMessage(0); }else if(msg.pSender->GetName() == L"MinBtn") { ::SendMessage(m_hWnd,WM_SYSCOMMAND, SC_MINIMIZE, 0); } } } };INT WinMain(HINSTANCE hInst,HINSTANCE hPreInst,LPSTR lpCmdLine,INT Show){ CPaintManagerUI::SetInstance(hInst); CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetResourcePath()); //创建主窗口 MyWnd* pFrame = new MyWnd(); pFrame->Create(NULL,L"Tutorial5",UI_WNDSTYLE_FRAME^WS_THICKFRAME,WS_EX_WINDOWEDGE); pFrame->CenterWindow(); pFrame->ShowWindow(true); CPaintManagerUI::MessageLoop(); return 0;}
我们只需要在GetSkinFile方法中返回一个界面布局文件名称,比上节简单多了吧,编译运行看到上节的界面能够成功加载而且已经可以拖动了。
博文源码: