您的位置:首页 > 其它

WTL: CTabView 源码分析

2014-04-13 21:21 211 查看
分析CTabView的原因,是我想改进现有的 CTabViewImpl, 最终目标是建立一套界面类似VS2010的控件集合。我已经做了一些工作,改进了菜单部分,对于分割窗口也进行了改写,剩下部分就是这个 CTabView了。我已经大致看了下整个 CTabViewImpl的代码,显然这个地方的改动的部分会比较多。现在我们就带着这个目的来分析目标源码。

template <class T, class TBase = ATL::CWindow, class TWinTraits = ATL::CControlWinTraits>
class ATL_NO_VTABLE CTabViewImpl : public ATL::CWindowImpl<T, TBase, TWinTraits>
{
public:
DECLARE_WND_CLASS_EX(NULL, 0, COLOR_APPWORKSPACE)


这个分类的关键成员是: ATL::CContainedWindowT<CTabCtrl> m_tab;

继续向下解释之前,要先弄清楚这个 ATL::CContainedWindowT容器类的作用。CContainedWindowT 是一个模板类,它起到对基类行为进行扩充的目的。

CContainedWindowT<CEdit>, CContainedWindowT<CTabCtrl>, 在主窗口里面这样实现的成员,它的消息会给映射到主窗口类的一个分段。我们看看 CTabViewImpl 的构造函数和消息映射。

CTabViewImpl() :
m_nActivePage(-1),
m_cyTabHeight(0),
m_tab(this, 1),
==============
{
m_ptStartDrag.x = 0;
m_ptStartDrag.y = 0;
}
// Message map and handlers
BEGIN_MSG_MAP(CTabViewImpl)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
MESSAGE_HANDLER(WM_SIZE, OnSize)
MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
MESSAGE_HANDLER(WM_GETFONT, OnGetFont)
MESSAGE_HANDLER(WM_SETFONT, OnSetFont)
NOTIFY_HANDLER(m_nTabID, TCN_SELCHANGE, OnTabChanged)
NOTIFY_ID_HANDLER(m_nTabID, OnTabNotification)
#ifndef _WIN32_WCE
NOTIFY_CODE_HANDLER(TTN_GETDISPINFO, OnTabGetDispInfo)
#endif // !_WIN32_WCE
FORWARD_NOTIFICATIONS()
ALT_MSG_MAP(1)   // tab control
MESSAGE_HANDLER(WM_LBUTTONDOWN, OnTabLButtonDown)
MESSAGE_HANDLER(WM_LBUTTONUP, OnTabLButtonUp)
MESSAGE_HANDLER(WM_CAPTURECHANGED, OnTabCaptureChanged)
MESSAGE_HANDLER(WM_MOUSEMOVE, OnTabMouseMove)
MESSAGE_HANDLER(WM_RBUTTONUP, OnTabRButtonUp)
MESSAGE_HANDLER(WM_SYSKEYDOWN, OnTabSysKeyDown)
END_MSG_MAP()


CContainedWindowT 通过初始化的参数将映射区映射到ATL_MSG_MAP(1), 于是在主窗口里面就可以对子控件进行消息处理了。 CContainedWindowT 对于窗口消息函数的处理类似于CWindowImpl里面所做的。如果你想深究可以自己去研究,否则知道如何使用这个类就可以了。我不想管他是怎么做到的,但我知道它做到了。

通过处理1区这些映射消息,TabView 实现了如下几个功能:

1. 选项卡的拖动移动Tab页。

2. 右键点击TAB时候,或者 按下Shift + F10 可以弹出一个指定的菜单,

关于拖动TAB选项卡,我们来研究一下,它是如何做到的,如何构建拖动图片,等等。

void GenerateDragImage(int nItem)
{
ATLASSERT(IsValidPageIndex(nItem));

#ifndef _WIN32_WCE
RECT rcItem = { 0 };
m_tab.GetItemRect(nItem, &rcItem);
::InflateRect(&rcItem, 2, 2);   // make bigger to cover selected item
#else // CE specific
nItem;   // avoid level 4 warning
RECT rcItem = { 0, 0, 40, 20 };
#endif // _WIN32_WCE

ATLASSERT(m_ilDrag.m_hImageList == NULL);
m_ilDrag.Create(rcItem.right - rcItem.left, rcItem.bottom - rcItem.top, ILC_COLORDDB | ILC_MASK, 1, 1);

CClientDC dc(m_hWnd);
CDC dcMem;
dcMem.CreateCompatibleDC(dc);
ATLASSERT(dcMem.m_hDC != NULL);
dcMem.SetViewportOrg(-rcItem.left, -rcItem.top);

CBitmap bmp;
bmp.CreateCompatibleBitmap(dc, rcItem.right - rcItem.left, rcItem.bottom - rcItem.top);
ATLASSERT(bmp.m_hBitmap != NULL);

HBITMAP hBmpOld = dcMem.SelectBitmap(bmp);
#ifndef _WIN32_WCE
m_tab.SendMessage(WM_PRINTCLIENT, (WPARAM)dcMem.m_hDC);
#else // CE specific
dcMem.Rectangle(&rcItem);
#endif // _WIN32_WCE
dcMem.SelectBitmap(hBmpOld);

ATLVERIFY(m_ilDrag.Add(bmp.m_hBitmap, RGB(255, 0, 255)) != -1);
}


来看看产生拖动图像的这段代码,首先从m_tab获得选项卡区域的大小, CimageList 成员 m_ilDrag 建立。然后创建一个位图,配置好DC, 然后 向 m_tab发送WM_PRINTCLIENT 消息。于是,m_tab将它自身的图像绘制到我们刚才准备好的位图里面。最后,我们把这个位图加入到 CImageList 对象里面。

// Tab control message handlers
LRESULT OnTabLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
{
if(!m_bNoTabDrag && (m_tab.GetItemCount() > 1))
{
m_bTabCapture = true;
m_tab.SetCapture();

m_ptStartDrag.x = GET_X_LPARAM(lParam);
m_ptStartDrag.y = GET_Y_LPARAM(lParam);
}

bHandled = FALSE;
return 0;
}

LRESULT OnTabLButtonUp(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
{
if(m_bTabCapture)
{
if(m_bTabDrag)
{
TCHITTESTINFO hti = { 0 };
hti.pt.x = GET_X_LPARAM(lParam);
hti.pt.y = GET_Y_LPARAM(lParam);
int nItem = m_tab.HitTest(&hti);
if(nItem != -1)
MovePage(m_nActivePage, nItem);
}

::ReleaseCapture();
}

bHandled = FALSE;
return 0;
}

LRESULT OnTabCaptureChanged(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
if(m_bTabCapture)
{
m_bTabCapture = false;

if(m_bTabDrag)
{
m_bTabDrag = false;
T* pT = static_cast<T*>(this);
pT->DrawMoveMark(-1);

#ifndef _WIN32_WCE
m_ilDrag.DragLeave(GetDesktopWindow());
#endif // !_WIN32_WCE
m_ilDrag.EndDrag();

m_ilDrag.Destroy();
m_ilDrag.m_hImageList = NULL;
}
}

bHandled = FALSE;
return 0;
}

LRESULT OnTabMouseMove(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
{
bHandled = FALSE;

if(m_bTabCapture)
{
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };

if(!m_bTabDrag)
{
#ifndef _WIN32_WCE
if(abs(m_ptStartDrag.x - GET_X_LPARAM(lParam)) >= ::GetSystemMetrics(SM_CXDRAG) ||
abs(m_ptStartDrag.y - GET_Y_LPARAM(lParam)) >= ::GetSystemMetrics(SM_CYDRAG))
#else // CE specific
if(abs(m_ptStartDrag.x - GET_X_LPARAM(lParam)) >= 4 ||
abs(m_ptStartDrag.y - GET_Y_LPARAM(lParam)) >= 4)
#endif // _WIN32_WCE
{
T* pT = static_cast<T*>(this);
pT->GenerateDragImage(m_nActivePage);

int cxCursor = ::GetSystemMetrics(SM_CXCURSOR);
int cyCursor = ::GetSystemMetrics(SM_CYCURSOR);
m_ilDrag.BeginDrag(0, -(cxCursor / 2), -(cyCursor / 2));
#ifndef _WIN32_WCE
POINT ptEnter = m_ptStartDrag;
m_tab.ClientToScreen(&ptEnter);
m_ilDrag.DragEnter(GetDesktopWindow(), ptEnter);
#endif // !_WIN32_WCE

m_bTabDrag = true;
}
}

if(m_bTabDrag)
{
TCHITTESTINFO hti = { 0 };
hti.pt = pt;
int nItem = m_tab.HitTest(&hti);

T* pT = static_cast<T*>(this);
pT->SetMoveCursor(nItem != -1);

if(m_nInsertItem != nItem)
pT->DrawMoveMark(nItem);

m_ilDrag.DragShowNolock((nItem != -1) ? TRUE : FALSE);
m_tab.ClientToScreen(&pt);
m_ilDrag.DragMove(pt);

bHandled = TRUE;
}
}

return 0;
}


在 OnTabLButtonDown 中,鼠标的位置成为拖动的起点。


还有一个成员: ATL::CWindow m_wndTitleBar;

因为没有找到提示,我捉摸了挺长时间才明天这个成员的意义。TabView 由于要不停地切换页面, 考虑到主窗口的标题栏的内容可能需要根据这个切换来变化,所以增加了这个成员。void SetTitleBarWindow(HWND hWnd), 和 UpdateTitleBar(), 分别用于指定和更新标题。

// TabView Notifications

#define TBVN_PAGEACTIVATED (0U-741)

#define TBVN_CONTEXTMENU (0U-742)

CTabView定义了两个通知代码, 用户切换了TAB页,或者启用了场景菜单, 会发送这两个通知到父窗口。你可以在哪里进行相应的处理。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: