从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; }