Ch 21 通用控制項(1):標籤控制項


什麼是通用控制項 ( Common Controls )?

在 Windows 1.0 的時代,系統中就已經使用按鈕、編輯框、複合框等控制項 ( 也稱為使用者控制項 ),來加強使用者界面,讓電腦的使用不再是冷冰冰的機器,增進了不少親和力。當然進入 Windows 95 年時,這些使用者控制項仍保留繼續使用,在第 8∼12 章,小木偶已簡單介紹了在 Win 32 系統中的使用者控制項。

除了這些使用較頻繁的使用者控制項之外,還有另外一類是在 Windows 95 之後,才加入的控制項,稱為通用控制項,包含 TreeView、ListView、Tab、Toolbar、StatusBar、TabStrip、Slider 等控制項。處理這些控制項的 API 都包含在 COMCTL32.DLL 堙A其版本是 4.00 版,隨著 Windows 95/NT 4.0 原版光碟販售。隨時間流逝,每發佈新版本的 IE 或新版本的 Windows ,COMCTL32.DLL 也隨之新增,每次增加新的通用控制項,也都會考慮使舊的控制項相容。經過歷年來逐漸增加,到 Windows XP 時已經增為約 23 個通用控制項了 ( 參考賴榮樞先生的 Windows XP 程式設計概論 ),版本為 6.00 版。即使這些通用控制項之間的外觀與功用相差十萬八千里,但從程式設計師的眼光看來,通用控制項和使用者控制項一樣,都是一種子視窗,只是不同的控制項有一些特殊的風格 ( style )、訊息 ( message ) 與通知碼 ( notification )。底下是一些常見的通用控制項:



載入通用控制項

如前所述,處理與通用控制項有關的 API 是放在「C:\WINDOWS\SYSTEM32\COMCTL32.DLL」堙A由於通用控制項的數量太多,假如系統一啟動,就把所有的通用控制項載入並註冊,是非常浪費記憶體的,所以微軟並沒有這樣做。這意味著,要使用它們之前,必須先確定是否已經載入記憶體。程式可以呼叫 InitCommonControls 或 InitCommonControlsEx,它們的原型是:

void InitCommonControls(VOID);

BOOL InitCommonControlsEx(
    const  LPINITCOMMONCONTROLSEX lpInitCtrls
);

InitCommonControls 沒有參數,因此只要執行了 InitCommonControls 就會把所有的通用控制項載入。InitCommonControlsEx 有一個參數,此參數是一個位址指標,指向稱為 INITCOMMONCONTROLSEX 結構體的位址,成員如下:

INITCOMMONCONTROLSEX    STRUC
dwSize                  DWORD   ?
dwICC                   DWORD   ?
INITCOMMONCONTROLSEX    ENDS

INITCOMMONCONTROLSEX 的第一個參數,dwSize 是此結構體大小,以位元組為單位,必須在呼叫 InitCommonControlsEx API 之前就填入適當的大小,系統藉此分辨版本。dwICC 是指要初始化的通用控制項,可以是下表中,最右邊的那一欄:

控制項說明 視窗類別風格dwICC 值
動畫控制項
animate control
通常用於拷貝檔案時的動畫,如上圖左側下的視窗中,以紅色框起來的控制項就是動畫控制項 SysAnimate32ACS_ICC_ANIMATE_CLASS
日期時間控制項
date and time picker
用於挑選日期,如上圖右側上視窗中,以天空色框住的控制項 SysDateTimePick32DTS_ICC_DATE_CLASSES
標題欄
header control
常用在「清單檢視」控制項堛獐陏D,因此也常常與其同時載入,如上圖左側中的視窗堙A以淡綠色框住的控制項 SysHeader32HDS_ICC_LISTVIEW_CLASSES
快捷鍵
hot key control
msctls_hotkey32 ICC_HOTKEY_CLASS
IP 控制項
IP address control
類似編輯框,但其內有三個小數點分隔成四欄,作為輸入 IP 位址之用 SysIPAddress32 ICC_INTERNET_CLASSES
超連結控制項
hyperlink control
如同網頁上的超連結,可供使用者按下後連接到一個網頁上。如右側下圖,以粉紅色框住的控制項 HyperlinkDemoICC_LINK_CLASS
清單檢視
list-view control
通常在一矩形區域有數個項目,可能是圖示或文字,也可能兩者皆有,可供使用者點選。如上圖左側中的視窗堙A以藍色框起來的就是 list-view 控制項 SysListView32LVS_ICC_LISTVIEW_CLASSES

rebar control
通常 rebar 控制項是可以包含其他控制項的控制項,並且可以讓使用者按住滑鼠移動其位置,如上圖左側上的視窗,用淡綠色框框圍起來的控制項 ReBarWindow32RBS_ICC_COOL_CLASSES
樹狀檢視
tree-view control
通常用來檢視磁碟目錄、登錄檔機碼,如上圖左側中視窗堙A以天空色框住的控制項就是 tree-view 控制項 SysTreeView32TVS_ICC_TREEVIEW_CLASSES

native font control
這是一個看不見的控制項,它能在背地堣u作,讓對話盒的控制項以現在的語系顯示文字 NativeFontCtlNFS_ICC_NATIVEFNTCTL_CLASS

pager control
通常工具列太窄時,在其兩側的箭號,有點像捲軸,可供使用者捲動工具列。如上圖左側上的視窗,以紅框圍住的就是 pager 控制項 SysPagerPGS_ICC_PAGESCROLLER_CLASS
進度列
progress bar
通常用在需要電腦花費很多時間處理的事物時,顯示處理進度的控制項,如上圖左側下的視窗堨恲臟漅堸_來的控制項 msctls_progress32PBS_ICC_PROGRESS_CLASS
狀態列
status bar
通常在視窗的最底下,顯示使用狀態。如上圖左側中的視窗,以粉紅色框住的就是狀態列 msctls_statusbar32SBARS_ICC_BAR_CLASSES
標籤
tab control
通常在對話盒上方,有好幾頁可供使用者選擇,如上圖右側上邊的視窗堙A其中「日期時間」、「時區」、「網際網路時間」就是標籤控制項。 SysTabControl32TCS_ICC_TAB_CLASSES
工具列
toolbar control
通常在選單下方,功能類似選單,供使用者快速選擇某些功能,如上圖左側上的視窗堙A用粉紅色框住的就是工具列 ToolbarWindow32TBSTYLE_ICC_BAR_CLASSES
工具提示
tooltip control
滑鼠停留在某處一段時間出現的子視窗,如上圖左側中的視窗堙A「最小化」就是工具提示 Tooltips_class32TTS_ICC_TAB_CLASSES 或
ICC_BAR_CLASSES 或
ICC_TREEVIEW_CLASSES
滑動軸控制項
trackbar control
類似捲軸,可以供使用者用在操縱桿上,按住滑鼠左鍵不放,拖動它,如上圖右側下的視窗所示,用紅色框線圍住的就是 msctls_trackbar32TBS_ICC_BAR_CLASSES
上下控制項
up-down control
如上圖右側上的視窗堙A用粉紅色框住的兩個上下箭頭就是「up-down」控制項,可以讓使用者增加或減少特定數值 msctls_updown32UDS_ICC_UPDOWN_CLASS

ComboBoxEx
比複合框多了對「圖像清單」( image list ) 的支援 擴展複合框
ComboBoxEx32
CBS_ICC_USEREX_CLASSES

另外,dwICC 也可以是 ICC_STANDARD_CLASSES、ICC_WIN95_CLASSES 兩種,前者是載入固有的使用者控制項,包含按鈕、編輯框、靜態控件、清單控件、複合框、捲軸控件,後者是載入動畫、標題欄、快捷鍵、清單檢視、進度列、狀態列、標籤、工具提示、工具列、trackbar、樹狀檢視和上下控制項。

InitCommonControlsEx 具有累積性,意思是如果第一次 dwICC 參數是 ICC_ANIMATE_CLASS 呼叫 InitCommonControlsEx,第二次以 ICC_PROGRESS_CLASS 呼叫,最後系統會載入動畫控制項與進度列控制項。另外上表中,有些 dwICC 參數值會載入兩個或兩個以上的通用控制項,例如 ICC_TAB_CLASSES 會載入「標籤」與「工具提示」兩種控制項。

Rebar controls、日期時間控制項、IP 控制項、Pager Controls、擴展複合框這五個通用控制項,必須由 InitCommonControlsEx 載入,而無法由 InitCommonControls 載入。

WM_NOTIFY 訊息

一般的使用者控制項,如按鈕、編輯框、複合框等會發出 WM_COMMAND 訊息,並將此訊息傳給父視窗的視窗函式,以使程式處理這些訊息。但是通用控制項所發出來的訊息與資料太多了,WM_COMMAND 無法完全容納,所以改發出 WM_NOTIFY 訊息。WM_NOTIFY 訊息中的 wParam 是發出 WM_NOTIFY 的通用控制項識別碼,不過識別碼可以重複,因此最好還是要檢查發出 WM_NOTIFY 的控制項代碼才好。lParam 是一個指標,指向 NMHDR 結構體,它的內容是:

NMHDR       STRUC
hwndFrom    HWND    ?
idFrom      DWORD   ?
code        DWORD   ?
NMHDR       ENDS

NMHDR 成員中的 hwndFrom、idFrom 分別是指發出 WM_NOTIFY 的控制項代碼及識別碼,code 是該控制項因應使用者動作所發出的通知碼 ( notification )。不同的通用控制項,可能有不同的通知碼,但是底下的八種通知碼是每一種通用控制項都會發出的:

通知碼說明
NM_CLICK使用者在通用控制項按下鬆開滑鼠左鍵
NM_DBLCLK使用者在通用控制項連續兩次按下鬆開滑鼠左鍵
NM_RCLICK使用者在通用控制項按下鬆開滑鼠右鍵
NM_RDBLCLK使用者在通用控制項連續兩次按下鬆開滑鼠右鍵
NM_RETURN使用者在通用控制項擁有輸入焦點時,按下鍵盤上的「Enter」鍵
NM_SETFOCUS通用控制項獲得輸入焦點
NM_KILLFOCUS通用控制項失去輸入焦點
NM_OUTOFMEMORY沒有足夠的記憶體操作通用控制項

這八個通知碼處理完畢,返回時,若返回值不為零,則系統不會再以內定方式處理;若返回值為零,系統會以內定方式再行處理一次。上面只是簡單說明 WM_NOTIFY 訊息,實際上不同的通用控制項,發送 WM_NOTIFY 訊息給父視窗時,lParam 所指的結構體是不同的,但是該結構體的第一個成員必定是 NMHDR 結構體。因此上面才說 WM_NOTIFY 訊息的 lParam 是指向 NMHDR 結構體。例如在「清單檢視」控制項內點選一個項目時,發出的 WM_NOTIFY 訊息中的 lParam 所指的結構體為 NMLISTVIEW,內容為:

NMLISTVIEW      STRUCT
hdr             NMHDR   <?>
iItem           DWORD   ?
iSubItem        DWORD   ?
uNewState       DWORD   ?
uOldState       DWORD   ?
uChanged        DWORD   ?
ptAction        POINT   <?>
lParam          LPARAM  ?
NMLISTVIEW      ENDS

NMLISTVIEW 結構體的第一個成員是 NMHDR 結構體。亦即 WM_NOTIFY 訊息中的 lParam 參數所指位址的內容為 NMHDR 結構體,而 NMHDR 結構體也可能是另一個更大更複雜的結構體中的第一個成員。

WM_NOTIFY 的返回值並不一定,與通知碼有關,大部分的情形,會被系統忽略。如果 WM_NOTIFY 是發送到對話盒的視窗函式,那麼返回值必須呼叫 SetWindowLong API 設定,原型為:

SetWindowLong   PROTO   hDlg,DWL_MSGRESULT,dwReturnValue

hDlg 是對話盒代碼,dwRetureValue 為返回值。底下,小木偶要介紹的第一個通用控制項是標籤控制項 ( Tab Control )。


標籤控制項 ( Tab Control )

標籤控制項各部名稱

下圖是 Windows XP 調整日期時間的對話盒,此對話盒為可稱為「主對話盒」,它是一個模式對話盒,一般以 DialogBoxParam 建立。其內含有一個標籤控制項以及三個按鈕 ( 確定、取消、套用 )。標籤控制項的上會有好幾個選項 ( 圖中以紅框圍住 ),稱之為「項目」( item,也叫標籤,tab,不過本文都叫它為項目 ),可以供使用者選擇,每個項目以一個數值表示,此數值稱為索引,由 0 開始,每個項目上有項目文字 ( label ) 或圖示。此外標籤控制項還包含一個顯示區域 ( diaplay area,圖中以藍框圍住 )。使用者選不同的項目,會看到顯示區域有不同的畫面。以 Windows XP 的「日期時間內容」來說明,它的項目有「日期時間」、「時區」、「網際網路時間」三項。「日期時間」項目則有「日期」、「時間」按鈕、「一月」複合框、「2012」上下控制項等等。

如上面所說,在標籤控制項堙A每一個項目都有不同的畫面,上面又各自有不同的控制項,這是如何辦到的呢?原來每一個項目在顯示區域媗膆靰漕銋窸ㄛO一個沒有標題欄的非模式對話盒,如果有三個項目,就得準備三個對話盒。而這三個對話盒都必須包含 WS_CHILD 風格,當呼叫 CreateDialogParam 三次,分別建立起這三個對話盒時,均指定父視窗為標籤控制項,同時也指定各對話盒的視窗函式。這樣一來,我們就可以利用各對話盒的視窗函式處理各對話盒的訊息,同時這三個對話盒也是標籤控制項的子視窗。

建立標籤控制項

建立標籤控制項的方法跟其他控制項並沒有什麼不同,不外乎是在資源描述檔中的對話盒面板堜w義或者是在程式中呼叫 CreateWindowEx 建立,不論是哪一種方法,所使用的視窗類別都是「SysTabControl32」。建立標籤控制項可以使用下面的風格:

風格說明
TCS_BOTTOM 項目在顯示區域的下方,COMCTL32.DLL 必須在 4.70 版本及其以後以上才有此風格。( 註一 )
TCS_BUTTONS項目會以按鈕的外觀顯示,且顯示區域沒有框線
TCS_FIXEDWIDTH所有的項目寬度相同
TCS_FLATBUTTONS項目會以平的按鈕外觀顯示,須搭配 TCS_BUTTONS 使用,在 COMCTL32.DLL 4.71 及其以後版本方有此風格
TCS_FOCUSNEVER使用者選按時,標籤不接收輸入焦點
TCS_FOCUSONBUTTONDOWN使用者選按時,標籤接收輸入焦點
TCS_FORCEICONLEFT項目圖示靠左對齊,須搭配 TCS_FIXEDWIDTH 使用
TCS_FORCELABELLEFT項目文字靠左對齊,須搭配 TCS_FIXEDWIDTH 使用,並且暗示 TCS_FORCEICONLEFT
TCS_HOTTRACK當滑鼠移到項目上時,項目會以高亮度顯示,須在 4.70 版的 COMMCTL.DLL 才有此風格。
TCS_MULTILINE
TCS_MULTISELECT
TCS_OWNERDRAWFIXED擁有者自行繪製標籤
TCS_RAGGEDRIGHT
TCS_RIGHTCOMCTL32.DLL 4.70 版才有此風格,須搭配 TCS_VERTICAL 風格使用。TCS_RIGHT 會使標籤控制項的項目垂直顯示在顯示區域的右側
TCS_RIGHTJUSTIFY
TCS_SCROLLOPPOSITE
TCS_SINGLELINE項目僅一列排列,內定的風格
TCS_TABS顯示項目、框線,內定的風格
TCS_TOOLTIPS標籤控制項具有工具提示
TCS_VERTICAL項目顯示在顯示區域左邊,COMCTL32.DLL 須 4.70 版或以後的版本

在標籤控制項增添項目

新建立好的標籤控制項並沒有項目,需要對標籤控制項發出 TCM_INSERTITEM 訊息增添才行,方法是呼叫 SendMessage,如下:

INVOKE  SendMessage,hTabCtrl,TCM_INSERTITEM,wParam,lParam

事實上程式可以對標籤控制項發出許多訊息,除了增添項目外,還可對標籤控制項做不同的動作,例如取得項目個數、刪除某些項目、設定工具提示……等等。不管哪一種訊息,hTabCtrl 參數是標籤控制項的代碼;但是不同的訊息,wParam 與 lParam 參數所表示的意義也不同。對 TCM_INSERTITEM 訊息而言,wParam 是項目索引,由零開始,表示插入項目後,該項目的索引值;lParam 是指向 TCITEM 結構體 ( 也稱為 TC_ITEM ) 的指標,結構體中指定了項目的文字或圖示。TCITEM 結構體的成員是:

TCITEM  STRUC
  mask          DWORD   ?
  dwState       DWORD   ?
  dwStateMask   DWORD   ?
  pszText       LPTSTR  ?   
  cchTextMax    DWORD   ?   
  iImage        DWORD   ?   
  lParam        LPARAM  ? 
TCITEM  ENDS

成員中的 mask 是設定這次所要發出的訊息中哪些欄位是可用的,可以有下面幾種選擇:

欄位說明
TCIF_IMAGEiImage 須填上在 ImageList 的圖片索引
TCIF_PARAMlParam 須填上訊息。當項目加入標籤時,會對程式發出此訊息
TCIF_RTLREADING文字由右至左,只有希伯來與阿拉伯語系才能用
TCIF_STATEdwState 須填上標籤控制項的狀態,不過對標籤發送 TCM_INSERTITEM 時,系統會忽略 dwState
TCIF_TEXTpszText 須填上一個結尾為零的字串指標,這個字串會顯示在項目上

顯示區域與其內的對話盒

為了讓標籤內每個項目的顯示區域有不同的畫面,一般做法是在每個項目內的顯示區域顯示不同的非模式對話盒,此非模式對話盒是沒有標題的,而其大小恰好是顯示區域的大小。問題是顯示區域的大小如何取得呢?我們知道控制項其實也是個視窗,我們可以呼叫 GetWindowRect 取得視窗的大小,GetWindowRect 原型是:

GetWindowRect(
    HWND    hWnd,   // handle of window
    LPRECT  lpRect  // address of structure for window coordinates
);

其中 hWnd 是視窗代碼,lpRect 是指向 RECT 結構體位址的指標,傳回來的 RECT 是相對於螢幕座標,亦即以螢幕左上角為座標原點。若 hWnd 參數為標籤控制項的代碼,呼叫 GetWindowRect 後所傳回來的矩形是整個標籤控制項的大小,包含了項目及框線的大小,並且相對於螢幕左上角位置。這時,程式必須再以 TCM_ADJUSTRECT 為參數,呼叫 SendMessage,其程式碼為

INVOKE  SendMessage,hTabCtrl,TCM_ADJUSTRECT,fLarger,lpRect

上面的 lParam 參數,lpRect 是指向 RECT 結構體位址的指標,亦即呼叫 GetWindowRect 後的 RECT 位址;wParam 參數,fLarger 是指如何調整 RECT,有兩種選擇:FALSE 或 TRUE。假如 fLarger 為 FALSE,系統會把 lpRect 所指的矩形扣除項目及框線的大小,這樣就可以得到以螢幕左上角為原點的顯示區域的矩形大小。如果 fLarger 為 TRUE,系統則會加上項目及框線的大小,這種情形不常用也叫不符合我們的需求。有了顯示區域相對於螢幕左上角的位置,只要再呼叫 MoveWindow 就可以移動對話盒到適當位置。

常用的標籤控制項訊息

對標籤控制項發出的訊息,除了 TCM_INSERTITEM 和 TCM_ADJUSTRECT 之外,還有很多其他訊息,底下介紹幾種常用的:

訊息wParam lParam說明
TCM_ADJUSTRECTfLarger lpRect調整矩形大小,使其適合顯示區域。lpRcet 是指向 RECT 結構體位址,fLarger 是指如何調整
TCM_DELETEALLITEMS0 0刪除標籤控制項的所有項目,wParam 與 lParam 必須均為 0
TCM_DELETEITEMiItem 0刪除某個項目,iItem 是要刪除項目的索引值。假如刪除的項目之後還有項目,那麼在被刪除的項目後的每個項目的索引值會減一。
TCM_GETCURSEL0 0取得現在被選擇的項目。若呼叫成功,返回值為項目的索引值;若失敗傳回 -1
TCM_GETIMAGELIST0 0取得與標籤連結的圖片清單代碼,如果呼叫成功,EAX 返回 ImageList 代碼;否則 EAX 為 NULL
TCM_GETITEMiItem pitem取得標籤控制項的項目資料,iItem 是項目的索引值,pitem 是指向 TCITEM 結構體位址的指標。在呼叫 SendMessage 前,須指定 TCITEM 成員中的 mask 欄位及相關資料,以指定要取得哪一項資料。例如要取得項目的標籤文字,mask 要填入 TCIF_TEXT,pszText 填入字串存放位址,cchTextMax 填入獲得幾個字元,這樣返回後 pszText 就會有標籤文字了
TCM_GETITEMCOUNT0 0取得標籤的項目總數
TCM_GETITEMRECTiItem lpRect取得項目大小,iItem 是要取得大小的項目索引,lpRect 是指向 RECT 結構體位址的指標,返回後此 RECT 結構體會存有項目矩形,此矩形以標籤控制項的左上角為原點
TCM_INSERTITEMiItem pitem插入一個項目,iItem 是要插入的項目索引,pitem 是指向 TCITEM 結構體位址的指標
TCM_REMOVEIMAGEiImage 0從標籤控制項的 ImageList 中刪除一個位元圖或圖示,iImage 是在標籤控制項的 ImageList 中,要刪除的位元圖或圖示索引值
TCM_SETCURSELiItem 0設定被選擇的項目。若呼叫成功,返回值為前一個被選擇項目的索引值;若失敗傳回 -1
TCM_SETIMAGELIST0 himl設定標籤控制項的 ImageList,himl 就是 ImageList 代碼
TCM_SETITEMiItem pitem設定籤控制項的項目資料,iItem 是要設定的項目索引,pitem 是指向 TCITEM 結構體位址的指標,使用方法跟 TCM_GETITEM 差不多
TCM_SETITEMSIZE0 cy_cx在具有 TCS_OWNERDRAWFIXED 或 TCS_FIXEDWIDTH 風格的標籤設定寬度與高度,lParam 中的低字組為寬度,高字組為高度
TCM_SETPADDING0 cy_cx在項目四周設定空白區域,lParam 中的低字組為空白寬度,高字組為空白高度

還有一些可以發送給標籤控制項的訊息並沒有列在上表,請自行參考 MSDN。

標籤控制項常用的通知碼

當使用者對標籤控制項做某些動作,例如使用者選擇另一個項目、使用者在項目上按一次滑鼠左鍵、使用者在項目上連按兩次滑鼠左鍵等等,標籤控制項都會發出 WM_NOTIFY 訊息給父視窗。而 WM_NOTIFY 訊息中,lParam 參數為一指標,所指位址為某個結構體,此結構體的第一個成員是 NMHDR,NMHDR 成員中的 code 為通知碼 ( notifications )。通知碼記錄著使用者對標籤控制項所做的動作,視窗函式應該對某些通知碼做適當的回應。常見的通知碼如下:

通知碼說明
TCN_FOCUSCHANGE具有 TCS_BUTTONS 風格的標籤控制項輸入焦點改變了
TCN_KEYDOWN鍵盤上的按鍵被壓下,此時 WM_NOTIFY 的 lParam 為一個指標,指向 NMTCKEYDOWN 結構體,此結構體成員為
NMTCKEYDOWN     STRUCT
hdr             NMHDR   <?>
wVKey           WORD    ?
flags           UINT    ?
NMTCKEYDOWN     ENDS
NMTCKEYDOWN 結構體的第一個成員就是 NMHDR 結構體,在這之後再添加兩個成員,一個是虛擬鍵碼,亦即使用者按下的按鍵;另一個是 flags 參數,其意義與 WM_KEYDOWN 的 lParam 相同。
TCN_SELCHANGE通知父視窗,被選定的項目已經改變了,亦即當使用者選擇不同項目後,系統完全備妥資料,更新好畫面之後,才會發出 TCN_SELCHANGE。
TCN_SELCHANGING通知父視窗,被選定的項目即將改變了。

圖片清單 ( ImageList )

ImageList 可翻成「圖片清單」或「影像清單」,它是一個儲存大小相同、顏色深度相同的點陣圖或圖示的組件,每個圖片都有不同的「索引值」,我們以索引值區別這些屬性相同的圖片。您也可以把圖片清單想像成一個專門存放圖片的「容器」,在這個「容器」堶悸犒洃糷j小與顏色數都相同。某些控制項可以顯示許多圖片,例如標籤控制項的每個項目上可以顯示不同的圖示,其他還有 list-view、tree-view 等控制項也可以。這些控制項都可以指定某個圖片清單,或者說可以指定圖片清單與這些控制項連結在一起。連結之後,當這些控制項要顯示圖片時,可以在圖片清單中,以索引值挑選,就能夠很容易的顯示圖片。我們通常呼叫 ImageList_Create 建立新的圖片清單:

HIMAGELIST ImageList_Create (
    int     cx,
    int     cy,
    UINT    flags,
    int     cInitial,
    int     cGrow
);

如果呼叫成功,系統會傳回圖片清單代碼,以後對這個新建立的圖片清單進行刪除或新增某個圖片,都是以這個代碼進行操作,與檔案代碼的觀念相同;若呼叫失敗,傳回 NULL。cx、cy 是圖片的寬度與高度,flags 是圖片屬性,可以是 ILC_COLOR4、ILC_COLOR8、ILC_COLOR16、ILC_COLOR24、ILC_COLOR32,分別表示 16 色、256 色、65536 色、224 色、232 色。flags 也可以是 ILC_COLOR,表示以內定的顏色建立圖片清單,一般而言內定的顏色是 ILC_COLOR4,但有些舊型顯示器是 ILC_COLORDDB。ILC_COLOR 也可以是 ILC_COLORDDB,表示建立的圖片清單存放與裝置相關 ( device-dependent bitmap ) 的點陣圖。ILC_COLOR 也可以是 ILC_MASK,表示圖片清單除了把程式設計師加入的圖片存入之外,還會在圖片清單中,為每張圖片建立遮罩圖,其實就是一張黑白位元圖,顯示的時候用於選擇性透過這張位元圖的顏色,白色區域顯示,黑色區域不顯示。在 COMCTL32.DLL 6.0 版或以後的版本,flags 也可以是 ILC_MIRROR,表示圖片會左右互換,以適用於希伯來與阿拉伯語系。cInitial 是表示在建立圖片清單時,預留的圖片數。cGrow 是指如果要增加圖片時,每次增加的張數。

要在圖片清單中加入位元圖,可以呼叫 ImageList_Add,其原型為:

int ImageList_Add (
    HIMAGELIST himl,
    HBITMAP hbmImage,
    HBITMAP hbmMask
);

himl 是圖片清單代碼,表示要把位元圖添加在哪個圖片清單中。hbmImage 和 hbmMask 分別是位元圖代碼與遮罩圖代碼,如果建立檔案清單時,沒有設定 ILC_MASK,hbmMask 設為 NULL。如果成功地添加圖片,傳回位元圖的索引值,如果失敗傳回 0。把位元圖加入到圖片清單之後,系統就已經在圖片清單堳貝一份位元圖,所以原來的位元圖如果不再需要,就可以用 DeleteObject API 刪除,不會影響在圖片清單堛漲鴗號洁C

如果要在圖片清單中添加圖示,不能呼叫 ImageList_Add,而必須呼叫 ImageList_ReplaceIcon。ImageList_ReplaceIcon 的原型如下:

int ImageList_ReplaceIcon ( 
    HIMAGELIST himl,
    int i,
    HICON hicon
);

ImageList_ReplaceIcon 可以把 himl 圖片清單中的第 i 個索引值的圖片取代成為 hicon 所代表的圖示。如果是一個剛建立好的圖片清單,那麼堶探N會連一張圖片也沒有,如果這時候以索引值為 -1,呼叫 ImageList_ReplaceIcon,那麼 ImageList_ReplaceIcon 會在 himl 圖片清單中添加 hicon 所代表的圖示或游標。ImageList_ReplaceIcon 如果成功地返回,返回值是索引值,若失敗,返回值是 0。

在標籤控制項中按 Tab 鍵

眾所皆知,在對話盒中,使用者可以按下 Tab 鍵,使對話盒中的「按鍵」、「編輯框」……等具有 WS_TABSTOP 風格的控制項輪流獲得鍵盤輸入焦點。但是在標籤控制項堛瘍膆黹炾魽A是一個個含有許多控制項的對話盒,這些對話盒均為標籤控制項的子視窗。也就是說,在對話盒堛滷惆貕腋雃釆陓珣惆貕答滿u孫」視窗了。也就是說,一般風格無法使標籤內對話盒的子控制項,以連續按 TAB 鍵的方式獲得輸入焦點。

那該怎麼辦呢?可以藉助 WS_EX_CONTROLPARENT 延伸風格。具有 WS_EX_CONTROLPARENT 延伸風格的視窗,能在使用者按下 TAB 鍵時,使其子視窗也能輪流獲得輸入焦點。


範例

小木偶寫了這麼多,讀者也看了這麼多,來個總整理也不錯!您若常看電影,可能會遇上字幕和影像無法對上,這時就必須調整字幕檔的時間了。假設有一個程式,它可以調整電影字幕檔的時間,或者將兩個字幕檔合併 ( 如果您想看中英文對照 ),執行畫面如下:

在主對話盒中有一個標籤控制項,而這個標籤控制項具有三個項目,「時間調整」、「字幕檔合併」、「說明」。這三個項目分別有三個無標題的非模式對話盒恰好填滿其顯示區域。那我們先來看看資源描述檔中的對話盒面板應該怎麼定義:

Subrip           DIALOG  60,20,560,350
STYLE            WS_POPUP|WS_VISIBLE|WS_CAPTION|WS_SYSMENU
CAPTION          "Subrip 字幕工具"
FONT             8,"MS Sans Serif"
{
 CONTROL         "",    IDC_TAB, "SysTabControl32",WS_CHILD|TCS_TABS,5,5,537,313,WS_EX_CONTROLPARENT
 PUSHBUTTON      "離開",IDC_EXIT,437,324,100, 20
}

TC_AdjTime       DIALOG  0,0,100,200
EXSTYLE          WS_EX_CONTROLPARENT
STYLE            WS_CHILD
FONT             8,"MS Sans Serif"
{
 此處填入第一個項目中的控制項
}

TC_Merge        DIALOG  0,0,100,200
EXSTYLE         WS_EX_CONTROLPARENT
STYLE           WS_CHILD
FONT            8,"MS Sans Serif"
{
 此處填入第二個項目中的控制項
}

TC_Help         DIALOG  0,0,100,200
STYLE           WS_CHILD
FONT            8,"MS Sans Serif"
{
 此處填入第三個項目中的控制項
}

Subrip           ICON            SRT.ICO
Merge            ICON            M.ICO
Adjust           ICON            A.ICO
Help             ICON            H.ICO

小木偶把主對話盒命名為「Subrip」,主對話盒埵釣潃荓惆貕窗A分別是一個標籤控制項與一個按鈕。標籤控制項不須標題,所以以「""」表示,此外它還具有 WS_EX_CONTROLPARENT 延伸風格,以便使其子視窗也在使用者按下 TAB 鍵時獲得輸入焦點。

此標籤控制項有三個項目,亦即有三個顯示區域,分別由三個非模式對話盒填滿,分別命名為「TC_AdjTime」、「TC_Merge」、「TC_Help」。這三個非模式對話盒的位置、大小在建立後會由程式修改到恰好填滿標籤的顯示區域,因此在對話盒面板中的數值是隨便亂寫的,並不會影響結果。另外,請注意,「TC_AdjTime」、「TC_Merge」這兩個標籤控制項的子視窗都具有「WS_EX_CONTROLPARENT」,而「TC_Help」則不具此延伸風格,這是因為後者僅含一個靜態控件,根本不須輸入焦點。底下看看部份原始碼:

                OPTION  CASEMAP:NONE
                .586
                .MODEL  FLAT,STDCALL

INCLUDE         WINDOWS.INC
INCLUDE         COMCTL32.INC
INCLUDELIB      COMCTL32.LIB
;*******************************************************************************
.CONST
szDlgTemplate   BYTE    'Subrip',0
szTabAdjTime    BYTE    '時間調整',0
szTabMerge      BYTE    '字幕檔合併',0
szTabHelp       BYTE    '說明',0
szAdjTimeTemp   BYTE    'TC_AdjTime',0  ;「時間調整」對話盒面板名稱
szMergeTemp     BYTE    'TC_Merge',0    ;「字幕檔合併」對話盒面板名稱
szHelpTemp      BYTE    'TC_Help',0     ;「說明」對話盒面板名稱
szAdjIcon       BYTE    'Adjust',0
szMergeIcon     BYTE    'Merge',0
szHelpIcon      BYTE    'Help',0
;*******************************************************************************
.DATA
hInstance       HINSTANCE               ?
hTabCtrl        HANDLE                  ?
hImgLst         HANDLE                  ?
iccex           INITCOMMONCONTROLSEX    <?>
tci             TCITEM                  <?>
rcTab           RECT                    <?>
;*******************************************************************************
.CODE
;-------------------------------------------------------------------------------
    ;其他副程式
;-------------------------------------------------------------------------------
AdjTimeProc     PROC    hDlg:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
    ;「時間調整」對話盒的視窗函式
AdjTimeProc     ENDP
;-------------------------------------------------------------------------------
MergeProc       PROC    hDlg:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
    ;「字幕檔合併」對話盒的視窗函式
MergeProc       ENDP
;-------------------------------------------------------------------------------
HelpProc        PROC    hDlg:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
    ;「說明」對話盒的視窗函式
HelpProc        ENDP
;-------------------------------------------------------------------------------
DlgProc         PROC    hDlg:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
                LOCAL   rcMainDlg:RECT                  ;主對話盒原來位置
                LOCAL   ptTabDisp:POINT                 ;標籤顯示區域的左上角位置
                LOCAL   cyBtnExit:DWORD                 ;「離開」按鈕的高度
                LOCAL   rcTabItem,rcBtnExit:RECT        ;標籤、離開按鈕的位置
                LOCAL   cxTabDisp,cyTabDisp:DWORD       ;標籤顯示區域的寬度、高度
                LOCAL   cxMainDlg,cyMainDlg:DWORD       ;主對話盒的寬度、高度
.IF uMsg==WM_COMMAND

.ELSEIF uMsg==WM_INITDIALOG
            ;建立ImageList
                INVOKE  GetSystemMetrics,SM_CXSMICON    ;取得圖示大小
                INVOKE  ImageList_Create,eax,eax,ILC_COLOR32 or ILC_MASK,3,0
                mov     hImgLst,eax
            ;把三個圖示加入到圖片清單
                INVOKE  LoadIcon,hInstance,OFFSET szAdjIcon
                INVOKE  ImageList_ReplaceIcon,hImgLst,-1,eax
                INVOKE  LoadIcon,hInstance,OFFSET szMergeIcon
                INVOKE  ImageList_ReplaceIcon,hImgLst,-1,eax
                INVOKE  LoadIcon,hInstance,OFFSET szHelpIcon
                INVOKE  ImageList_ReplaceIcon,hImgLst,-1,eax
            ;設定標籤的ImageList
                INVOKE  GetDlgItem,hDlg,IDC_TAB
                mov     hTabCtrl,eax
                INVOKE  SendMessage,eax,TCM_SETIMAGELIST,0,hImgLst
            ;取得「主」對話盒工作區大小
                INVOKE  GetClientRect,hDlg,OFFSET rcDlgClient
            ;取得「離開」按鈕大小
                INVOKE  GetDlgItem,hDlg,IDC_EXIT
                mov     ecx,eax
                INVOKE  GetWindowRect,ecx,ADDR rcBtnExit        ;取得「離開」子視窗的矩形位置,相對於螢幕座標
                mov     edx,rcBtnExit.bottom
                sub     edx,rcBtnExit.top                       ;EDX=「離開」按鈕的高度
                mov     cyBtnExit,edx
            ;取得標籤控制項代碼及在標籤控制項中插入三個項目,「時間調整」項目、「字幕檔合併」項目、「說明」項目
                mov     tci.imask,TCIF_TEXT or TCIF_IMAGE       ;項目上包含文字與圖示
                mov     tci.pszText,OFFSET szTabAdjTime
                mov     tci.iImage,0
                INVOKE  SendMessage,hTabCtrl,TCM_INSERTITEM,0,OFFSET tci
                mov     tci.pszText,OFFSET szTabMerge
                mov     tci.iImage,1
                INVOKE  SendMessage,hTabCtrl,TCM_INSERTITEM,1,OFFSET tci
                mov     tci.pszText,OFFSET szTabHelp
                mov     tci.iImage,2
                INVOKE  SendMessage,hTabCtrl,TCM_INSERTITEM,2,OFFSET tci
            ;計算標籤控制項位置及大小
                mov     ecx,rcDlgClient.bottom  ;ECX=「字幕工具」對話盒的工作區高度
                sub     ecx,cyBtnExit           ;減去「離開」按鈕的高度
                sub     ecx,PADDING*3           ;再減去兩者(「字幕工具」與「離開」)的空隙
                mov     edx,rcDlgClient.right   ;EDX=「字幕工具」對話盒的工作區寬度
                sub     edx,PADDING*2           ;減去左右間隙
                INVOKE  MoveWindow,hTabCtrl,PADDING,PADDING,edx,ecx,FALSE
                INVOKE  GetWindowRect,hTabCtrl,OFFSET rcTab     ;取得「標籤」子視窗的矩形位置,相對於螢幕座標
                mov     edx,rcTab.left
                mov     ecx,rcTab.top
                mov     ptTabDisp.x,edx         ;保存「標籤」子視窗左上角座標,此座標相對於螢幕座標
                mov     ptTabDisp.y,ecx
                INVOKE  SendMessage,hTabCtrl,TCM_ADJUSTRECT,FALSE,OFFSET rcTab
                mov     edx,rcTab.left          ;rcTab為「標籤」控制項顯示區域的座標,此座標相對於螢幕座標
                mov     ecx,rcTab.top
                sub     edx,ptTabDisp.x
                sub     ecx,ptTabDisp.y
                mov     ptTabDisp.x,edx         ;(EDX,ECX)=「標籤」控制項顯示區域的座標,此座標原點為標籤控件左上角
                mov     ptTabDisp.y,ecx
            ;建立「時間調整」對話盒
                INVOKE  CreateDialogParam,hInstance,OFFSET szAdjTimeTemp,hTabCtrl,OFFSET AdjTimeProc,0
                mov     hDlgAdjTime,eax
                mov     edx,rcTab.right
                mov     ecx,rcTab.bottom
                sub     edx,rcTab.left
                sub     ecx,rcTab.top
                mov     cxTabDisp,edx           ;EDX=「標籤」控制項顯示區域的寬度
                mov     cyTabDisp,ecx           ;EDX=「標籤」控制項顯示區域的高度
                INVOKE  MoveWindow,eax,ptTabDisp.x,ptTabDisp.y,edx,ecx,FALSE
                INVOKE  ShowWindow,hDlgAdjTime,TRUE
            ;建立「字幕檔合併」對話盒
                INVOKE  CreateDialogParam,hInstance,OFFSET szMergeTemp,hTabCtrl,OFFSET MergeProc,0
                mov     hDlgMerge,eax
                INVOKE  MoveWindow,eax,ptTabDisp.x,ptTabDisp.y,cxTabDisp,cyTabDisp,FALSE
                INVOKE  ShowWindow,hDlgMerge,FALSE
            ;建立「說明」對話盒
                INVOKE  CreateDialogParam,hInstance,OFFSET szHelpTemp,hTabCtrl,OFFSET HelpProc,0
                mov     hDlgHelp,eax
                INVOKE  MoveWindow,eax,ptTabDisp.x,ptTabDisp.y,cxTabDisp,cyTabDisp,FALSE
                INVOKE  ShowWindow,hDlgHelp,FALSE

.ELSEIF uMsg==WM_COMMAND
                mov     edx,wParam
                mov     eax,edx
                shr     edx,10h         ;EDX=通知碼
                and     eax,0ffffh      ;EAX=控制元件識別碼
    .IF dx==BN_CLICKED
                ;處理按鈕壓下的程式
    .ENDIF

.ELSEIF uMsg==WM_NOTIFY
                mov     eax,wParam
                mov     edx,lParam
                and     eax,0ffffh      ;EAX=控制元件識別碼
                mov     edx,[edx+8]     ;EDX=通知碼
    .IF edx==TCN_SELCHANGE
        .IF eax==IDC_TAB
                INVOKE  SendMessage,hTabCtrl,TCM_GETCURSEL,0,0
            .IF eax==0
                INVOKE  ShowWindow,hDlgAdjust,TRUE
                INVOKE  ShowWindow,hDlgMerge,FALSE
                INVOKE  ShowWindow,hDlgHelp,FALSE
            .ELSEIF eax==1
                INVOKE  ShowWindow,hDlgAdjust,FALSE
                INVOKE  ShowWindow,hDlgMerge,TRUE
                INVOKE  ShowWindow,hDlgHelp,FALSE
            .ELSEIF eax==2
                INVOKE  ShowWindow,hDlgAdjust,FALSE
                INVOKE  ShowWindow,hDlgMerge,FALSE
                INVOKE  ShowWindow,hDlgHelp,TRUE
            .ENDIF
        .ENDIF
    .ENDIF

.ELSE       ;其他訊息
                mov     eax,FALSE
                ret
.ENDIF
                mov     eax,TRUE
                ret
DlgProc         ENDP
;-------------------------------------------------------------------------------
START:          mov     iccex.dwSize,SIZEOF INITCOMMONCONTROLSEX
                mov     iccex.dwICC,ICC_TAB_CLASSES
                INVOKE  InitCommonControlsEx,OFFSET iccex
                INVOKE  GetModuleHandle,NULL
                mov     hInstance,eax
                INVOKE  DialogBoxParam,eax,OFFSET szDlgTemplate,NULL,OFFSET DlgProc,NULL
                INVOKE  ExitProcess,eax
;*******************************************************************************
END             START

上面的例子堙A當使用者選定不同的標籤項目時,利用 ShowWindow 來顯示被選定項目內的非模式對話盒,並且使其他對話盒不顯示。您可以在 WM_NOTIFY 訊息中見到。假如標籤控制項的項目很多的話,就不能像上面以列舉方式一一處理,這時可以把那些非模式對話盒代碼變成一個陣列,這樣可以大幅減少程式碼。

因為這個例子程式碼太大,所以小木偶把它壓縮成一個檔案,請到這兒下載。先解壓縮後,再依下面方法就可以製造出可執行檔:

E:\HomePage\SOURCE\Win32\SUBRIP>rc SUBRIP.RC [Enter]

E:\HomePage\SOURCE\Win32\SUBRIP>ml SUBRIP.ASM /link SUBRIP.RES [Enter]
Microsoft (R) Macro Assembler Version 6.14.8444
Copyright (C) Microsoft Corp 1981-1997.  All rights reserved.

 Assembling: SUBRIP.ASM
Microsoft (R) Incremental Linker Version 5.12.8078
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

/SUBSYSTEM:WINDOWS
"SUBRIP.obj"
"/OUT:SUBRIP.exe"
"SUBRIP.RES"

E:\HomePage\SOURCE\Win32\SUBRIP>

註一:

Windows 經過許多次改版,每次改版界面總是越來越漂亮,當然各控制項 ( 包含通用控制項 ) 的外觀也同樣的發生變化。而通用控制項的外觀、訊息等都是由 COMCTL32.DLL 負責處理,因此底下所介紹的就是 COMCTL32.DLL 幾次重要的版本變化︰

  1. 4.00︰第一個版本的 Win32 作業系統是 Windows 95/Windows NT 4.0,所使用的 COMCTL32.DLL 版本是 4.00。其界面當然是比當初 Windows 3.x 好看得多。
  2. 4.70︰原版的 Windows 95/NT 4.0 並沒有附 Internet Explorer ( 簡稱 IE ),如果您想連上網際網路,必須再安裝 Internet Explorer 以及撥接程式,您可以免費下載 IE 1.0∼3.0。COMCTL32.DLL 4.70 版本是隨 Internet Exploere 3.0 安裝的,所以如果您安裝好 IE 3.x 後,同時安裝好 COMCTL32.DLL 4.70 版了。
  3. 4.71︰COMCTL32.DLL 4.71 版是隨 IE 4.0 安裝的,所以如果您安裝好 IE 4.0 後,同時安裝好 COMCTL32.DLL 4.71 版了。
  4. 4.72︰Windows 98 第一版的 COMCTL32.DLL 是 4.72 版的。或者是安裝好 IE 4.01 後,也能升級到 4.72 版的 COMCTL32.DLL。
  5. 5.80︰Windows 98 第二版的 COMCTL32.DLL 是 5.80 版的,或安裝好 IE 5.0。
  6. 5.81︰Windows 2000/Me 的 COMCTL32.DLL 是 5.81 版的,或安裝好 IE 5.01、5.5、6.0。
  7. 6.00︰Windows XP 的 COMCTL32.DLL 是 6.00 版的。
  8. 6.10︰Windows Vista/7 的 COMCTL32.DLL 是 6.10 版的。

有關 COMCTL32.DLL 更詳細的版本及演進,可以參閱 Geoff Chappell - Software Analyst

註二:

WM_KEYDOWN 訊息的 lParam 參數的意義如下:

位元說明
0∼15 位元為重複次數
16∼23 位元掃描碼
24 位元是否為擴充按鍵,如果是的話,此位元為 1;否則為 0。擴充按鍵包括右手邊的 Alt 鍵、Ctrl 鍵等等。
25∼28 位元未使用
29 位元0
30 位元現在按鍵的狀態。當 WM_KEYDOWN 被送出之前,若按鍵仍被壓下,此位元為 1;否則為 0。
31 位元0

到第二十章回到首頁到第二十二章