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类,查看 ...
随机推荐
- ACM The Famous Clock
The Famous Clock 时间限制:1000 ms | 内存限制:65535 KB 难度:1 描述 Mr. B, Mr. G and Mr. M are now in Warsaw, ...
- ACM: 限时训练题解-Epic Professor-水题
Epic Professor Dr. Bahosain works as a professor of Computer Science at HU (Hadramout Universit ...
- 【BZOJ2473/2120】维护队列 分块+二分
Description 你小时候玩过弹珠吗? 小朋友A有一些弹珠,A喜欢把它们排成队列,从左到右编号为1到N.为了整个队列鲜艳美观,小朋友想知道某一段连续弹珠中,不同颜色的弹珠有多少.当然,A有时候会 ...
- HDU 2089 简单数位dp
1.HDU 2089 不要62 简单数位dp 2.总结:看了题解才敲出来的,还是好弱.. #include<iostream> #include<cstring> #i ...
- UpdatePanel完成后调用js
引言: asp.net 微软引入了UpdatePanel 使用起来很方便 如果 我们想UpdatePanel加载完成后做一些事情 需要使用js <script type="text/j ...
- Java 路径
http://swiftlet.net/archives/713 Java中不存在标准的相对路径,各种相对路径取资源的方式都是基于某种规则转化为绝对路径.所以在Java中文件路径问题无非归结为一点:找 ...
- c#面向对象基础 封装、继承
一.封装 这是一种隐藏的特性.可以用一个公式来展示类的封装特性: 封装的类=数据 + 对此数据进行的操作(即算法) 通俗的说,封装就是:包起外界不必要知道的东西,只向外界展露可供展示的东西. 在面 ...
- php完全销毁session
<?php session_start(); session_unset(); session_destroy(); ?> session_unset()释放当前在内存中已经创建的所有$_ ...
- Web前端开发基础 第四课(CSS文字和段落排版)
文字排版--字体 我们可以使用css样式为网页中的文字设置字体.字号.颜色等样式属性.下面我们来看一个例子,下面代码实现:为网页中的文字设置字体为宋体. body{font-family:" ...
- JDK+MyEclipse+Tomcat的配置(修改Tomcat 6.x的端口)
修改tomcat的端口,在conf目录里的server.xml文件 例如想将端口修改为8080则将port的值修改为8081,其余值不变 通过Tomcat服务器访问 想通过浏览器访问这个页面,需要在T ...