2.duilib消息处理简单理解
基于之前对windows通信的大致了解和网友Alberl的教程。我修改了下Alberl在他教程中的HelloWorld程序,添加了两个按键。
使用duilib
时,一般在Notify
里面处理各个控件的消息,HandleMessage
里面一般是创建窗口。刚开始看到这个代码的时候,有几个疑问:
1.框架是怎么调用这两个接口的?
2.都是处理消息为啥搞两个接口,这两个有啥区别?
class CDuiFrameWnd : public CWindowWnd, private INotifyUI
{
public:
virtual LPCTSTR GetWindowClassName() const
{
return _T("DUIMainFrame4444");
}
virtual void Notify(TNotifyUI& msg)
{
if (msg.sType == _T("click"))
{
if (msg.pSender->GetName() == _T("btnHello1"))
{
::MessageBox(NULL, _T("我是按钮1"),_T("点击了按钮1"),NULL);
}
if (msg.pSender->GetName() == _T("btnHello2"))
{
::MessageBox(NULL, _T("我是按钮2"), _T("点击了按钮2"), NULL);
}
}
}
virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LRESULT lRes = 0;
if (uMsg == WM_CREATE)
{
m_PaintManager.Init(m_hWnd); // 关联ui管理器; m_hWnd --->CreateWindowEx时操作系统分配的窗口句柄;
CControlUI *pVLayout = new CVerticalLayoutUI;
pVLayout->SetName(_T("mainVertical"));
pVLayout->SetBkColor(0xFF00FF00);
m_PaintManager.AttachDialog(pVLayout); // CPaintManagerUI成员部分重新初始化操作
m_PaintManager.AddNotifier(this);
auto mainVertical = (DuiLib::CVerticalLayoutUI*)m_PaintManager.FindControl(_T("mainVertical"));
//
CControlUI *pButton1 = new CButtonUI;
pButton1->SetFloat();
SIZE leftTop = { 30, 80};
pButton1->SetFixedXY(leftTop);
pButton1->SetFixedWidth(70);
pButton1->SetFixedHeight(30);
pButton1->SetText(_T("btnHello1")); // 设置文字
pButton1->SetBkColor(0xFF00FFFF); // 设置背景色
pButton1->SetName(_T("btnHello1"));
mainVertical->Add(pButton1);
//
CControlUI *pButton2 = new CButtonUI;
pButton2->SetFloat();
SIZE leftTop1 = { 120, 80 };
pButton2->SetFixedXY(leftTop1);
pButton2->SetFixedWidth(70);
pButton2->SetFixedHeight(40);
pButton2->SetText(_T("btnHello2")); // 设置文字
pButton2->SetBkColor(0xFF00FFFF); // 设置背景色
pButton2->SetName(_T("btnHello2"));
mainVertical->Add(pButton2);
return lRes;
}
if (m_PaintManager.MessageHandler(uMsg, wParam, lParam, lRes))
{
return lRes;
}
return __super::HandleMessage(uMsg, wParam, lParam);
}
protected:
CPaintManagerUI m_PaintManager;
};
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
CPaintManagerUI::SetInstance(hInstance); // 应用程序分配的实例句柄;
CDuiFrameWnd duiFrame;
// 注册窗口类、创建窗口实例;
duiFrame.Create(NULL, _T("DUIWnd"), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE);
duiFrame.ShowModal(); // 消息循环; 接收、处理windows消息
return 0;
}
带着这些疑问,走读源码,梳理如下:
duiFrame.Create
会进行窗口类注册和窗口实例创建:
HWND CWindowWnd::Create(HWND hwndParent, LPCTSTR pstrName, DWORD dwStyle, DWORD dwExStyle, int x, int y, int cx, int cy, HMENU hMenu)
{
if( GetSuperClassName() != NULL && !RegisterSuperclass() ) return NULL;
if( GetSuperClassName() == NULL && !RegisterWindowClass() ) return NULL;
m_hWnd = ::CreateWindowEx(dwExStyle, GetWindowClassName(), pstrName, dwStyle, x, y, cx, cy, hwndParent, hMenu, CPaintManagerUI::GetInstance(), this);
ASSERT(m_hWnd!=NULL);
return m_hWnd;
}
注册窗口过程
的回调函数是CWindowWnd::__WndProc
,当windows给窗口发送消息时会调用到这个回调函数;
这里CreateWindowEx
的最后一个参数LPVOID lpParam
被赋值为this
即duiFrame
对象的地址,这个十分重要,后面会用到;
这是窗口过程
的代码:
LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
CWindowWnd* pThis = NULL;
if( uMsg == WM_NCCREATE ) {
// 很重要;
LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams); // pThis实际指向duiFrame
pThis->m_hWnd = hWnd;
::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(pThis));
}
else {
// pThis实际指向duiFrame
pThis = reinterpret_cast<CWindowWnd*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
if( uMsg == WM_NCDESTROY && pThis != NULL ) {
LRESULT lRes = ::CallWindowProc(pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam);
::SetWindowLongPtr(pThis->m_hWnd, GWLP_USERDATA, 0L);
if( pThis->m_bSubclassed ) pThis->Unsubclass();
pThis->m_hWnd = NULL;
pThis->OnFinalMessage(hWnd);
return lRes;
}
}
if( pThis != NULL ) {
return pThis->HandleMessage(uMsg, wParam, lParam);
}
else {
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
}
}
WM_NCCREATE消息在WM_CREATE消息之前发送,lParam
携带LPCREATESTRUCT
结构体地址,该结构体的成员lpCreateParams
为调用CreateWindowEx
时传入的最后一个参数,即duiFrame
对象的地址。拿到这个地址后,就能够调用CDuiFrameWnd
类的HandleMessage
方法。上述就是HandleMessage
方法的调用过程。
在这个方法里面,我们处理了WM_CREATE消息,我们创建了一个垂直布局和两个按键。m_PaintManager.AddNotifier
是添加控件等消息响应,这样消息就会传达到duilib
的消息循环,我们可以在Notify
函数里做消息处理。
m_PaintManager.AttachDialog(pVLayout)
做了很多工作,其中InitControls
用于设置控件的父节点和该控件所属的UI管理实例(即CPaintManagerUI
)。
mainVertical->Add(pButton1);
也是这个作用,这个很重要,Notify(TNotifyUI& msg)
的调用依赖于这步配置。
virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LRESULT lRes = 0;
if (uMsg == WM_CREATE)
{
m_PaintManager.Init(m_hWnd); // 关联ui管理器; m_hWnd --->CreateWindowEx时操作系统分配的窗口句柄;
CControlUI *pVLayout = new CVerticalLayoutUI;
pVLayout->SetName(_T("mainVertical"));
pVLayout->SetBkColor(0xFF00FF00);
m_PaintManager.AttachDialog(pVLayout); // CPaintManagerUI成员部分重新初始化操作
m_PaintManager.AddNotifier(this);
auto mainVertical = (DuiLib::CVerticalLayoutUI*)m_PaintManager.FindControl(_T("mainVertical"));
//
CControlUI *pButton1 = new CButtonUI;
pButton1->SetFloat();
SIZE leftTop = { 30, 80};
pButton1->SetFixedXY(leftTop);
pButton1->SetFixedWidth(70);
pButton1->SetFixedHeight(30);
pButton1->SetText(_T("btnHello1")); // 设置文字
pButton1->SetBkColor(0xFF00FFFF); // 设置背景色
pButton1->SetName(_T("btnHello1"));
mainVertical->Add(pButton1);
//
CControlUI *pButton2 = new CButtonUI;
pButton2->SetFloat();
SIZE leftTop1 = { 120, 80 };
pButton2->SetFixedXY(leftTop1);
pButton2->SetFixedWidth(70);
pButton2->SetFixedHeight(40);
pButton2->SetText(_T("btnHello2")); // 设置文字
pButton2->SetBkColor(0xFF00FFFF); // 设置背景色
pButton2->SetName(_T("btnHello2"));
mainVertical->Add(pButton2);
return lRes;
}
if (m_PaintManager.MessageHandler(uMsg, wParam, lParam, lRes))
{
return lRes;
}
return __super::HandleMessage(uMsg, wParam, lParam);
}
当点击按键时,windows系统先后发送WM_LBUTTONDOWN
和WM_LBUTTONUP
消息。消息被投递到窗口过程
CWindowWnd::__WndProc
,CDuiFrameWnd
类的HandleMessage
再次被调用,消息会投递到m_PaintManager.MessageHandler
进行处理。
在这里,duilib
会根据坐标的位置,得到控件实例。本例中即按钮控件对象的地址。按钮对象知道自己所属的UI和父控件(初始化时设置的)。这样就调用到Notify
了。
因此Notify
方法的调用,是先调用HandleMessage
而来,然后在该方法里面再调回到Notify
。
个人感觉,从使用来说HandleMessage
里面一般处理窗口、控件创建,Notify
中处理各个控件产生的事件。
参考文献:
- duilib简明教程
- duilib进阶教程
- duilib使用心得
- duilib各种布局的作用,相对布局与绝对布局的的意义与用法_Redrain的专栏-CSDN博客
- Duilib教程-自动布局2 - 夜雨無聲 - 博客园 (cnblogs.com)
- duilib开发(六):基本控件介绍_yp18792574062的博客-CSDN博客_duilib radio
- duilib开发(十):动态添加控件_yp18792574062的博客-CSDN博客
- DUILIB UI创建过程 - Guozht - 博客园 (cnblogs.com
- duilib库框架介绍_架构师之路-CSDN博客_duilib框架
- DuiLib源码分析_万千知了的博客