背景
最近由于各种原因又学习了duilib c/c++界面库
总结
- CWindowWnd::__WndPro
- pThis->HandleMessage
- m_PaintManager.MessageHandler(uMsg, wParam, lParam, lRes)[这个是最重要的]
- pClick->Event(event); 走到control 处理时间里面
- control 调用OnEvent(一般不做处理,也可以在这里处理事件),然后在 DoEvent 满足条件参数Notify投递
- 通知监听通知,一般是传递是窗口,这里一般是 WindowImplBase,因为我们注册了INotifyUI,默认处理通知,这个时候我们只要在消息宏设置函数就可以了。这个是WindowImplBase 带来便利,CWindowWnd,自己处理Notify,可以不用那么绕。
window窗口事件-->m_PaintManager(处理界面逻辑)--->丢回窗口处理业务逻辑(通过通知,也可以通过OnEvent处理)
我建议处理函数还是在通知里面,不要在事件里面,除非自己做特别处理。
比喻:点击事件其实通过BUTTON_UP事件判断,如果只是判断UP可能不准,同时跟界面按钮状态可能有冲突。
每个window对应一个CPaintManagerUI.
前2步
调用虚函数 HandleMessage, 我们做界面开发时候会继承CWindowWnd其实前面2步,只是把窗口HWND 与 我们代码对象绑定起来,建立关系,duilib 提供 WindowImplBase 方便我们开发界面而已,我们也可以不继承这个类,自己写事件和一些事件处理。
m_PaintManager.MessageHandle
进入界面库核心逻辑,前面都是只是传统界面逻辑,这里根据windwos 系统消息[WM_PAINT, WM_LBUTTONDOWN] 等等事件。
- 属性【画图】
- 行为
画图
根据我们的节点,一个一个画出来,这个类似html技术,目前所有主流界面开发都有点类似html 界面开发思路。
行为
根据鼠标和按键触发对应行为,dulib的行为基本都是通过notify 时间投递,这个投递在当前HWND里面。
这个类似win32 界面开发, 传统win32开发增加一个按钮事件 ,那么就投递 BM_CLICK给父窗口,父窗口在消息处理函数中处理这个消息。
例子
Button 点击例子代码分析
case WM_LBUTTONUP:
{
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
m_ptLastMousePos = pt;
if( m_pEventClick == NULL ) break;
ReleaseCapture();
TEventUI event = { 0 };
event.Type = UIEVENT_BUTTONUP;
event.pSender = m_pEventClick;
event.wParam = wParam;
event.lParam = lParam;
event.ptMouse = pt;
event.wKeyState = (WORD)wParam;
event.dwTimestamp = ::GetTickCount();
// By daviyang35 at 2015-6-5 16:10:13
// 在Click事件中弹出了模态对话框,退出阶段窗口实例可能已经删除
// this成员属性赋值将会导致heap错误
// this成员函数调用将会导致野指针异常
// 使用栈上的成员来调用响应,提前清空成员
// 当阻塞的模态窗口返回时,回栈阶段不访问任何类实例方法或属性
// 将不会触发异常
CControlUI* pClick = m_pEventClick;
m_pEventClick = NULL;
pClick->Event(event);
}
break;
在CPaintManagerUI::MessageHandler中处理windows 窗口消息,这里贴出来处理鼠标点击松开事件,基本都是这种写法来处理自绘的界面,记得很久很久之前自己写自绘的按钮就是在WM_PAINT 进行贴图,然后点击处理点击事件,就可以实现图片的按钮,比自己修改win32 button还方便一些,因为整个自绘逻辑都是自己开发的,用系统Button,很多逻辑都是系统,这样子导致很多限制,要打补丁的地方太多了。
TEventUI event = { 0 };
event.Type = UIEVENT_BUTTONUP;
event.pSender = m_pEventClick;
event.wParam = wParam;
event.lParam = lParam;
event.ptMouse = pt;
event.wKeyState = (WORD)wParam;
event.dwTimestamp = ::GetTickCount();
// By daviyang35 at 2015-6-5 16:10:13
// 在Click事件中弹出了模态对话框,退出阶段窗口实例可能已经删除
// this成员属性赋值将会导致heap错误
// this成员函数调用将会导致野指针异常
// 使用栈上的成员来调用响应,提前清空成员
// 当阻塞的模态窗口返回时,回栈阶段不访问任何类实例方法或属性
// 将不会触发异常
CControlUI* pClick = m_pEventClick;
m_pEventClick = NULL;
pClick->Event(event);
这里获取到点击控件,通过坐标计算找到合适控件【通过计算】,然后触发事件,这里事件就是UIEVENT_BUTTONUP,然后控件开始处理该事件。这个是虚函数,基本不会重载该虚函数,
button 控件处理函数
void CControlUI::Event(TEventUI& event)
{
if( OnEvent(&event) ) DoEvent(event);
}
所有控件的基类,OnEvent 是委托事件,用户代码基本不会委托这个函数,除了一些复杂的控件。
DoEvent是虚函数,所有控件基本都会重载这个函数,用来专门事件处理的。
这里OnEvent 一般是不会赋值,只有特殊控件需要自己处理时间,才会触发,这里OnEvent(event) 默认返回true,除非我自己添加委托事件返回false不喜欢通知外部才会不调用DoEvent。
butto调用 DoEvent
if( event.Type == UIEVENT_BUTTONUP )
{
if( (m_uButtonState & UISTATE_CAPTURED) != 0 ) {
if( ::PtInRect(&m_rcItem, event.ptMouse) && IsEnabled()) Activate();
m_uButtonState &= ~(UISTATE_PUSHED | UISTATE_CAPTURED);
Invalidate();
}
return;
}
Activate 激活按钮触发点击事件,Button还有其他事件要处理,来显示不同点击事件
bool CButtonUI::Activate()
{
if( !CControlUI::Activate() ) return false;
if( m_pManager != NULL ) m_pManager->SendNotify(this, DUI_MSGTYPE_CLICK);
return true;
}
触发通知,这个等同win32变成的发送 BN_CLICKED事件,m_pManager里面有一个list 包含notify listener,遍历进行通知。
duilib窗口对象都会绑定一个CPaintManagerUI,窗口创建后就添加事件监听。
我们开发代码都会实现INotifyUI或者直接继承WindowImplBase,我们业务代码基本都在notify实现的。
总结
这里这么多代码,无法就是子控件与窗口建立联系,方便回调。
duiib这个界面库,基本在各大厂商进行验证,可以算是windows pc上面一个标准了,官方开源版本太老,可以用其他大厂的开源duilib直接开发,不过自己继续在官方版本也可以。
如果跨平台基本就是electron,但这种内存比较大,几百M跑不掉,对内容类型的界面用这个比较方便,对于性能要求比较高还是用duilib比较好。
QT也是不错的选择,不过我自己没有用过,wps yy貌似都是qt开发的。
选择哪个开发主要看自己业务,没有完美的东西