通过设计图有了一个初步认识后,接下来开始进一步深入学习了解,主要从以下几个方面进行了解学习:
库的组成;框架基本流程;元素创建机制;消息处理机制。
由于duilib没有对外部的任何库进行依赖,所以在其内部实现了很多用于支撑项目的基础类(如下图所示)。这些类分布在Util文件夹中:
上面这些类看名字就基本能够理解其具体的含义了,当然除了基本的基础库,还有一些和窗口使用相关的工具的封装,如窗口工具:WindowImplBase,这个工具我们在这里不详述,后面使用中会经常用到。
控件库在duilib的实现中被分为了两块:Core和Control:
这当中尤其要注意控件基类CControlUI和容器基类CContainerUI,这是duilib核心类(如下图所示)中是很重要的两部分:
CControlUI在整个控件体系中非常重要,它是所有控件的基类,也是组成控件树的基本元素,控件树中所有的节点都是一个CControlUI。
它基本包括了所有控件公共的属性,如:位置,大小,颜色,是否有焦点,是否被启用等等。当然这个类中还提供了非常多的基础函数,用于重载来实现子控件,如获取控件名称和ClassName,是否显示等等。
另外为了方便从XML中直接解析出控件的各个属性,这个类中还在提供了一个SetAttribute的方法,传入字符串的属性名称和值对特定的属性进行设置,内部其实就是挨个比较字符串去完成的,所以平时使用的时候就还是不要使用的比较好了,因为每个属性实际上都有特定的方法来获取和设置。
另外每个控件中还有几个事件管理的对象——CEventSource,这些对象会在特定的时机被触发,如OnInit,调用其中保存的各个回调函数。
有了基本的控件基类之后,我们就需要容器来将他管理起来,这个容器就是CContainerUI,其内部用一个数组来保存所有的CControlUI的对象,后续的所有工作,就都是基于这个对象来进行的了。
这样在CContainerUI里面,主要实现了一下几个功能:
而对于这些控件的绘制实现以及相关使用,在后续具体进一步学习中再深入详解。
框架的基本流程实际上类似Win32创建窗口流程,如果对于Win32比较熟悉,这部分可以很快掌握。
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
CPaintManagerUI::SetInstance(hInstance); // 第一步: 实例句柄与渲染类关联
CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath() + _T("skin"));
HRESULT Hr = ::CoInitialize(NULL); // 第二步:初始化COM库, 为加载COM库提供支持
if( FAILED(Hr) )
return 0;
CMainFrameWnd* pFrame = new CMainFrameWnd(); // 第三步:创建窗口类
if( pFrame == NULL )
return 0;
pFrame->Create(NULL, _T("主程序"), UI_WNDSTYLE_FRAME, 0L, 0, 0, 800, 600); // 第四步:注册窗口类与创建窗口
// 实际上这里调用Create操作和Win32创建窗体一样,内部实际上做了以下操作:
// -> RegisterSuperclass (注册一个超类 即已有一个窗口类的基上再注册一个窗口类)
// -> RegisterWindowClass (注册窗口类)
// -> ::CreateWindowEx (创建窗口,此时触发 WM_CREATE 消息)
// -> HandleMessage ( WM_CREATE消息处理OnCreate)
pFrame->CenterWindow(); // 第五步:窗口居中显示
::ShowWindow(*pFrame, SW_SHOW);
CPaintManagerUI::MessageLoop(); // 第六步:处理消息循环
::CoUninitialize(); // 第七部:退出程序并释放COM库
return 0;
}
第一步:注册消息处理函数
在CWindowWnd注册窗口(RegisterWindowClass())里,注册消息回调函数(__WndProc);
第二步:消息分发
消息回调函数(处理所有系统发送的消息),然后回调函数通过子类的CMainFrameWnd::HandleMessage对消息进行分发。
非窗口消息通过CMainFrameWnd::HandleMessage调用CPaintManagerUI::MessageHandler进行分发。
第三步:消息循环
在CPaintManagerUI类的MessageLoop处理消息循环;
接收到消息以后,进入消息回调函数(__WndProc);
(注:以下内容以鼠标单机Button事件为例)。
第四步:处理控件消息
鼠标按下时(WM_LBUTTONDOWN),查找鼠标点击的控件。
处理控件的鼠标按下消息:通过调用基类CControlUI:: DoEvent,引起子类如CButtonUI::DoEvent事件。
子类的DoEvent对不同类型的事件进行处理。通过CPaintManagerUI:: SendNotify回调控件注册的事件。
消息的处理在duilib继承自public CWindowWnd, public INotifyUI 的类中**void Notify(TNotifyUI& msg)**中,如下:
void Notify(TNotifyUI& msg)
{
if( msg.sType == _T("windowinit") ) //此处为消息类型宏定义或者消息类型字符串
{
//要做的事情
}
}
void CContainerDlg::Notify(TNotifyUI& msg)
{
//
if (msg.sType == DUI_MSGTYPE_CLICK)
{
if (msg.pSender == m_pCloseBtn)
{
CefModule::CefManager::GetInstance()->PostQuitMessage(0);
m_pCefControl->CloseAllBrowser();
m_pSDOLogin->CloseLoginDialog();
this->Close();
}
else if (msg.pSender == m_pSettingBtn)
{
wstring strExe = GAME_DIR L"wooolcfg.exe";
PROCESS_INFORMATION pi = { 0 };
STARTUPINFO si = { 0 };
si.cb = sizeof(si);
BOOL ret = CreateProcess(nullptr, (LPWSTR)strExe.c_str(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi);
if (ret)
{
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
}
else if (msg.pSender == m_pHideLoginDialogBtn)
{
//MessageBox(NULL, L"1", L"1", MB_OK);
m_pSDOLogin->SetLoginDialogState(2);
//MessageBox(NULL, L"2", L"2", MB_OK);
}
else if (msg.pSender == m_pShowLoginDialogBtn)
{
//MessageBox(NULL, L"3", L"3", MB_OK);
m_pSDOLogin->SetLoginDialogState(1);
//MessageBox(NULL, L"4", L"4", MB_OK);
}
}
else if (msg.sType == DUI_MSGTYPE_WINDOWINIT)
{
if (m_pSDOLogin != nullptr && m_pLoginFrameArea != nullptr)
{
m_pSDOLogin->ShowLoginDialog(LoginCallback, 0, 0);
MoveLoginFrame();
}
}
if (m_pAreaTypeSwitch != nullptr)
{
m_pAreaTypeSwitch->Notify(msg);
}
}
自定义消息:
自定义消息是应用程序开发者自己定义的消息类型,用于在不同的控件之间进行通信和传递数据。这些消息通常不是系统定义的消息,而是开发者根据自己的需求添加的。自定义消息的用途包括但不限于在控件之间传递数据、触发特定的操作等。当控件接收到自定义消息时,它会调用对应的处理函数来处理这些消息。
事件消息:
事件消息是在DuiLib中预定义的消息类型,用于响应用户的交互操作,如鼠标点击、键盘输入等。当用户在控件上进行操作时,DuiLib会生成相应的事件消息,并传递给控件的父控件,直到找到一个处理该事件的控件为止。通常,控件会通过处理事件消息来执行相应的操作,例如打开一个菜单、处理用户输入等。
HandleMessage和Notify:
在DuiLib中,HandleMessage和Notify是两个用于处理消息的函数。
HandleMessage:
HandleMessage是用于处理自定义消息和一些特定的系统消息的函数。每个控件类都有一个HandleMessage函数,你可以在这个函数中实现对自定义消息的处理逻辑。当控件接收到一个消息时,DuiLib会调用控件的HandleMessage函数,然后你可以根据消息类型进行相应的处理。
Notify:
Notify是用于处理事件消息的函数。当控件接收到一个事件消息时,DuiLib会调用控件的Notify函数,然后你可以在这个函数中根据事件类型执行相应的操作。例如,当用户点击了一个按钮控件时,DuiLib会生成一个点击事件消息,然后调用按钮控件的Notify函数,你可以在这个函数中执行按钮的点击响应逻辑。
在处理自定义消息和事件消息时,你可以通过wParam和lParam参数来传递额外的数据。在DuiLib中,还有一些其他的消息处理函数,比如OnEvent和OnNotify等,用于处理特定类型的事件和通知消息。这些函数都是为了方便你在控件中处理消息而提供的。
总结:自定义消息用于应用程序内部的通信和数据传递,而事件消息用于响应用户的交互操作。在处理自定义消息时,使用控件的HandleMessage函数,在处理事件消息时,使用控件的Notify函数,同时可以结合其他特定的消息处理函数来实现控件的功能。