Duilib界面库Capture系列的相关bug解决

  Duilib是个轻量级的Windows界面库,可以让使用者在最快地时间构建出自己的项目,而且依赖最少。这里之所以突出它的“轻量级”,是为了强调作者在构建该界面库时的良苦用心。但是,物极必反,有些东西做得足够“傻瓜”后,就会变的不灵活。Duilib的控件的Caputre是个硬伤啊(希望作者不要骂我啊,我真是不好意思拆这个台,但是我今天真的遇到一个不可忽视的bug)。这个bug咱们暂且不说,先介绍一下什么是Capture,我想大家如果经常做界面的话,这个东西应该并不陌生,当我们鼠标按下一个Slider时,只要左键不放,鼠标无论滑到哪里,MouseMove事件都会一直在这Slider上面响应,这就是Capture的一个典型应用。在windows API中有SetCapture和ReleaseCapture这两个函数,SetCapture就是把当前窗口“Capture”(捕获),一旦Capture后,所有的鼠标事件都会在这个窗口响应,无论你的鼠标指针在什么位置。但是这两个函数是窗口级别的,windows的标准控件都是基于窗口,在Duilib中我们的控件都是绘图产生的,不受windows的管理,所以我们要自己为控件模拟一个类似于Windows标准控件的capture事件。
       其实,这个Capture事件的模拟,Duilib的原作者已经为我们做了,只是没有把capture事件暴露出来,而是傻瓜式的鼠标按下时就强制给窗口SetCapture,鼠标弹起时去ReleaseCapture,单击事件产生后,Capture事件强制产生,就用m_pEventClick把这个被点击的控件存下来,在窗口的MouseMove事件到来时,让m_pEventClick去响应。这样Capture事件就不用让控件层干涉了,PaintManager就全搞定了,如果这样做没有问题的话,那这个办法还相当不错,但是最终还是出现问题了。比如现在的Slider,当你按下鼠标左键不放,然后拖动到Slider区域外的位置然后点击右键然后松开,就会发现,Slider的鼠标左键好像一直没有弹起来,鼠标一移动上去,滑块就有反应了。再举个例子,水平布局的分隔线,在拖动时,同样按下右键,会立即出现问题,其实这都是一个原因导致的,就是这个Capture事件导致的。

      这些bug不再一一列举了,反正只要和Capture相关的拖动事件,都会有这个问题。直接说说我的的解决方案吧,说先找到CPaintManagerUI里面的m_pEventClick变量,直接把它删掉,然后把所有和m_pEventClick相关的语句全部删掉,这样做就是为了不再依赖作者这种方式,破釜沉舟,完全自己来解决这个Capture的问题。找到CPaintManagerUI里面的一对成员函数 SetCapture ,ReleaseCaputure,我们给SetCapture 加一个CControlUI* pControl参数。给CPaintManagerUI加一个新的成员变量CControlUI* m_pEventCapture(记得在构造时初始化为空),把SetCapture传入的pControl赋值给 m_pEventCapture,在ReleaseCaputure里面把m_pEventCapture置空,修改MessageHandler里面下面几个消息的处理,代码如下:

 case WM_MOUSEMOVE:
        {
            // Start tracking this entire window again...
            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 && m_pEventCapture == NULL ) break;

			TEventUI event = { 0 };
			event.ptMouse = pt;
			event.dwTimestamp = ::GetTickCount();

			if (m_pEventCapture==NULL)
			{
				if( pNewHover != m_pEventHover && m_pEventHover != NULL ) {
					event.Type = UIEVENT_MOUSELEAVE;
					event.pSender = m_pEventHover;
					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_pEventCapture != NULL ) {
                event.Type = UIEVENT_MOUSEMOVE;
                event.pSender = m_pEventCapture;
                m_pEventCapture->Event(event);
            }
            else if( pNewHover != NULL ) {
                event.Type = UIEVENT_MOUSEMOVE;
                event.pSender = pNewHover;
                pNewHover->Event(event);
            }
        }
        break;
    case WM_LBUTTONDOWN:
        {
            // We alway set focus back to our app (this helps
            // when Win32 child windows are placed on the dialog
            // and we need to remove them on focus change).

			if (!m_bUnfocusPaintWindow)
			{
                  ::SetFocus(m_hWndPaint);
			}
            POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
            m_ptLastMousePos = pt;
            CControlUI* pControl = FindControl(pt);

            if( (pControl == NULL||pControl->GetManager() != this)&&m_pEventCapture!=NULL) break;
           
			if (m_pEventCapture!=NULL)
			{
				pControl = m_pEventCapture;
			}
			

            pControl->SetFocus();
        
            TEventUI event = { 0 };
            event.Type = UIEVENT_BUTTONDOWN;
            event.pSender = pControl;
            event.wParam = wParam;
            event.lParam = lParam;
            event.ptMouse = pt;
            event.wKeyState = (WORD)wParam;
            event.dwTimestamp = ::GetTickCount();
            pControl->Event(event);
        }
        break;
    case WM_LBUTTONDBLCLK:
        {
			if (!m_bUnfocusPaintWindow)
			{
                  ::SetFocus(m_hWndPaint);
			}
            POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
            m_ptLastMousePos = pt;
            CControlUI* pControl = FindControl(pt);

			if( (pControl == NULL|| pControl->GetManager() != this)&&m_pEventCapture!=NULL) break;

			if (m_pEventCapture!=NULL)
			{
				pControl = m_pEventCapture;
			}

            TEventUI event = { 0 };
            event.Type = UIEVENT_DBLCLICK;
            event.pSender = pControl;
            event.ptMouse = pt;
            event.wKeyState = (WORD)wParam;
            event.dwTimestamp = ::GetTickCount();
            pControl->Event(event);

        }
        break;
    case WM_LBUTTONUP:
        {
			POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
			m_ptLastMousePos = pt;
			CControlUI* pControl = FindControl(pt);

			if( (pControl == NULL|| pControl->GetManager() != this)&&m_pEventCapture!=NULL) break;

			if (m_pEventCapture!=NULL)
			{
				pControl = m_pEventCapture;
			}

			TEventUI event = { 0 };
			event.Type = UIEVENT_BUTTONUP;
			event.pSender = pControl;
			event.wParam = wParam;
			event.lParam = lParam;
			event.ptMouse = pt;
			event.wKeyState = (WORD)wParam;
			event.dwTimestamp = ::GetTickCount();
			pControl->Event(event);
        }
        break;
    case WM_RBUTTONDOWN:
        {
			if (!m_bUnfocusPaintWindow)
			{
                   ::SetFocus(m_hWndPaint);
			}
            POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
            m_ptLastMousePos = pt;
            CControlUI* pControl = FindControl(pt);
			if( (pControl == NULL||pControl->GetManager() != this)&&m_pEventCapture!=NULL) break;

			if (m_pEventCapture!=NULL)
			{
				pControl = m_pEventCapture;
			}
            pControl->SetFocus();

            TEventUI event = { 0 };
            event.Type = UIEVENT_RBUTTONDOWN;
            event.pSender = pControl;
            event.wParam = wParam;
            event.lParam = lParam;
            event.ptMouse = pt;
            event.wKeyState = (WORD)wParam;
            event.dwTimestamp = ::GetTickCount();
            pControl->Event(event);

        }
        break;
    case WM_CONTEXTMENU:
        {
			POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
			 ::ScreenToClient(m_hWndPaint, &pt);
			m_ptLastMousePos = pt;
			CControlUI* pControl = FindControl(pt);
			if( (pControl == NULL||pControl->GetManager() != this)&&m_pEventCapture!=NULL) break;

			if (m_pEventCapture!=NULL)
			{
				pControl = m_pEventCapture;
			}
	
			// SetCapture();
			TEventUI event = { 0 };
			event.Type = UIEVENT_CONTEXTMENU;
			event.pSender = pControl;
			event.wParam = wParam;
			event.lParam = lParam;
			event.ptMouse = pt;
			event.wKeyState = (WORD)wParam;
			event.dwTimestamp = ::GetTickCount();
			pControl->Event(event);
        }
        break;
      完事后给CControlUI类加两个成员函数SetCapture,ReleaseCapture和一个成员变量bool m_bCapture(构造时初始化为假),关于SetCapture,ReleaseCapture的实现如下:
void CControlUI::SetCapture()
{
	if (m_pManager)
	{
		m_pManager->SetCapture(this);
		m_bCapture = true;
	}

}


void CControlUI::ReleaseCapture()
{
	if (m_pManager)
	{
		m_pManager->ReleaseCapture();
		m_bCapture = false;
	}
}
     以上,我们只是简单地把Capture事件在控件上模拟了一下(因为Duilib本身的轻量级,就不整太复杂了,干净利索就好),下面就要修改控件层了。为了不影响任何一个控件的使用,我们把所有控件的UIEVENT_BUTTONDOWN和UIEVENT_RBUTTONDOWN的处理的开头处,执行控件的SetCapture,在UIEVENT_BUTTONUP和UIEVENT_CONTEXTMENU的处理的开头处,执行控件的ReleaseCapture。比如CButtonUI中应该这样使用:

	if( event.Type == UIEVENT_BUTTONDOWN || event.Type == UIEVENT_DBLCLICK)
		{
			SetCapture();
			if( ::PtInRect(&m_rcItem, event.ptMouse) && IsEnabled() ) {
				m_uButtonState |= UISTATE_PUSHED | UISTATE_CAPTURED;
				Invalidate();
			}
			return;
		}	
		if( event.Type == UIEVENT_MOUSEMOVE )
		{
			if( (m_uButtonState & UISTATE_CAPTURED) != 0 ) {
				if( ::PtInRect(&m_rcItem, event.ptMouse) ) m_uButtonState |= UISTATE_PUSHED;
				else m_uButtonState &= ~UISTATE_PUSHED;
				Invalidate();
			}
			return;
		}
		if( event.Type == UIEVENT_BUTTONUP )
		{
			ReleaseCapture();
			if( (m_uButtonState & UISTATE_CAPTURED) != 0 ) {
				if( ::PtInRect(&m_rcItem, event.ptMouse) ) Activate();				
				m_uButtonState &= ~(UISTATE_PUSHED | UISTATE_CAPTURED);
				Invalidate();
			}
			return;
		}
		if( event.Type == UIEVENT_CONTEXTMENU )
		{
			if( IsContextMenuUsed() ) {
				m_pManager->SendNotify(this, DUI_MSGTYPE_MENU, event.wParam, event.lParam);
			}
			return;
		}

CSliderUI这样使用:

	if( event.Type == UIEVENT_BUTTONDOWN || event.Type == UIEVENT_DBLCLICK )
		{
			SetCapture();
			if( IsEnabled() ) {//2014.7.28 redrain 注释掉原来的代码,加上这些代码后可以让Slider不是在鼠标弹起时才改变滑块的位置
				m_uButtonState |= UISTATE_CAPTURED;

				int nValue;

				if( m_bHorizontal ) {
					if( event.ptMouse.x >= m_rcItem.right - m_szThumb.cx / 2 ) nValue = m_nMax;
					else if( event.ptMouse.x <= m_rcItem.left + m_szThumb.cx / 2 ) nValue = m_nMin;
					else nValue = m_nMin + (m_nMax - m_nMin) * (event.ptMouse.x - m_rcItem.left - m_szThumb.cx / 2 ) / (m_rcItem.right - m_rcItem.left - m_szThumb.cx);
				}
				else {
					if( event.ptMouse.y >= m_rcItem.bottom - m_szThumb.cy / 2 ) nValue = m_nMin;
					else if( event.ptMouse.y <= m_rcItem.top + m_szThumb.cy / 2  ) nValue = m_nMax;
					else nValue = m_nMin + (m_nMax - m_nMin) * (m_rcItem.bottom - event.ptMouse.y - m_szThumb.cy / 2 ) / (m_rcItem.bottom - m_rcItem.top - m_szThumb.cy);
				}
				if(m_nValue !=nValue && nValue>=m_nMin && nValue<=m_nMax)
				{
					m_nValue =nValue;
					Invalidate();
				}
			}
				return;
		}

// 		if( event.Type == UIEVENT_BUTTONDOWN || event.Type == UIEVENT_DBLCLICK )
// 		{
// 			if( IsEnabled() ) {
// 				RECT rcThumb = GetThumbRect();
// 				if( ::PtInRect(&rcThumb, event.ptMouse) ) {
// 					m_uButtonState |= UISTATE_CAPTURED;
// 				}
// 			}
// 			return;
// 		}
		if( event.Type == UIEVENT_BUTTONUP)
		{
			ReleaseCapture();
			int nValue;
			if( (m_uButtonState & UISTATE_CAPTURED) != 0 ) {
				m_uButtonState &= ~UISTATE_CAPTURED;
			}
			if( m_bHorizontal ) {
				if( event.ptMouse.x >= m_rcItem.right - m_szThumb.cx / 2 ) nValue = m_nMax;
				else if( event.ptMouse.x <= m_rcItem.left + m_szThumb.cx / 2 ) nValue = m_nMin;
				else nValue = m_nMin + (m_nMax - m_nMin) * (event.ptMouse.x - m_rcItem.left - m_szThumb.cx / 2 ) / (m_rcItem.right - m_rcItem.left - m_szThumb.cx);
			}
			else {
				if( event.ptMouse.y >= m_rcItem.bottom - m_szThumb.cy / 2 ) nValue = m_nMin;
				else if( event.ptMouse.y <= m_rcItem.top + m_szThumb.cy / 2  ) nValue = m_nMax;
				else nValue = m_nMin + (m_nMax - m_nMin) * (m_rcItem.bottom - event.ptMouse.y - m_szThumb.cy / 2 ) / (m_rcItem.bottom - m_rcItem.top - m_szThumb.cy);
			}
			if(/*m_nValue !=nValue && 2014.7.28 redrain 这个注释很关键,是他导致了鼠标拖动滑块无法发出DUI_MSGTYPE_VALUECHANGED消息*/nValue>=m_nMin && nValue<=m_nMax)
			{
				m_nValue =nValue;
				m_pManager->SendNotify(this, DUI_MSGTYPE_VALUECHANGED);
				Invalidate();
			}
			return;
		}
           使用方法很简单,还有CScrollBarUI还有水平和垂直布局的分隔线也需要这样去使用,因为比较简单,就不过多解释了,照着步骤改就行了。如果发现bug或者其他问题请联系我,QQ:848861075(Skilla)


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Skilla

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值