Duilib 視窗流程

NO IMAGE

從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以後再分析