DuiLib事件分析(一)——鼠标事件响应
最近在处理DuiLib中自定义列表行元素事件,因为处理方案得不到较好的效果,于是只好一层一层的去剥离DuiLib事件是怎么来的,看能否在某一层截取消息,自己重写。
我这里使用CListContainerElementUI行元素,元素中有插入button,平时行元素不显示,鼠标移动上去显示出来,鼠标移走就隐藏button。Duilib自己是不带这个功能的,它有一个鼠标移动上去的热点事件,按理说重写热点事件就好了。但是当时比较急没找到怎么触发的,之后一直没继续走这条思路。后来找到源码事件里面有
void CListContainerElementUI::DoEvent(TEventUI& event) if( event.Type == UIEVENT_MOUSEENTER )
{
if( IsEnabled() ) {
m_uButtonState |= UISTATE_HOT;
Invalidate();
//我自己添加的回调函数
pCallBackFunc(this);
}
return;
}
于是就在UIEVENT_MOUSEENTER中加入了自己写的一个回调函数,只要行元素触发鼠标移动上去的事件,就去调用回调,回调再去根据业务逻辑操作UI。想法很好,但是遇到一个问题,当鼠标移动到行元素的button上时,行元素会触发
if( event.Type == UIEVENT_MOUSELEAVE )
{
if( (m_uButtonState & UISTATE_HOT) != 0 ) {
m_uButtonState &= ~UISTATE_HOT;
Invalidate();
}
return;
}
所以,如果在UIEVENT_MOUSEENTER时去设置为移动上行元素效果,在UIEVENT_MOUSELEAVE时设置为离开行元素效果,行不通。
当时设置了一个偷懒的方式,当鼠标移动到另外一个行元素上时,重置其他行元素的UI为鼠标离开。看似这样就解决问题了,但是当鼠标不移动到其他行元素上,直接离开列表时,则会导致鼠标最后停留的那个行元素不能被置为鼠标离开状态。
通过对相关源码的阅读,得到以下关系图:
而CControlUI并不继承自任何类。
class UILIB_API CControlUI
{
public:
CControlUI();
virtual ~CControlUI();
......
}
同时,在CControlUI中,DoEvent是来自于以下调用
void CControlUI::Event(TEventUI& event)
{
if( OnEvent(&event) ) DoEvent(event);
}
查看Event引用得到下图:
前面提到CControlUI不继承自任何类,显然Event不是重构了一个系统函数,调用必然不会来自于上图的后面4条,而1,2条是定义和实现。这条线索算是中断了。
于是,现在想从行元素鼠标离开事件本身着手,看能否从堆栈跟踪里面得到答案。在UIEVENT_MOUSELEAVE处断点,跟踪堆栈:
这样我们就看到了是来自于以下调用
> dclient.exe!DuiLib::CPaintManagerUI::MessageHandler(unsigned int uMsg, unsigned int wParam, long lParam, long & lRes) 行 843 + 0x21 字节 C++
我们再来看看MessangeHandler的代码
bool CPaintManagerUI::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lRes)
{
......
switch( uMsg ) {
.....
case WM_CLOSE:...
case WM_SIZE:...
case WM_MOUSEHOVER:...
case WM_MOUSELEAVE:...
case WM_MOUSEMOVE:...
......
}
也就是说,消息会在这里被默认处理,而它的再上一层调用是用户自己定义的消息处理机制。我们此时再来看自己写的调用:
//消息循环
LRESULT MyFrameWnd::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
......
case WM_CLOSE:...
......
default:
break;
}
if( bHandled ) return lRes;
return WindowImplBase::HandleMessage(uMsg, wParam, lParam);
}
这里的return就是把消息返回给了WindowImplBase
官方示例文档Duilib入门是返回给了
return CWindowWnd::HandleMessage(uMsg, wParam, lParam);
这两个有些差异,但是并不影响我们继续分析,我们的WindowImpIBase的调用就是来自于
LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
CWindowWnd* pThis = NULL;
......
pThis = reinterpret_cast<CWindowWnd*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA))
.....
if( pThis != NULL ) {
return pThis->HandleMessage(uMsg, wParam, lParam);
}
else {
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
}
}
可见所有事件是来自于CWindowWnd窗口,窗口直接把事件给了开发者,当开发者不拦截相应事件的时候,就return给了DuiLib处理。DuiLib通过层层机制,反馈给相应的控件去处理事件。
所有,要实现自定义消息处理,不用动源码,只要直接在HandleMessage中拦截消息即可。
OK,看似思路清晰了,但是还面临一个问题,给开发者调用的事件里面,没有参数表明是哪个元素触发的,想从这个层面拦截,似乎只能拦截一些通用事件,比如关闭,比如按钮点击。
我们来看看UIManager中,MessageHandler在处理鼠标移动时的处理方法 case WM_MOUSEMOVE:
if( !m_bMouseTracking ) {
TRACKMOUSEEVENT tme = { 0 };
tme.cbSize = sizeof(TRACKMOUSEEVENT);
tme.dwFlags = TME_HOVER | TME_LEAVE;
tme.hwndTrack = m_hWndPaint;
tme.dwHoverTime = m_hwndTooltip == NULL ? 400UL : (DWORD) ::SendMessage(m_hwndTooltip, TTM_GETDELAYTIME, TTDT_INITIAL, 0L);
_TrackMouseEvent(&tme);
m_bMouseTracking = true;
}
// Generate the appropriate mouse messages
//获取鼠标当前位置
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
m_ptLastMousePos = pt;
//根据位置获取鼠标在某个元素上
CControlUI* pNewHover = FindControl(pt);
if( pNewHover != NULL && pNewHover->GetManager() != this ) break;
TEventUI event = { 0 };
event.ptMouse = pt;
event.dwTimestamp = ::GetTickCount();
//判断是否鼠标离开某个元素
if( pNewHover != m_pEventHover && m_pEventHover != NULL ) {
event.Type = UIEVENT_MOUSELEAVE;
event.pSender = m_pEventHover;
//这里的m_pEventHover经过后面一些代码运作,就会被赋值为pNewHover,也就是鼠标移动上去的那个元素
m_pEventHover->Event(event);
m_pEventHover = NULL;
if( m_hwndTooltip != NULL ) ::SendMessage(m_hwndTooltip, TTM_TRACKACTIVATE, FALSE, (LPARAM) &m_ToolTip);
}
//判断鼠标是否进入某个元素
if( pNewHover != m_pEventHover && pNewHover != NULL ) {
event.Type = UIEVENT_MOUSEENTER;
event.pSender = pNewHover;
pNewHover->Event(event);
//设置热点元素为当前元素
m_pEventHover = pNewHover;
}
if( m_pEventClick != NULL ) {
event.Type = UIEVENT_MOUSEMOVE;
event.pSender = m_pEventClick;
m_pEventClick->Event(event);
}
else if( pNewHover != NULL ) {
event.Type = UIEVENT_MOUSEMOVE;
event.pSender = pNewHover;
pNewHover->Event(event);
}
}
先通过鼠标位置判断是否在某个元素上,在元素上则标记m_pEventHover为当前元素,再去配置相关消息,调用元素的Event。也就是说,在DuiLib处理鼠标移动事件时,根据鼠标位置获取了相应元素,并通触发该元素行为。由此可见,自定义列表行元素,当鼠标停留在行元素上时,会触发UIEVENT_MOUSEENTER,而当鼠标移动到行元素的button上时,会修改m_pEventHover,它会认为这是移动到另外一个元素上了,去调用这个新元素的Event。
到我们可以整理出DuiLib鼠标事件响应的大概流程:
1.窗体触发事件
2.用户消息循环,把处理不掉的信息反馈给DuiLib的基类处理,基类通过CPaintManagerUI的MessageHandler来处理一些基础消息
3.MessageHandler把消息分类处理
根据以上分析,如果要实现我前面提到的鼠标移动上行元素,再改变行元素状态,要通过修改DuiLib源码来实现就非常困难了。MessageHandler是无法预知,也不应该知道用户层面想要的特效的。所以最终要解决这个问题,还是应该放到用户消息循环,通过比较复杂的逻辑去实现。
DuiLib事件分析(一)——鼠标事件响应的更多相关文章
- js课程 5-13 js事件绑定和鼠标事件注意事项有哪些
js课程 5-13 js事件绑定和鼠标事件注意事项有哪些 一.总结 一句话总结:js代码的灵魂就是改变标签的属性和样式,就这两种. 1.js触发改的东西是哪两样? 属性和样式 2.js如何让页面用标 ...
- javaScript事件(五)事件类型之鼠标事件
一.事件 二.事件流 以上内容见:javaScript事件(一)事件流 三.事件处理程序 四.IE事件处理程序 以上内容见javaScript事件(二)事件处理程序 五.事件对象 以上内容见javaS ...
- JavaScript的事件对象_鼠标事件
鼠标事件是 Web 上面最常用的一类事件,毕竟鼠标还是最主要的定位设备.那么通过事件对象可以获取到鼠标按钮信息和屏幕坐标获取等. 一.鼠标按钮 只有在主鼠标按钮被单击时(常规一般是鼠标左键)才会触发 ...
- JS——事件详情(鼠标事件:clientX、clientY的用法)
鼠标位置 >可视区位置:clientX.clientY 跟着鼠标移动的div案例 代码如下图: 这个案例,运用到前一篇文章中的event事件来处理.获取div的left和top值,当鼠标移动 ...
- java 事件监听 - 鼠标
java 事件监听 - 鼠标 //事件监听 //鼠标事件监听 //鼠标事件监听有两个实现接口 //1.MouseListener 普通的鼠标操作 //2.MouseMotionListener 鼠标的 ...
- 以JTextPanel为例Swing的鼠标事件详解
如下界面可以通过该界面研究一下Swing的鼠标事件: 图中用红粗线圈起来的为JtextPanel,该Panel添加了鼠标事件监听器,鼠标事件监听器有三种,分别为MouseWheelListener,M ...
- jacascript 鼠标事件和键盘事件
前言:这是笔者学习之后自己的理解与整理.如果有错误或者疑问的地方,请大家指正,我会持续更新! 鼠标事件 鼠标事件共10类,包括click.contextmenu(右键).dblclick(双击).mo ...
- PyQt5编程:鼠标事件
参考链接:https://www.cnblogs.com/zhuluqing/p/9028816.html 一.每个事件都被封装成相应的类: pyqt中,每个事件类型都被封装成相应的事件类,如鼠标事件 ...
- pyqt5事件与鼠标事件
一,每个事件都被封装成相应的类: pyqt中,每个事件类型都被封装成相应的事件类,如鼠标事件为QMouseEvent,键盘事件为QKeyEvent等.而它们的基类是QEvent. 二,基类QEvent ...
- soeasy的键盘鼠标事件
在web自动化中,我们可能会遇到需要通过键盘或者鼠标去操作某些元素,那么我们就需要用到键盘事件和鼠标事件了,今天对键盘和鼠标操作进行一个总结 鼠标事件 鼠标事件需要引入ActionChains类,查看 ...
随机推荐
- UVA 10780 - Again Prime? No Time.
题目链接 思路好想,注意细节.错了很多次. #include <cstdio> #include <cstring> #include <string> #incl ...
- linux 下如何打开core dump文件开关
dump文件可以在程序crash时,方便我们查看程序crash的地方和上下文信息.在window下,要能生成dump文件,需要自己编写相应的代码.不过现在网上可以找到相应的代码,只要把它下载后然后加到 ...
- bug:无法给图片加边框
一,经历 1.错误代码 _avatarView.layer.cornerRadius = GIFT_AVATAR_WIDTH * 0.5; _avatarView.layer.borderColor ...
- HttpClient_使用httpclient必须知道的参数设置及代码写法、存在的风险
结论: 如果使用httpclient 3.1并发量比较大的项目,最好升级到httpclient4.2.3上,保证并发量大时能抗住.httpclient 4.3.3,目前还有一些bug:还是用4.2.x ...
- 六、雪花《苹果iOS实例编程入门教程》
该app为应用的功能为制作一场雪景 现版本 SDK 8.4 Xcode 纲要:- UIImageView 的运用- onTimer 代码运用- onAnimation 代码运用 运行Xcode 选择 ...
- Iterator用法
<% List<Emp> all=DAOFactory.getIEmpDAOInstance().findAll(keyWord); Itrator<Emp> iter= ...
- select()函数 timval问题
如果select调用中设置了等待时间,那么每次调用时都需要重新对这个时间赋值.例如: struct timval tv; while(1) { ........; tv.tv_sec = 2; ...
- signal(SIGPIPE, SIG_IGN)
文章来源:http://blog.163.com/niuxiangshan@126/blog/static/170596595201221942952676/ 当服务器close一个连接时,若cl ...
- [LintCode] Length of Last Word 求末尾单词的长度
Given a string s consists of upper/lower-case alphabets and empty space characters ' ', return the l ...
- 格式与布局 CSS阴影效果(Box-shadow)用法
一.position:fixed 锁定位置(相对于浏览器的位置),例如有些网站的右下角的弹出窗口. 二.position:absolute 1.外层没有position:absolute(或relat ...