實現類似Excel和Visual C++裡文件夾式樣的標簽控制
在本文的前面第一部分和第二部分中,我們描述了CFolderTabCtrl標簽控制的設計思想、創建過程以及工作原理,通過一個實用程序分析了將CFolderTabCtrl與MFC框架結構融於一體的思路以及關鍵技巧。CFolderTabCtrl的主要目的是仿真Excel和Visual C++應用程序中標簽控制頁的UI功能。在這一部分我們將進一步增強和完善CFolderTabCtrl標簽控制的仿真效果以及可重用性。內容包括創建多個標簽頁、並增加讓標簽頁左右滾動的箭頭按鈕,這兩個按鈕上分別是示意左右的小三角形。
我們將以《VC知識庫在線雜志》第十五期裡一篇關於圖像顯示的文章中所附帶的源代碼作為例子,將CFolderTabCtrl實現的標簽控制功能應用到圖像顯示程序中。原來的程序是一個MFC程序,它通過一個C++類(CPicture)封裝Windows系統提供的IPicture 低級COM接口,使我們能輕松地顯示各種格式圖像文件,包括*.gif、*.jpg、*.bmp和DIBs文件。有關圖像顯示的具體細節不是本文要討論的內容,具體細節請參考文章——“在MFC程序中顯示JPG/GIF圖像”。
首先,我們來看看如何實現箭頭按鈕?我的基本思路是將按鈕作為CFolderTabCtrl的自繪窗口來創建,將標簽放置在按鈕的右邊。如圖一所示:
圖一 程序中有13個標簽頁
為實現這個按鈕,我創建了一個新類,CFolderButton。它個類是一個自繪按鈕類,它有一個DrawItem函數負責繪制按鈕,而不是用位圖按鈕。我選擇用GDI類繪制表示左右的三角形,這樣的話就不用擔心由於縮放而導致的邊緣顯示問題。CFolderButton::DrawItem自己能繪制三角形來適應按鈕的大小。按鈕在置灰狀態時用3D陰影顏色表示,但按鈕被按下時,用象素替換的方法表示按鈕狀態。CFolderButton還處理鼠標消息以實現兩個專用的特性,通常,按鈕時不響應雙擊操作的,但這個按鈕可以處理雙擊鼠標事件,它使得標簽滾動兩頁。也就是說雙擊相當於兩次單擊一樣。下面是實現代碼:
void CFolderButton::OnLButtonDblClk (UINT nFlags, CPoint pt) {
SendMessage(WM_LBUTTONDOWN, nFlags, MAKELONG(pt.x,pt.y));
}
另一個特性是用戶按著按鈕不放,則標簽會一直滾動,直到標簽的端口。實現這個特性的方法是用一個定時器,當定時器被激活時,CFolderButton向它的父窗口發送一個WM_COMMAND消息,就好像按鈕已經被壓下一樣。 void CFolderButton::OnTimer(UINT nIDEvent) {
GetParent()->SendMessage(WM_COMMAND, GetDlgCtrlID());
}
詳細的實現細節請參考本文的源代碼。實際上,CFolderButton::OnTimer的實現是有一個啟動延時的,所以持續滾動特性猶如鍵盤操作一樣:在重復按下之前有輕微的延時。
CFolderButton並不知道有標簽頁以及如何滾動它們。它只知道如何畫出按鈕並響應鼠標行為。當用戶按下按鈕,Windows將WM_COMMAND/BN_CLICKED消息發送到父窗口:也就是CFolderTabCtrl。CFolderTabCtrl才能使標簽頁滾動。CFolderTabCtrl是按鈕和標簽的操縱者,就有點象組合框(ComboBox)操縱其編輯框、下拉按鈕和列表框一樣。
在CFolderTabCtrl中添加滾動按鈕需要對幾個地方進行修改。首先,你必須創建按鈕。在哪裡創建呢?記住!無論你什麼時候創建有子窗口的復合控制,都應該在OnCreate中進行。
// 在 CFolderTabCtrl::OnCreate 中 if (m_dwFtabStyle & FTS_BUTTONS) {
CRect rc;
for (int id=FTBPREV;id<=FTBNEXT;id++) {
VERIFY(m_wndButton[id-1].Create( WS_VISIBLE|WS_CHILD, this, rc, id));
}
m_cxButtons = 2*CXBUTTON;
}
FTS_BUTTONS是CFolderTabCtrl顯示按鈕的新式樣。FTBPREV和FTBNEXT是枚舉類型,其值分別為1和2,用於標示按鈕的IDs。
在Windows系統裡,你只要有子窗口,就必須管理它們的大小。這個工作由CFolderTabCtrl::OnSize專門負責。現在標簽控制中加入了按鈕,你就必須修改CFolderTabCtrl::OnPaint函數,將標簽畫在按鈕的右邊。為此不用修改原來的繪制代碼,只要改一下視圖窗口就可以了:
// x origin = (按鈕的寬度) - (第一個標簽頁的x 坐標);
int xOrigin = m_cxButtons - GetTab(m_iFirstTab)->GetRect().left;
dc.SetViewportOrg(xOrigin,0);
設置完這個視圖窗口後,你不用修改原來的代碼便能正確切換標簽。其中的轉換發生在GDI內部的底層。
最後一個難題是處理按鈕的單擊。這個任務落在了CFolderTabCtrl對WM_COMMAND/BN_CLICKED消息的處理上。下面是關鍵代碼:
BEGIN_MESSAGE_MAP(CFolderTabCtrl, CWnd) …
…
ON_BN_CLICKED(FTBNEXT,OnNextTab) END_MESSAGE_MAP()
void CFolderTabCtrl::OnNextTab() {
if (m_iFirstTab < m_lsTabs.GetCount()-1) {
m_iFirstTab++;
Invalidate();
UpdateButtons();
}
}
CFolderTabCtrl遞增m_iFirstTab並重畫。同時調用UpdateButtons函數更新按鈕的狀態(Enabled)。如果第一個標簽可見,也就是左邊不會再有標簽,UpdateButtons便置灰左邊按鈕;如果最後一個標簽完全可見,也就是右邊不會再有標簽,UpdateButtons便置灰右邊按鈕。其實,標簽除了占據屏幕空間外不做任何事情。實現細節請參考源代碼。(完)