從GameDemo.cpp看起
1回顧通常的sdk窗口程序流程:注冊窗口-創建窗口-顯示窗口-啟動消息循環
1.1注冊窗口類
Duilib中最平凡的真實窗口類是:CWindowWnd,關于窗口注冊提供了兩個函數,嚴格的說應該是幾個:
RegisterWindowClass()
RegisterSuperclass()
GetWindowClassName()
GetSuperClassName()
GetClassStyle()
在我的理解中,后面兩個虛函數的意義應該是:上面這些接口分兩組,一組是用于正常注冊使用,一組用于擴展。
使用的時候用自定義的窗口對象從CWindowWnd繼承下來,然后定制自己需要的window class
1.2創建窗口
CGameFrameWnd* pFrame = new CGameFrameWnd(); if( pFrame == NULL ) return 0; pFrame->Create(NULL, _T(""), UI_WNDSTYLE_FRAME, 0L, 0, 0, 1024, 738);.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }
CWindowWnd帶有mfc CWnd類似的Create接口用于創建窗口,同事注冊窗口類也通過虛函數的方式延后到子類實現,super機制(如果有super優先注冊)也帶進來。
if( GetSuperClassName() != NULL && !RegisterSuperclass() ) return NULL; if( GetSuperClassName() == NULL && !RegisterWindowClass() ) return NULL;.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }
1.3顯示窗口
和sdk是一樣的
::ShowWindow(*pFrame, SW_SHOWMAXIMIZED);
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } 1.4消息循環
CPaintManagerUI::MessageLoop();.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }
1.5消息回調函數
在窗口類注冊的時候應該要注冊窗口回調函數指針,Duilib中默認是在CWindowWnd::RegisterWindow注冊:
bool CWindowWnd::RegisterWindowClass()
{
WNDCLASS wc = { 0 };
wc.style = GetClassStyle();
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hIcon = NULL;
wc.lpfnWndProc = CWindowWnd::__WndProc;
__WndProc是CWindowWnd的一個靜態成員,這個函數值得看一下:
LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { CWindowWnd* pThis = NULL; if( uMsg == WM_NCCREATE ) { LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam); pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams); pThis->m_hWnd = hWnd; ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(pThis)); } else { pThis = reinterpret_cast<CWindowWnd*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA)); if( uMsg == WM_NCDESTROY && pThis != NULL ) { LRESULT lRes = ::CallWindowProc(pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam); ::SetWindowLongPtr(pThis->m_hWnd, GWLP_USERDATA, 0L); if( pThis->m_bSubclassed ) pThis->Unsubclass(); pThis->m_hWnd = NULL; pThis->OnFinalMessage(hWnd); return lRes; } } if( pThis != NULL ) { return pThis->HandleMessage(uMsg, wParam, lParam); } else { return ::DefWindowProc(hWnd, uMsg, wParam, lParam); } }.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }
針對WM_NCCREATE這個消息有一個管用技巧,先復習下這個消息:
The WM_NCCREATE message is sent prior to the WM_CREATE message when a window is first created.
我的理解中,這個消息應該是窗口收到的第一個消息。
還有WM_NCDESTROY這個特殊的消息。
當然了,還有這個牛逼的戲法:
::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(pThis));
總的來說一句話,兩個消息NC create destroy對應的應該有連個函數,但是原本的OnNcCreate響應隱藏在了__WndProc中,還有一個函數OnFinalMessage。this指針藏在SetWindowLongPtr(hWnd, GWLP_USERDATA,,,
好了底層需要注意的就只有這兩個函數,其他的消息都應該是拋給子類去處理了。
既然是玩directUi,就重點關注一下WM_NCPAINT消息,也一個也很重要的消息WM_NCHITTEST。
不知道為什么這里用的都是NC系列消息。
NC應該是理解成none client,初步觀察Duilib的size拖拉支持是使用NCHITTEST+SIZE消息來實現的。
看NCPAINT消息體里邊沒有任何代碼這很詭異,所以還是看看完整的消息響應函數,是不是漏掉了什么:
LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) { LRESULT lRes = 0; BOOL bHandled = TRUE; switch( uMsg ) { case WM_CREATE: lRes = OnCreate(uMsg, wParam, lParam, bHandled); break; case WM_CLOSE: lRes = OnClose(uMsg, wParam, lParam, bHandled); break; case WM_DESTROY: lRes = OnDestroy(uMsg, wParam, lParam, bHandled); break; case WM_NCACTIVATE: lRes = OnNcActivate(uMsg, wParam, lParam, bHandled); break; case WM_NCCALCSIZE: lRes = OnNcCalcSize(uMsg, wParam, lParam, bHandled); break; case WM_NCPAINT: lRes = OnNcPaint(uMsg, wParam, lParam, bHandled); break; case WM_NCHITTEST: lRes = OnNcHitTest(uMsg, wParam, lParam, bHandled); break; case WM_SIZE: lRes = OnSize(uMsg, wParam, lParam, bHandled); break; case WM_GETMINMAXINFO: lRes = OnGetMinMaxInfo(uMsg, wParam, lParam, bHandled); break; case WM_SYSCOMMAND: lRes = OnSysCommand(uMsg, wParam, lParam, bHandled); break; default: bHandled = FALSE; } if( bHandled ) return lRes; if( m_pm.MessageHandler(uMsg, wParam, lParam, lRes) ) return lRes; return CWindowWnd::HandleMessage(uMsg, wParam, lParam); }.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }
乍看之下這里的消息處理至少分為三層,CWindowWnd派生類本身沒有處理的消息將會被送到m_pm中去處理,如果m_pm.MessageHandler返回false消息最后還是CWindowWnd處理。
bool CPaintManagerUI::MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lRes) { //#ifdef _DEBUG // switch( uMsg ) { // case WM_NCPAINT: // case WM_NCHITTEST: // case WM_SETCURSOR: // break; // default: // DUITRACE(_T("MSG: %-20s (%08ld)"), DUITRACEMSG(uMsg), ::GetTickCount()); // } //#endif // Not ready yet? if( m_hWndPaint == NULL ) return false; TNotifyUI* pMsg = NULL; while( pMsg = static_cast<TNotifyUI*>(m_aAsyncNotify.GetAt(0)) ) { m_aAsyncNotify.Remove(0); if( pMsg->pSender != NULL ) { if( pMsg->pSender->OnNotify ) pMsg->pSender->OnNotify(pMsg); } for( int j = 0; j < m_aNotifiers.GetSize(); j++ ) { static_cast<INotifyUI*>(m_aNotifiers[j])->Notify(*pMsg); } delete pMsg; } // Cycle through listeners for( int i = 0; i < m_aMessageFilters.GetSize(); i++ ) { bool bHandled = false; LRESULT lResult = static_cast<IMessageFilterUI*>(m_aMessageFilters[i])->MessageHandler(uMsg, wParam, lParam, bHandled); if( bHandled ) { lRes = lResult; return true; } } // Custom handling of events switch( uMsg ) { case WM_APP + 1: { for( int i = 0; i < m_aDelayedCleanup.GetSize(); i++ ) delete static_cast<CControlUI*>(m_aDelayedCleanup[i]); m_aDelayedCleanup.Empty(); } break; case WM_CLOSE: { // Make sure all matching "closing" events are sent TEventUI event = { 0 }; event.ptMouse = m_ptLastMousePos; event.dwTimestamp = ::GetTickCount(); if( m_pEventHover != NULL ) { event.Type = UIEVENT_MOUSELEAVE; event.pSender = m_pEventHover; m_pEventHover->Event(event); } if( m_pEventClick != NULL ) { event.Type = UIEVENT_BUTTONUP; event.pSender = m_pEventClick; m_pEventClick->Event(event); } SetFocus(NULL); // Hmmph, the usual Windows tricks to avoid // focus loss... HWND hwndParent = GetWindowOwner(m_hWndPaint); if( hwndParent != NULL ) ::SetFocus(hwndParent); } break; case WM_ERASEBKGND: { // We'll do the painting here... lRes = 1; } return true; case WM_PAINT: { // Should we paint? RECT rcPaint = { 0 }; if( !::GetUpdateRect(m_hWndPaint, &rcPaint, FALSE) ) return true; if( m_pRoot == NULL ) { PAINTSTRUCT ps = { 0 }; ::BeginPaint(m_hWndPaint, &ps); ::EndPaint(m_hWndPaint, &ps); return true; } // Do we need to resize anything? // This is the time where we layout the controls on the form. // We delay this even from the WM_SIZE messages since resizing can be // a very expensize operation. if( m_bUpdateNeeded ) { m_bUpdateNeeded = false; RECT rcClient = { 0 }; ::GetClientRect(m_hWndPaint, &rcClient); if( !::IsRectEmpty(&rcClient) ) { if( m_pRoot->IsUpdateNeeded() ) { m_pRoot->SetPos(rcClient); if( m_hDcOffscreen != NULL ) ::DeleteDC(m_hDcOffscreen); if( m_hDcBackground != NULL ) ::DeleteDC(m_hDcBackground); if( m_hbmpOffscreen != NULL ) ::DeleteObject(m_hbmpOffscreen); if( m_hbmpBackground != NULL ) ::DeleteObject(m_hbmpBackground); m_hDcOffscreen = NULL; m_hDcBackground = NULL; m_hbmpOffscreen = NULL; m_hbmpBackground = NULL; } else { CControlUI* pControl = NULL; while( pControl = m_pRoot->FindControl(__FindControlFromUpdate, NULL, UIFIND_VISIBLE | UIFIND_ME_FIRST) ) { pControl->SetPos( pControl->GetPos() ); } } // We'll want to notify the window when it is first initialized // with the correct layout. The window form would take the time // to submit swipes/animations. if( m_bFirstLayout ) { m_bFirstLayout = false; SendNotify(m_pRoot, DUI_MSGTYPE_WINDOWINIT, 0, 0, false); } } } // Set focus to first control? if( m_bFocusNeeded ) { SetNextTabControl(); } // // Render screen // // Prepare offscreen bitmap? if( m_bOffscreenPaint && m_hbmpOffscreen == NULL ) { RECT rcClient = { 0 }; ::GetClientRect(m_hWndPaint, &rcClient); m_hDcOffscreen = ::CreateCompatibleDC(m_hDcPaint); m_hbmpOffscreen = ::CreateCompatibleBitmap(m_hDcPaint, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top); ASSERT(m_hDcOffscreen); ASSERT(m_hbmpOffscreen); } // Begin Windows paint PAINTSTRUCT ps = { 0 }; ::BeginPaint(m_hWndPaint, &ps); if( m_bOffscreenPaint ) { HBITMAP hOldBitmap = (HBITMAP) ::SelectObject(m_hDcOffscreen, m_hbmpOffscreen); int iSaveDC = ::SaveDC(m_hDcOffscreen); if( m_bAlphaBackground ) { if( m_hbmpBackground == NULL ) { RECT rcClient = { 0 }; ::GetClientRect(m_hWndPaint, &rcClient); m_hDcBackground = ::CreateCompatibleDC(m_hDcPaint);; m_hbmpBackground = ::CreateCompatibleBitmap(m_hDcPaint, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top); ASSERT(m_hDcBackground); ASSERT(m_hbmpBackground); ::SelectObject(m_hDcBackground, m_hbmpBackground); ::BitBlt(m_hDcBackground, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.bottom - ps.rcPaint.top, ps.hdc, ps.rcPaint.left, ps.rcPaint.top, SRCCOPY); } else ::SelectObject(m_hDcBackground, m_hbmpBackground); ::BitBlt(m_hDcOffscreen, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.bottom - ps.rcPaint.top, m_hDcBackground, ps.rcPaint.left, ps.rcPaint.top, SRCCOPY); } m_pRoot->DoPaint(m_hDcOffscreen, ps.rcPaint); for( int i = 0; i < m_aPostPaintControls.GetSize(); i++ ) { CControlUI* pPostPaintControl = static_cast<CControlUI*>(m_aPostPaintControls[i]); pPostPaintControl->DoPostPaint(m_hDcOffscreen, ps.rcPaint); } ::RestoreDC(m_hDcOffscreen, iSaveDC); ::BitBlt(ps.hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.bottom - ps.rcPaint.top, m_hDcOffscreen, ps.rcPaint.left, ps.rcPaint.top, SRCCOPY); ::SelectObject(m_hDcOffscreen, hOldBitmap); if( m_bShowUpdateRect ) { HPEN hOldPen = (HPEN)::SelectObject(ps.hdc, m_hUpdateRectPen); ::SelectObject(ps.hdc, ::GetStockObject(HOLLOW_BRUSH)); ::Rectangle(ps.hdc, rcPaint.left, rcPaint.top, rcPaint.right, rcPaint.bottom); ::SelectObject(ps.hdc, hOldPen); } } else { // A standard paint job int iSaveDC = ::SaveDC(ps.hdc); m_pRoot->DoPaint(ps.hdc, ps.rcPaint); ::RestoreDC(ps.hdc, iSaveDC); } // All Done! ::EndPaint(m_hWndPaint, &ps); } // If any of the painting requested a resize again, we'll need // to invalidate the entire window once more. if( m_bUpdateNeeded ) { ::InvalidateRect(m_hWndPaint, NULL, FALSE); } return true; case WM_PRINTCLIENT: { RECT rcClient; ::GetClientRect(m_hWndPaint, &rcClient); HDC hDC = (HDC) wParam; int save = ::SaveDC(hDC); m_pRoot->DoPaint(hDC, rcClient); // Check for traversing children. The crux is that WM_PRINT will assume // that the DC is positioned at frame coordinates and will paint the child // control at the wrong position. We'll simulate the entire thing instead. if( (lParam & PRF_CHILDREN) != 0 ) { HWND hWndChild = ::GetWindow(m_hWndPaint, GW_CHILD); while( hWndChild != NULL ) { RECT rcPos = { 0 }; ::GetWindowRect(hWndChild, &rcPos); ::MapWindowPoints(HWND_DESKTOP, m_hWndPaint, reinterpret_cast<LPPOINT>(&rcPos), 2); ::SetWindowOrgEx(hDC, -rcPos.left, -rcPos.top, NULL); // NOTE: We use WM_PRINT here rather than the expected WM_PRINTCLIENT // since the latter will not print the nonclient correctly for // EDIT controls. ::SendMessage(hWndChild, WM_PRINT, wParam, lParam | PRF_NONCLIENT); hWndChild = ::GetWindow(hWndChild, GW_HWNDNEXT); } } ::RestoreDC(hDC, save); } break; case WM_GETMINMAXINFO: { LPMINMAXINFO lpMMI = (LPMINMAXINFO) lParam; if( m_szMinWindow.cx > 0 ) lpMMI->ptMinTrackSize.x = m_szMinWindow.cx; if( m_szMinWindow.cy > 0 ) lpMMI->ptMinTrackSize.y = m_szMinWindow.cy; if( m_szMaxWindow.cx > 0 ) lpMMI->ptMaxTrackSize.x = m_szMaxWindow.cx; if( m_szMaxWindow.cy > 0 ) lpMMI->ptMaxTrackSize.y = m_szMaxWindow.cy; } break; case WM_SIZE: { if( m_pFocus != NULL ) { TEventUI event = { 0 }; event.Type = UIEVENT_WINDOWSIZE; event.pSender = m_pFocus; event.dwTimestamp = ::GetTickCount(); m_pFocus->Event(event); } if( m_pRoot != NULL ) m_pRoot->NeedUpdate(); } return true; case WM_TIMER: { for( int i = 0; i < m_aTimers.GetSize(); i++ ) { const TIMERINFO* pTimer = static_cast<TIMERINFO*>(m_aTimers[i]); if( pTimer->hWnd == m_hWndPaint && pTimer->uWinTimer == LOWORD(wParam) && pTimer->bKilled == false) { TEventUI event = { 0 }; event.Type = UIEVENT_TIMER; event.pSender = pTimer->pSender; event.wParam = pTimer->nLocalID; event.dwTimestamp = ::GetTickCount(); pTimer->pSender->Event(event); break; } } } break; case WM_MOUSEHOVER: { m_bMouseTracking = false; POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; CControlUI* pHover = FindControl(pt); if( pHover == NULL ) break; // Generate mouse hover event if( m_pEventHover != NULL ) { TEventUI event = { 0 }; event.ptMouse = pt; event.Type = UIEVENT_MOUSEHOVER; event.pSender = m_pEventHover; event.dwTimestamp = ::GetTickCount(); m_pEventHover->Event(event); } // Create tooltip information CDuiString sToolTip = pHover->GetToolTip(); if( sToolTip.IsEmpty() ) return true; ::ZeroMemory(&m_ToolTip, sizeof(TOOLINFO)); m_ToolTip.cbSize = sizeof(TOOLINFO); m_ToolTip.uFlags = TTF_IDISHWND; m_ToolTip.hwnd = m_hWndPaint; m_ToolTip.uId = (UINT_PTR) m_hWndPaint; m_ToolTip.hinst = m_hInstance; m_ToolTip.lpszText = const_cast<LPTSTR>( (LPCTSTR) sToolTip ); m_ToolTip.rect = pHover->GetPos(); if( m_hwndTooltip == NULL ) { m_hwndTooltip = ::CreateWindowEx(0, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, m_hWndPaint, NULL, m_hInstance, NULL); ::SendMessage(m_hwndTooltip, TTM_ADDTOOL, 0, (LPARAM) &m_ToolTip); } ::SendMessage(m_hwndTooltip, TTM_SETTOOLINFO, 0, (LPARAM) &m_ToolTip); ::SendMessage(m_hwndTooltip, TTM_TRACKACTIVATE, TRUE, (LPARAM) &m_ToolTip); } return true; case WM_MOUSELEAVE: { if( m_hwndTooltip != NULL ) ::SendMessage(m_hwndTooltip, TTM_TRACKACTIVATE, FALSE, (LPARAM) &m_ToolTip); if( m_bMouseTracking ) ::SendMessage(m_hWndPaint, WM_MOUSEMOVE, 0, (LPARAM) -1); m_bMouseTracking = false; } break; 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 ) 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->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); } } 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). ::SetFocus(m_hWndPaint); POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; m_ptLastMousePos = pt; CControlUI* pControl = FindControl(pt); if( pControl == NULL ) break; if( pControl->GetManager() != this ) break; m_pEventClick = pControl; pControl->SetFocus(); SetCapture(); 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: { ::SetFocus(m_hWndPaint); POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; m_ptLastMousePos = pt; CControlUI* pControl = FindControl(pt); if( pControl == NULL ) break; if( pControl->GetManager() != this ) break; SetCapture(); TEventUI event = { 0 }; event.Type = UIEVENT_DBLCLICK; event.pSender = pControl; event.ptMouse = pt; event.wKeyState = (WORD)wParam; event.dwTimestamp = ::GetTickCount(); pControl->Event(event); m_pEventClick = pControl; } break; 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(); m_pEventClick->Event(event); m_pEventClick = NULL; } break; case WM_RBUTTONDOWN: { ::SetFocus(m_hWndPaint); POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; m_ptLastMousePos = pt; CControlUI* pControl = FindControl(pt); if( pControl == NULL ) break; if( pControl->GetManager() != this ) break; pControl->SetFocus(); SetCapture(); 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); m_pEventClick = pControl; } break; case WM_CONTEXTMENU: { POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; ::ScreenToClient(m_hWndPaint, &pt); m_ptLastMousePos = pt; if( m_pEventClick == NULL ) break; ReleaseCapture(); TEventUI event = { 0 }; event.Type = UIEVENT_CONTEXTMENU; event.pSender = m_pEventClick; event.ptMouse = pt; event.wKeyState = (WORD)wParam; event.lParam = (LPARAM)m_pEventClick; event.dwTimestamp = ::GetTickCount(); m_pEventClick->Event(event); m_pEventClick = NULL; } break; case WM_MOUSEWHEEL: { POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; ::ScreenToClient(m_hWndPaint, &pt); m_ptLastMousePos = pt; CControlUI* pControl = FindControl(pt); if( pControl == NULL ) break; if( pControl->GetManager() != this ) break; int zDelta = (int) (short) HIWORD(wParam); TEventUI event = { 0 }; event.Type = UIEVENT_SCROLLWHEEL; event.pSender = pControl; event.wParam = MAKELPARAM(zDelta < 0 ? SB_LINEDOWN : SB_LINEUP, 0); event.lParam = lParam; event.wKeyState = MapKeyState(); event.dwTimestamp = ::GetTickCount(); pControl->Event(event); // Let's make sure that the scroll item below the cursor is the same as before... ::SendMessage(m_hWndPaint, WM_MOUSEMOVE, 0, (LPARAM) MAKELPARAM(m_ptLastMousePos.x, m_ptLastMousePos.y)); } break; case WM_CHAR: { if( m_pFocus == NULL ) break; TEventUI event = { 0 }; event.Type = UIEVENT_CHAR; event.chKey = (TCHAR)wParam; event.ptMouse = m_ptLastMousePos; event.wKeyState = MapKeyState(); event.dwTimestamp = ::GetTickCount(); m_pFocus->Event(event); } break; case WM_KEYDOWN: { if( m_pFocus == NULL ) break; TEventUI event = { 0 }; event.Type = UIEVENT_KEYDOWN; event.chKey = (TCHAR)wParam; event.ptMouse = m_ptLastMousePos; event.wKeyState = MapKeyState(); event.dwTimestamp = ::GetTickCount(); m_pFocus->Event(event); m_pEventKey = m_pFocus; } break; case WM_KEYUP: { if( m_pEventKey == NULL ) break; TEventUI event = { 0 }; event.Type = UIEVENT_KEYUP; event.chKey = (TCHAR)wParam; event.ptMouse = m_ptLastMousePos; event.wKeyState = MapKeyState(); event.dwTimestamp = ::GetTickCount(); m_pEventKey->Event(event); m_pEventKey = NULL; } break; case WM_SETCURSOR: { if( LOWORD(lParam) != HTCLIENT ) break; if( m_bMouseCapture ) return true; POINT pt = { 0 }; ::GetCursorPos(&pt); ::ScreenToClient(m_hWndPaint, &pt); CControlUI* pControl = FindControl(pt); if( pControl == NULL ) break; if( (pControl->GetControlFlags() & UIFLAG_SETCURSOR) == 0 ) break; TEventUI event = { 0 }; event.Type = UIEVENT_SETCURSOR; event.wParam = wParam; event.lParam = lParam; event.ptMouse = pt; event.wKeyState = MapKeyState(); event.dwTimestamp = ::GetTickCount(); pControl->Event(event); } return true; case WM_NOTIFY: { LPNMHDR lpNMHDR = (LPNMHDR) lParam; if( lpNMHDR != NULL ) lRes = ::SendMessage(lpNMHDR->hwndFrom, OCM__BASE + uMsg, wParam, lParam); return true; } break; case WM_COMMAND: { if( lParam == 0 ) break; HWND hWndChild = (HWND) lParam; lRes = ::SendMessage(hWndChild, OCM__BASE + uMsg, wParam, lParam); return true; } break; case WM_CTLCOLOREDIT: case WM_CTLCOLORSTATIC: { // Refer To: http://msdn.microsoft.com/en-us/library/bb761691(v=vs.85).aspx // Read-only or disabled edit controls do not send the WM_CTLCOLOREDIT message; instead, they send the WM_CTLCOLORSTATIC message. if( lParam == 0 ) break; HWND hWndChild = (HWND) lParam; lRes = ::SendMessage(hWndChild, OCM__BASE + uMsg, wParam, lParam); return true; } break; default: break; } pMsg = NULL; while( pMsg = static_cast<TNotifyUI*>(m_aAsyncNotify.GetAt(0)) ) { m_aAsyncNotify.Remove(0); if( pMsg->pSender != NULL ) { if( pMsg->pSender->OnNotify ) pMsg->pSender->OnNotify(pMsg); } for( int j = 0; j < m_aNotifiers.GetSize(); j++ ) { static_cast<INotifyUI*>(m_aNotifiers[j])->Notify(*pMsg); } delete pMsg; } return false; }.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }
我去這里代碼有點多,先簡單捋一下,首先處理了如下消息:
WM_CTLCOLOREDIT:
WM_CTLCOLORSTATIC
WM_COMMAND:
WM_NOTIFY:
WM_SETCURSOR:
WM_KEYUP
WM_KEYDOWN
WM_CHAR
WM_MOUSEWHEEL
WM_CONTEXTMENU
WM_RBUTTONDOWN
WM_LBUTTONUP
WM_LBUTTONDBLCLK
WM_LBUTTONDOWN
WM_MOUSEMOVE
WM_MOUSELEAVE
WM_MOUSEHOVER
WM_TIMER
WM_SIZE
WM_GETMINMAXINFO
WM_PRINTCLIENT
WM_PAINT
WM_ERASEBKGND
WM_CLOSE
WM_APP
除了這些消息還有一個特別的東西需要留意:
m_aAsyncNotify以后再分析
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }