Ch 22 通用控制項(2):List View


由外觀說明什麼是 List View 控制項

中文把 List View 控制項翻譯成「清單檢視」,它是一個可以包含許多「項目」( item ) 的通用控制項,這些項目可以供使用者挑選。下面的六張圖是同一個含有 18 個項目的清單檢視控制項在不同顯示方式的畫面。Windows 9x/Me 提供四種顯示方式 ( view type ):icon view、details view ( 也叫 report view )、small icon view、list view ,而 Windows XP 提供一種新的顯示方式,tile view。

圖上方的左圖顯示方式稱為「icon view」,每個項目僅僅顯示圖示及項目名稱。圖上方的中圖顯示方式稱為「details view」,也叫 report view,這種顯示方式能顯示最詳盡資料,每一列 ( 由左而右 ) 的最左邊 ( 第 0 欄 ) 稱為「項目」,其右可能包含數個「子項目」,這些子項目都在不同的欄位排列整齊,如同一張表格。第 0 個欄是顯示項目名稱,也只有第 0 個欄位可以顯示圖示;第一個欄位以後的稱為子項目,僅能顯示子項目名稱。圖上方的右圖顯示方式稱為「small icon view」每個項目僅僅顯示小圖示及項目名稱。圖下方的左圖顯示方式稱為「list view」,也僅僅顯示小圖示及項目名稱。圖下方的中圖顯示方式稱為「tile view」,它是以大圖示顯示,同時旁邊可以再顯示幾個欄位的資料。

  

  

到了 XP 的時代,「清單檢視」控制項也可以用「群組」模式顯示,如圖下方的右圖是「small icon view」顯示方式時的群組模式。但是並非所有顯示方式都可以以群組模式顯示,「list view」就無法以群組模式顯示,而其餘四種顯示方式都可以以群組模式顯示。

List View 的外觀

要建立 List View 控制項,必須先確認 COMCTL32.DLL 是否載入到記憶體,要確定這點可以呼叫 InitCommonControls 或 InitCommonControlsEx,這個步驟在前一章已經敘述過了。接下來就是建立 List View 控制項,我們可以在資源描述檔中加入

CONTROL "",IDC_ListView,"SYSListView32", style, x, y, width, height, exStyle

或者是以「SYSListView32」為類別,呼叫 CreateWindowEx 建立 List View 子視窗。這些步驟跟前面建立標籤控制項或其他控制項的過程,並無不同。當建立「清單檢視」控制項時,想以上面五種顯示方式的某一種方式顯示項目的訣竅,其實就是在建立「清單檢視」控制項時,採用不同的風格所致。下表就是這五種顯示方式所使用的風格。

顯示方式
view type
資源描述檔
中的風格
LVM_SETVIEW 訊息中的
wParam 參數
說  明
Icon ViewLVS_ICONLV_VIEW_ICON 每個項目以圖示顯示,而文字標籤 ( label ) 在圖示下面。使用者可以在「清單檢視」控制項內拖動圖示。
Details ViewLVS_REPORTLV_VIEW_DETAILS 也叫 Report View。最上面會有欄位名稱,欄位名稱底下則是各個項目,每一項目以列表方式顯示於一列中,而每個項目最左邊的一列包含圖示及標籤文字,而其右邊的每一列都稱為子項目 ( subitem )。最上面應有
Small Icon ViewLVS_SMALLICONLV_VIEW_SMALLICON 每個項目以小圖示顯示,而文字標籤在小圖示的右邊。使用者可以拖動圖示到任意地方。
List ViewLVS_LISTLV_VIEW_LIST 每個項目以小圖示顯示,而且整理成一行顯示。使用者無法拖動圖示。
Tile View LV_VIEW_TILE 只有 COMCTL32.DLL 6.0 版以後才提供的顯示方式。每個項目以大圖示顯示,文字標籤在圖示右邊,而文字標籤底下可能還有更多的資料說明。

改變 List View 的顯示方式

事實上,在資源描述檔中設定好的風格,並非完全不能更改。我們可以透過兩種方法改變「清單檢視」控制項的顯示方式:一是對「清單檢視」控制項發出 LVM_SETVIEW 訊息,也就是呼叫 SendMessage API。

INVOKE  SendMessage,hListView,LVM_SETVIEW,wParam,lParam

這時候 LVM_SETVIEW 訊息中的 wParam 參數就是如上表所示,而 lParam 參數為 0。例如原先是以 List View 方式顯示,要變更為 Icon View,那麼 wParam 參數就是 LV_VIEW_ICON。Tile View 是 XP 新增的顯示方式,無法在資源描述檔中設定,但可透過 SendMessage 發出帶著 LV_VIEW_TILE 參數的 LVM_SETVIEW 訊息改變。

另一種方法是改變視窗的風格。讀者們應該還記得,控制項其實就是一種視窗,更精確的說法,是「子視窗」。所以控制項也具有視窗的性質。我們可以藉由呼叫 SetWindowLong 來改變視窗風格。由於我們只希望改變「清單檢視」的顯示方式,而不希望改變其他風格,因此最好是先呼叫 GetWindowLong 取得現有的清單檢視風格,再更改其中的顯示方式。GetWindowLong 的原型是:

LONG GetWindowLong ( 
    HWND hWnd,
    int  nIndex
);

我們可以經由 GetWindowLong API 取得視窗的資料,這些資料包含了視窗風格。hWnd 是我們要取得資料的視窗代碼,nIndex 是要取得的資料,可以是下面幾種情形:

GetWindowLong 的 nIndex 參數

nIndex說 明
GWL_EXSTYLE取得擴充的風格,此擴充風格是呼叫 CreateWindowEx 時第一個參數 dwExStyle 所指定的。
GWL_STYLE取得視窗風格
GWL_WNDPROC取得視窗函式位址,取得此位址後,可以用 CallWindowProc 呼叫此位址
GWL_HINSTANCE取得視窗執行實例
GWL_HWNDPARENT取得父視窗的代碼
GWL_ID取得視窗識別碼
GWL_USERDATA得到和視窗相關聯的 32 位元數值,每一個視窗都有一個特地留給建立視窗的程式用的 32 位元數值
如果 hWnd 是對話盒,那麼也可以使用下面的數值取得特定資料
DWL_DLGPROC取得對話框函式位址,取得此位址後,可以用 CallWindowProc 呼叫此位址
DWL_MSGRESULT取得對話框函式訊息處理的傳回值
DWL_USER取得視窗額外私有的訊息,如指標或代碼

SetWindowLong 用法和 GetWindowLong 類似,只是由取得資料變成設定視窗的資料,其原型為:

LONG SetWindowLong (
    HWND hWnd,
    int  nIndex,
    LONG dwNewLong
);

參數 hWnd 是要設定資料的視窗代碼。nIndex 跟上表「GetWindowLong 的 nIndex 參數」的「nIndex」欄位相同,只是由取得改為設定。dwNewLong 是要新設定的值。如果成功的設定視窗資料,則會傳回原先的視窗資料;如果設定失敗,則傳回 FALSE。要設定「清單檢視」控制項的顯示方式,必需還要再經過幾個步驟。底下以設定檢視清單顯示方式為 Icon View 為例,整個過程如下:

                INVOKE  GetWindowLong,hListView,GWL_STYLE   ;取得原來風格
                and     eax,not LVS_TYPEMASK                ;清除顯示方式
                or      eax,LVS_ICON                        ;設定新的顯示方式
                INVOKE  SetWindowLong,hListView,GWL_STYLE,eax

假如您查閱 WINDOWS.INC 檔,會發現底下的清單檢視控制項的風格:

LVS_ICON        EQU     0000H
LVS_REPORT      EQU     0001H
LVS_SMALLICON   EQU     0002H
LVS_LIST        EQU     0003H
LVS_TYPEMASK    EQU     0003H

上面程式碼中最後一個,LVS_TYPEMASK 之值為 3,也就是二進位的 11,而其他有關顯示方式的幾個風格都是在 0∼3 的範圍內,也就是佔據第 0、1 位元。您可以查 WINDOWS.INC 發現檢視清單的其他風格則是佔據第 2∼31 位元。假設我們僅僅想要改變顯示方式,而不改其他風格,那麼就只能改變 0、1 位元。因此取得檢視清單原來的風格後,與 NOT LVS_TYPEMASK ( NOT LVS_TYPEMASK 之值為 1111 1111 1111 1100 ) 作 AND 運算就可使得第 0、1 位元被清除,接著再與想要顯示的顯示方式作 OR 運算,就可以得到新的風格。


在 List View 中的欄位 ( column )

添加欄位

由上面的說明,您應當已知,在清單檢視控制項的顯示方式中,能顯示最多資料的是「details view」。它顯示方式像是一個表格,最上面有一列標題,標題是由許多欄位 ( column ) 組成。每一個項目就像一筆資料一樣,由上向下一項一項列出於清單檢視中,而每一項又包含許多欄位,第一個欄位稱為「項目」,以後的欄位則稱為「子項目」。因此,假如您設計的清單檢視要以「details view」顯示,那麼就必須先設置標題及各欄位名稱,方法是呼叫 SendMessage,對清單檢視發出 LVM_INSERTCOLUMN 訊息,如下面程式片段:

        INVOKE  SendMessage,hListView,LVM_INSERTCOLUMN,iCol,pcol

hListView 是要新添加欄位的清單檢視控制項代碼;iCol 欄位編號,由零開始,0 表示項目,1 及其以後表示子項目;pcol 是指向一個稱為 LV_COLUMN 的結構體,這個結構體也叫 LVCOLUMN。LVCOLUMN 結構體也用在 LVM_GETCOLUMN、LVM_SETCOLUMN、LVM_DELETECOLUMN 訊息,分別取得、改變 ( 亦即設定 )、刪去清單檢視控制項的某個欄位。LVCOLUMN 的欄位成員定義是:

LVCOLUMN        STRUCT  
  imask         UINT    ?
  fmt           DWORD   ?
  lx            DWORD   ?
  pszText       LPTSTR  ?
  cchTextMax    DWORD   ?
  iSubItem      DWORD   ?
  iImage        DWORD   ?
  iOrder        DWORD   ?
  cxMin         DWORD   ?
  cxDefault     DWORD   ?
  cxIdeal       DWORD   ?
LVCOLUMN        ENDS

imask 是指 LVCOLUMN 結構體的哪幾項成員是有效的,在 MSDN 堙Aimask 稱為 mask,但在 MASM32 堙Amask 是保留字,因此改為 imask。不同的 imask 數值,代表 LVCOLUMN 中的某些欄位需要設定,或者是返回時某些欄位已填好資料;換句話說 imask 之值,會影響某些欄位,如下表︰

imask 影響欄位說   明
LVCF_FMT fmt fmt 欄位是用來設定欄位文字如何對齊。不過最左邊的欄位文字格式是無法設定的,其餘子項目的欄位文字格式則可以設定。fmt 可以是下面幾種數值:
LVCFMT_LEFT:文字向左對齊,其值為 0。
LVCFMT_RIGHT:文字向右對齊,其值為 1。
LVCFMT_CENTER:文字向中間對齊,其值為 2。
LVCFMT_JUSTIFYMASK:此數值用來遮罩文字排列方式,其值為 3,恰好就是 LVCFMT_LEFT、LVCFMT_RIGHT、LVCFMT_CENTER 所在的位元。
LVCFMT_IMAGE:由 image list 中挑一張圖片顯示,要顯示的圖片索引值填在 iImage 欄位堙A所以 imask 須為 LVCF_IMAGE or LVCF_FMT。4.70 版以後的 COMCTL32.DLL 才支援。
LVCFMT_BITMAP_ON_RIGHT:4.70 版以後的 COMCTL32.DLL 才支援,使位元圖出現在文字右邊。
LVCFMT_COL_HAS_IMAGES:欄位包含 image list 中的圖片,4.70 版以後的 COMCTL32.DLL 才支援。
LVCFMT_FIXED_WIDTH:不能變更欄位寬度,和 HDF_FIXEDWIDTH 相同,XP 或 COMCTL32.DLL 在 6.00 版以後才支援。
LVCFMT_NO_DPI_SCALE
LVCFMT_FIXED_RATIO:寬度會隨高度改變,XP 或 COMCTL32.DLL 在 6.00 版以後才支援。
LVCFMT_LINE_BREAK:XP 或 COMCTL32.DLL 在 6.00 版以後才適用,只用於 Title View 顯示方式時,強迫文字到下一欄位,需搭配 LVTVIF_EXTENDED 旗標。
LVCFMT_FILL:XP 或 COMCTL32.DLL 6.00 版以後才適用,只用於 Title View 顯示方式時,可以使整個欄位填滿文字,需搭配 LVTVIF_EXTENDED 旗標。
LVCFMT_WRAP:XP 或 COMCTL32.DLL 6.00 版以後才適用,只用於 Title View 顯示方式時,可以使延伸的文字換行,需搭配 LVTVIF_EXTENDED 旗標。
LVCFMT_NO_TITLE:XP 或 COMCTL32.DLL 在 6.00 版以後才適用,只用於 Title View 顯示方式時,使欄位不顯示文字,需搭配 LVTVIF_EXTENDED 旗標。
LVCFMT_TILE_PLACEMENTMASK:XP 或 COMCTL32.DLL 在 6.00 版以後才適用,相當於結合 LVCFMT_LINE_BREAK 與 LVCFMT_FILL,需搭配 LVTVIF_EXTENDED 旗標。
LVCFMT_SPLITBUTTON:XP 及或 COMCTL32.DLL 在 6.00 版以後才適用,欄位是一個 split button,與 HDF_SPLITBUTTON 相同。
LVCF_WIDTH lx lx 是欄位寬度,以圖素 ( pixel,即螢幕上的一個點 ) 為單位。在 MSDN 文獻中,此欄位名稱為 cx,但在組合語言堜M暫存器 CX 同名,故改稱為 lx。
LVCF_TEXT pszText 或
cchTextMax
pszText 是以 0 為結尾的字串位址,如果是在 LVM_GETCOLUMN 訊息中,代表系統會把清單控制項的欄位名稱填入 pszText 所指位址的緩衝區堙A其最大的大小是幾個寬字元長度,應該事先在 cchTextMax 中設定;如果是 LVM_SETCOLUMN、LVM_INSERTCOLUMN 訊息堙A會使清單控制項的欄位名稱設定為 pszText 位址所指的字串,而 cchTextMax 是無作用。
LVCF_SUBITEM iSubItem iSubItem 是第幾個欄位,由零開始,此欄位應與 iCol 相同。0 表示項目,1 及其以後表示子項目。
LVCF_IMAGE iImage 這是 COMCTL32.DLL 在 4.70 版及其以後才支援的欄位。( 關於 COMCTL32.DLL 的版本可以參考第 21 章註一。)iImage 是 Image List 堶捲譬X個圖示,這個圖示會顯示在欄位名稱上。
LVCF_ORDER iOrder 這是 COMCTL32.DLL 在 4.70 版及其以後才支援的欄位。
LVCF_MINWIDTH cxMin 這是 COMCTL32.DLL 在 6.00 版及其以後才支援的欄位。cxMin 表示最小欄位寬度,以圖素為單位。
LVCF_DEFAULTWIDTH cxDefault 這是 COMCTL32.DLL 在 6.00 版及其以後才支援的欄位。cxDefault 表示內定的欄位寬度,以圖素為單位。
LVCF_IDEALWIDTH cxIdeal 這是 COMCTL32.DLL 在 6.00 版及其以後才支援的欄位。

如果程式對清單檢視發出 LVM_INSERTCOLUMN 訊息,並成功地設置新的欄位,返回值為新欄位的編號;若失敗,則返回 -1。而 LVM_DELETECOLUMN、LVM_GETCOLUMN、LVM_SETCOLUMN 訊息若能成功,返回 TRUE;否則返回 FALSE。

在欄位名稱中顯示圖示

如果「清單檢視」控制項以 details view 方式顯示,其視窗的最上面是由好幾個欄位所組成的標題 ( header ),事實上,標題也是一種通用控制項,稱為「標題控制項」。如果要在標題中的欄位顯示圖示的話,必須先取得「標題控制項」的代碼。

  1. 取得在清單檢視中的「標題控制項」代碼︰可以對「清單檢視」控制項發出 LVM_GETHEADER 訊息,以獲得其標題控制項代碼。方法是︰
    INVOKE  SendMessage,hListView,LVM_GETHEADER,0,0  
    返回時,EAX 即為「標題控制項」代碼。

  2. 把某個已建立的「圖片清單」與「標題控制項」連結起來︰可以對標題控制項發出 HDM_SETIMAGELIST 訊息,這是標題控制項的訊息之一。
    INVOKE  SendMessage,hHeader,HDM_SETIMAGELIST,wParam,lParam
    上面程式中的 wParam 可以是 HDSIL_NORMAL 或 HDSIL_STATE,分別代表顯示在欄位或不同狀態時候的圖示,如果是只要在標題控制項的某個欄位顯示圖示的話,那麼 wParam 設為 HDSIL_NORMAL 即可。lParam 則是圖片清單的代碼。

  3. 在 LVCOLUMN 結構體中的 iImage 成員中設定要顯示在標題控制項中,某個欄位的圖示是在圖片清單的第幾個圖示。方法是對清單檢視控制項發出 LVM_SETCOLUMN,當然呼叫之前必須設好 LVCOLUMN 的適當欄位,如下面程式片段︰
    mov     lvc.imask,LVCF_IMAGE
    mov     lvc.fmt,LVCFMT_IMAGE or LVCFMT_CENTER or LVCFMT_BITMAP_ON_RIGHT
    INVOKE  SendMesage,hListView,LVM_SETCOLUMN,1,ADDR lvc

上面程式的第三行,發送 LVM_SETCOLUMN 訊息時,wParam 代表第幾個子項目,lParam 代表 LVCOLUMN 結構體所在位址。


在 List View 中增添項目 ( item ) 或子項目 ( subitem )

要在「清單檢視」控制項堬K加一個項目,必須呼叫 SendMessage,對「清單檢視」發出 LVM_INSERTITEM 訊息;要在此項目堬K加子項目 ( subitem ),也要呼叫 SendMessage,對其發出 LVM_SETITEM 訊息。如果要添加兩個子項目,就得對清單檢視控制項發出兩次 LVM_SETITEM 訊息,您無法用 LVM_INSERTITEM 中添加子項目。如下面程式碼:上面是添加項目,如果成功返回新項目的索引值,如果失敗傳回 -1;下面是添加子項目,如果成功返回 TRUE,如果失敗返回 FALSE︰

INVOKE  SendMessage,hListView,LVM_INSERTITEM,0,pitem  ;添加項目
INVOKE  SendMessage,hListView,LVM_SETITEM,0,pitme     ;添加子項目

LVM_INSERTITEM 和 LVM_SETITEM 訊息的 wParam 均為零;而 lParam 參數都是 pitem,它是指向一個名為 LVITEM 的結構體位址。您應該在呼叫 SendMessage 前先設定好 LVITEM 結構體的各項欄位。如果您想改變已添加項目的項目名稱、子項目名稱、狀態……等,也是對清單檢視控制項發出 LVM_SETITEM 訊息,同樣的,您也需要在呼叫 SendMessage 前設定好 LVITEM 各欄位之值。LVITEM 結構體舊稱 LV_ITEM 結構體,其成員如下:

LVITEM          STRUCT
  imask         UINT    ?
  iItem         DWORD   ?
  iSubItem      DWORD   ?
  state         UINT    ?
  stateMask     UINT    ?
  pszText       LPTSTR  ?
  cchTextMax    DWORD   ?
  iImage        DWORD   ?
  lParam        LPARAM  ?
  iIndent       DWORD   ?
  iGroupId      DWORD   ?
  cColumns      UINT    ?
  puColumns     DWORD   ?
  piColFmt      DWORD   ?
  iGroup        DWORD   ?
LVITEM          ENDS

imask 是用來表示 LVITEM 結構體的哪幾個欄位成員是有效的,不同的 imask 之值,代表 LVITEM 結構體中某些特定欄位必須設定。imask 值有下列幾種:( 在 MSDN 堙Aimask 稱為 mask,但在 MASM32 堙Amask 是保留字,因此改為 imask,其資料型態為 32 位元長的無號整數,即 UINT )

imask 影響欄位說   明
LVIF_TEXT pszText 或
cchTextMax
pszText 是一個指標,此指標指向一個字串,表示此項目的標籤文字,標籤文字雖然可以超過 260 字元,但只有前面的 260 字元會顯示出來。如果是用在取得項目資料的訊息時,pszText 就是接收的緩衝區位址,這時要接收的字元長度 ( 包含 NULL ) 由 cchTextMax 指定。如果發送 LVM_SETITEM 和 LVM_INSERTITEM 訊息時,pszText 就是要設定的項目或子項目名稱,此時 cchTextMax 就無作用了。
LVIF_IMAGE iImage iImage 是圖片清單中的索引值,表示要把此索引值所代表的圖片指定給此項目。
LVIF_COLUMNS cColumns
puColumns
Windows XP 及其以後的作業系統才支援此旗標。cColumns 是在 Tile View 顯示方式下,顯示幾欄的子項目,最多為 20。
LVIF_GROUPID iGroupId iGroupId 是 COMCTL32.DLL 6.00 版以後才有的欄位,是用來設定此項目屬於哪一個群組。如果沒有設定 LVIF_GROUPID,則系統會把 iGroupId 視為 I_GROUPIDNONE,表示此項目不屬於任何一個群組;iGroupId 也可以是 I_GROUPIDCALLBACK,表示此清單檢視控制項會對父視窗發出 LVN_GETDISPINFO 訊息,以取得群組的索引值。
LVIF_COLFMT piColFmt
cColumns
Windows Vista/7 及其以後的作業系統才支援此旗標。
LVIF_INDENT iIndent iIndent 欄位是安裝了 IE 3.0 以後或 COMCTL32.DLL 更新到 4.70 版以後,才有的欄位;它是用來指定項目寬度為幾個圖示的寬度,也就是說如果 iIndent 為 1,表示項目設為一個圖示寬,iIndent 為 2,表示項目設為兩個圖示寬。
LVIF_PARAM lParam lParam 是程式自行定義的數值,可以用在排序 ( 發出 LVM_SORTITEMS 訊息 ) 或搜尋 ( 發出 LVM_FINDITEM 訊息 )。
LVIF_STATE state 清單檢視控制項中的項目有底下幾種狀態︰
  1. LVIS_FOCUSED︰其值為 01H,此項目是具有輸入焦點的。被選定的項目可以有好幾個,但是僅有一個是具有輸入焦點的。
  2. LVIS_SELECTED︰其值為 02H,此項目是被選定的。
  3. LVIS_CUT︰其值為 04H,由於剪切被選定。
  4. LVIS_DROPHILITED︰其值為 08H,此項目正在被滑鼠拖移中。
  5. LVIS_ACTIVATING︰其值為 20H,未被選定,亦即一般狀態。
  6. LVIS_OVERLAYMASK︰其值為 0F00H,
  7. LVIS_STATEIMAGEMASK︰其值為 0F000H,
LVIF_NORECOMPUTE
LVIF_DI_SETITEM

iItem 項目索引,亦即第幾個項目,由零開始編號,在清單檢視奡﹞J項目時,要指定項目索引值。iSubItem 是子項目的索引值,亦即第幾個欄位,由一開始,因為零表示項目,在清單檢視奡﹞J項目或設定子項目時,應指定子項目索引值。例如我們先簡單考慮下面這種情形︰有個小公司僅有三人,想把員工基本資料 ( 姓名、性別、年齡、出生地 ) 顯示在清單檢視控制項中。因此我們得在標題中新增四個欄位︰姓名、性別、年齡、出生地,並加入三個項目,這三個項目分別是三個人的資料--姓名、性別、年齡、出生地,如下圖︰

那麼這三個人的姓名稱為「項目」,而性別、年齡、出生地則是第一、二、三個子項目。那麼程式碼應該是像下面這樣︰

.CONST
;欄位名
szName          BYTE    'Name',0
szSex           BYTE    'Sex',0
szAge           BYTE    'Age',0
szBirthPlace    BYTE    'Birth Place',0
;第 0 個項目的資料
szNo1Name       BYTE    'John',0
szNo1Sex        BYTE    'Male',0
szNo1Age        BYTE    '40',0
szNo1BP         BYTE    'Taipei',0
;第 1 個項目的資料
szNo2Name       BYTE    'Mary',0
szNo2Sex        BYTE    'Female',0
szNo2Age        BYTE    '30',0
szNo2BP         BYTE    'Kaohsiung',0
;第 2 個項目的資料
szNo3Name       BYTE    'David',0
szNo3Sex        BYTE    'Male',0
szNo3Age        BYTE    '28',0
szNo3BP         BYTE    'Taichung',0
 . . . . . . . . . . . . . . . . . . .
        ;添加標題,此標題共四欄
                mov     lvc.imask,LVCF_FMT or LVCF_TEXT or LVCF_WIDTH or LVCF_SUBITEM
                mov     lvc.fmt,LVCFMT_CENTER
                mov     lvc.lx,45
                mov     lvc.pszText,OFFSET szName
                mov     lvc.iSubItem,0
                INVOKE  SendMessage,hListView,LVM_INSERTCOLUMN,0,ADDR lvc   ;添加第 0 欄,姓名
                mov     lvc.lx,50
                mov     lvc.pszText,OFFSET szSex
                mov     lvc.iSubItem,1
                INVOKE  SendMessage,hListView,LVM_INSERTCOLUMN,1,ADDR lvc   ;添加第 1 欄,性別
                mov     lvc.lx,50
                mov     lvc.pszText,OFFSET szAge
                mov     lvc.iSubItem,2
                INVOKE  SendMessage,hListView,LVM_INSERTCOLUMN,2,ADDR lvc   ;添加第 2 欄,年齡
                mov     lvc.lx,90
                mov     lvc.pszText,OFFSET szBirthPlace
                mov     lvc.iSubItem,3
                INVOKE  SendMessage,hListView,LVM_INSERTCOLUMN,3,ADDR lvc   ;添加第 3 欄,出生地
        ;添加第一個員工的資料
                mov     lvi.imask,LVIF_TEXT or LVIF_PARAM
                mov     lvi.iItem,0
                mov     lvi.iSubItem,0
                mov     lvi.lParam,0
                mov     lvi.pszText,OFFSET szNo1Name
                INVOKE  SendMessage,hListView,LVM_INSERTITEM,0,ADDR lvi
                mov     lvi.imask,LVIF_TEXT
                mov     lvi.iSubItem,1
                mov     lvi.pszText,OFFSET szNo1Sex
                INVOKE  SendMessage,hListView,LVM_SETITEM,0,ADDR lvi
                mov     lvi.iSubItem,2
                mov     lvi.pszText,OFFSET szNo1Age
                INVOKE  SendMessage,hListView,LVM_SETITEM,0,ADDR lvi
                mov     lvi.iSubItem,3
                mov     lvi.pszText,OFFSET szNo1BP
                INVOKE  SendMessage,hListView,LVM_SETITEM,0,ADDR lvi
        ;添加第二個員工的資料
                mov     lvi.imask,LVIF_TEXT or LVIF_PARAM
                mov     lvi.iItem,1
                mov     lvi.iSubItem,0
                mov     lvi.lParam,1
                mov     lvi.pszText,OFFSET szNo2Name
                INVOKE  SendMessage,hListView,LVM_INSERTITEM,0,ADDR lvi
                mov     lvi.imask,LVIF_TEXT
                mov     lvi.iSubItem,1
                mov     lvi.pszText,OFFSET szNo2Sex
                INVOKE  SendMessage,hListView,LVM_SETITEM,0,ADDR lvi
                mov     lvi.iSubItem,2
                mov     lvi.pszText,OFFSET szNo2Age
                INVOKE  SendMessage,hListView,LVM_SETITEM,0,ADDR lvi
                mov     lvi.iSubItem,3
                mov     lvi.pszText,OFFSET szNo2BP
                INVOKE  SendMessage,hListView,LVM_SETITEM,0,ADDR lvi
        ;添加第三個員工的資料
                mov     lvi.imask,LVIF_TEXT or LVIF_PARAM
                mov     lvi.iItem,2
                mov     lvi.iSubItem,0
                mov     lvi.lParam,2
                mov     lvi.pszText,OFFSET szNo3Name
                INVOKE  SendMessage,hListView,LVM_INSERTITEM,0,ADDR lvi
                mov     lvi.imask,LVIF_TEXT
                mov     lvi.iSubItem,1
                mov     lvi.pszText,OFFSET szNo3Sex
                INVOKE  SendMessage,hListView,LVM_SETITEM,0,ADDR lvi
                mov     lvi.iSubItem,2
                mov     lvi.pszText,OFFSET szNo3Age
                INVOKE  SendMessage,hListView,LVM_SETITEM,0,ADDR lvi
                mov     lvi.iSubItem,3
                mov     lvi.pszText,OFFSET szNo3BP
                INVOKE  SendMessage,hListView,LVM_SETITEM,0,ADDR lvi

lParam 是程式自行定義的數值,可以用在排序 ( 發出 LVM_SORTITEMS 訊息 ) 或搜尋 ( 發出 LVM_FINDITEM 訊息 )。如果要設定項目的 lParam 數值,最好是在 LVM_INSERTITEM 時就設定好,在 LVM_SETITEM 時,連同子項目一起設定,會產生問題。如何排序,請參閱下面的說明。LVITEM 中的 lParam 雖可以任意定義,但是為了方便,一般會設定為和 iItem 相同,以便在排序時能很快的藉由 lParam 就可取得 iItem,進而得到某個項目的所有資料。

項目名稱中顯示圖示

如果要在項目名稱中顯示圖示,那麼要先對清單檢視控制項發出 LVM_SETIMAGELIST 訊息,使某個圖片清單 ( image list ) 與清單檢視控制項連接起來,程式碼如下︰

        INVOKE  SendMessage,hListView,LVM_SETIMAGELIST,wParam,lParam

hListView 是清單檢視控制項代碼,lParam 是 image list 代碼,代表要把 lParam 中的 image list 與清單檢視控制下連結起來,亦即在此程式碼之後,如果清單檢視控制項需用到 image list 時,就是以 lParam 中所指定的 image list 使用。wParam 可以是下面數值︰

  1. LVSIL_NORMAL︰Image List 為大圖示,一般是在 icon view 和 tile view 中使用。
  2. LVSIL_SMALL︰Image List 為小圖示,一般是在 detail view、small icon view、list view 中使用。
  3. LVSIL_STATE︰項目在某種狀態中使用。
  4. LVSIL_GROUPHEADER︰群組標題中使用。

在一般情形下,只有項目欄位才能顯示圖示,如果您想在子項目中也顯示圖示,那麼必須使用 LVS_EX_SUBITEMIMAGES 擴充風格,請參考 List View 的擴充風格

Windows XP 中的 Tile View

Tile View 是 Windows XP 中新增的一種顯示方式,它可以在圖示旁邊顯示項目名稱及數個子項目名稱。項目名稱是系統內定顯示的,而子項目名稱則是程式可以由 details view 中選擇哪幾個欄位可以顯示。至於要顯示子項目數目及哪些子項目,可以由 LVITEM 中的 cColumns 和 puColumns 決定。cColumns 最大值是 20,亦即最多只能顯示 20 個子項目,連同項目名稱,就有 21 筆資料顯示在圖示旁。puColumns 是指向一個陣列位址,此陣列由 cColumns 個雙字組組成,其每個成員就代表 details view 中的第幾個欄位。

如果顯示的子項目太多,空間不夠時,可能會被切除,這時必須設定每個項目的高度、寬度。方法是先設定 LVTILEVIEWINFO 結構體,再對清單檢視控制項發出 LVM_SETTILEVIEWINFO 訊息,如下面程式片段︰

        INVOKE  SendMessage,hListView,LVM_SETTILEVIEWINFO,wParam,lParam

wParam 必須為 0,lParam 是 LVTILEVIEWINFO 結構體位址。如果返回值為 TRUE,則呼叫成功;若為 FALSE,則呼叫失敗。假如要取得 tile view 中每一項目的長與寬,則可以對清單檢視控制項發出 LVM_GETTILEVIEWINFO 訊息,如下面程式片段︰

        INVOKE  SendMessage,hListView,LVM_GETTILEVIEWINFO,wParam,lParam

wParam 必須為 0,lParam 是 LVTILEVIEWINFO 結構體位址。沒有返回值。LVTILEVIEWINFO 結構體欄位如下︰

LVTILEVIEWINFO  STRUCT
  cbSize        UINT    ?
  dwMask        DWORD   ?
  dwFlags       DWORD   ?
  sizeTile      SIZEL   <>
  cLines        DWORD   ?
  rcLabelMargin RECT    <>
LVTILEVIEWINFO  ENDS

欄位中的 cbSize 是 LVTILEVIEWINFO 大小,以位元組為單位,應在呼叫前就已經填好,不管是發出 LVM_SETTILEVIEWINFO 訊息還是 LVM_GETTILEVIEWINFO 訊息。dwMask 是指定這兩個訊息要設定或取得哪些資料,因此也會影響 dwFlags、sizeTile、cLines、rcLabelMargin 等四個欄位,哪些欄位需要設定或是返回時會被系統填入適當的數值。dwMask 可能的數值有下面三種︰

dwMask 影響欄位說   明
LVTVIM_TILESIZE dwFlags
sizeTile
dwFlags 是用來設定 tile view 項目大小的方式,可以是下面五個數值之一︰
  LVTVIF_AUTOSIZE︰系統自動設定
  LVTVIF_EXTENDED︰Vista 才可以使用此數值,用於 extended tiles view
  LVTVIF_FIXEDWIDTH︰固定寬度
  LVTVIF_FIXEDHEIGHT︰固定高度
  LVTVIF_FIXEDSIZE︰固定寬度與高度
如果使用固定寬度與高度,還需設定 sizeTile。例如,如果 dwFlags 為 LVTVIF_FIXEDWIDTH,就必須設定 sizeTile.x;如果 dwFlags 為 LVTVIF_FIXEDHEIGHT,就必須設定 sizeTile.y;如果 dwFlags 為 LVTVIF_FIXEDSIZE,就必須同時設定 sizeTile.x 及 sizeTile.y。sizeTile 的資料型態是 SIZE,請看下面的說明。
LVTVIM_COLUMNS cLines 顯示在圖示旁的子項目數目,此值應該與 LVITEM 中的 cColumns 相同,如果不同,系統會選擇較小的那一個。
LVTVIM_LABELMARGIN rcLabelMargin

在 MSDN 堙ALVTILEVIEWINFO 中的 sizeTile 是一個稱為 SIZE 的結構體,其欄位為︰

SIZE    STRUCT
  cx    DWORD   ?
  cy    DWORD   ?
SIZE    ENDS

但是 SIZE 是 ML 的保留字,cx 是暫存器的名稱,故 MASM32 以 SIZEL 代替 SIZE,其欄位也以 x、y 代替。因此在 MASM32 堙ASIZE 結構體變為︰

SIZEL   STRUCT
  x     DWORD   ?
  y     DWORD   ?
SIZEL   ENDS

LVM_SETTILEVIEWINFO 及 LVM_GETTILEVIEWINFO 是用來設定或取得全部項目的資料,如果想要處理單一一個項目,或某個特定項目的資料,則必須發出 LVM_SETTILEINFO 或 LVM_GETTILEINFO 訊息。詳細情形可查閱 MSDN。


List View 中的群組 ( Group )

清單檢視控制項以群組顯示

在 Windows XP 及其以後的系統堙A也可以用「群組」模式顯示清單檢視控制項的內容,這時要對清單檢視控制項發出 LVM_ENABLEGROUPVIEW 訊息,切換是否以群組方式顯示︰

INVOKE  SendMessage,hWndControl,LVM_ENABLEGROUPVIEW,fEnable,0

hWndControl 是清單檢視控制項代碼,如果 fEnable 為 TRUE,則會使 hWndControl 變為群組模式;如果 fEnable 為 FALSE,則使得 hWndControl 變為非群組模式。當然如果清單檢視控制項中,沒有群組,那麼仍然無法看見其效果。要新增群組的方法是對清單檢視發出 LVM_INSERTGROUP 訊息,在發出訊息前應先指定群組識別碼及群組名稱,這些資料被包含在 LVGROUP 結構體內。

在清單檢視控制項中新添群組

事實上,除了 LVM_INSERTGROUP 訊息會牽涉到 LVGROUP 結構體外,設立或取得群組資料,也會牽涉到 LVGROUP 結構體。要設立或取得群組資料的方法是分別對清單檢視控制項發出 LVM_SETGROUPINFO 或 LVM_GETGROUPINFO 訊息。這三個訊息都牽涉到一個稱為 LVGROUP 的結構體,這個結構體用來存放清單檢視的群組資料,LVGROUP 結構體成員欄位如下︰

LVGROUP                 STRUCT
  cbSize                UINT    ?
  imask                 UINT    ?
  pszHeader             LPWSTR  ?
  cchHeader             UINT    ?
  pszFooter             LPWSTR  ?
  cchFooter             DWORD   ?
  iGroupId              DWORD   ?
  stateMask             UINT    ?
  state                 UINT    ?
  uAlign                UINT    ?
  pszSubtitle           LPWSTR  ?
  cchSubtitle           UINT    ?
  pszTask               LPWSTR  ?
  cchTask               UINT    ?
  pszDescriptionTop     LPWSTR  ?
  cchDescriptionTop     UINT    ?
  pszDescriptionBottom  LPWSTR  ?
  cchDescriptionBottom  UINT    ?
  iTitleImage           DWORD   ?
  iExtendedImage        DWORD   ?
  iFirstItem            DWORD   ?
  cItems                UINT    ?
  pszSubsetTitle        LPWSTR  ?
  cchSubsetTitle        UINT    ?
LVGROUP                 ENDS

cbSize 是此結構體大小,以位元組為單位,系統藉此分辨版本,必須在發出訊息前,先設定好此欄位。imask 是用來決定哪些欄位是有效的,可能的數值為︰

imask影響欄位 說 明
LVGF_HEADER pszHeader
cchHeader
pszHeader 指向一個以 NULL 為結尾的字串位址,此字串為群組名稱。cchHeader 為 pszHeader 長度,以字元為單位。如果是設定或新添群組,例如發出 LVM_SETGROUPINFO、LVM_INSERTGROUP 訊息時,cchHeader 會被忽略,群組名稱即為 pszHeader 所指的字串;但如果是取得群組資料,例如發出 LVM_GETGROUPINFO 訊息時,cchHeader 就得在發出訊息前設好,系統才知道緩衝區堻怞h可以容納多少字元,爾後所獲得的群組名稱將存於 pszHeader 所指位址。
LVGF_ALIGN uAlign Windows XP 及其以後的版本才可以使用,uAlign 之值可以是 LVGA_HEADER_CENTER、LVGA_HEADER_LEFT、LVGA_HEADER_RIGHT,分別表示群組名稱顯示在清單控制項的中間、左側、右側位置。
LVGF_GROUPID iGroupId 群組的識別碼。在一個清單檢視中應是唯一的,亦即新增群組的識別碼不可重複。
LVGF_SUBTITLE pszSubtitle Windows XP 及其以後的版本才可以使用,

List View 的擴充風格 ( Extended Style )

清單檢視控制項有 30 個擴充風格可以使用,都是以「LVS_EX_」為字首,例如 LVS_EX_BORDERSELECT、LVS_EX_FULLROWSELECT 等等。要使用這些擴充風格,似乎無法在資源描述檔 ( *.RC ) 堛滷惆貕竣云 exStyle 定義,至少小木偶沒成功過。但是可以透過 LVM_SETEXTENDEDLISTVIEWSTYLE 訊息,把資料傳遞到清單檢視控制項內,來設定擴充風格,方法是:

        INVOKE  SendMessage,hListView,LVM_SETEXTENDEDLISTVIEWSTYLE,dwExMask,dwExStyle

參數 wParam 代表 dwExMask 之意,Windows 用一個雙字組 ( 即 32 位元 ) 來表示 30 個擴充風格,您如果打開 WINDOWS.INC,可以找到

LVS_EX_GRIDLINES                 equ 00000001h
LVS_EX_SUBITEMIMAGES             equ 00000002h
LVS_EX_CHECKBOXES                equ 00000004h
LVS_EX_TRACKSELECT               equ 00000008h
LVS_EX_HEADERDRAGDROP            equ 00000010h
LVS_EX_FULLROWSELECT             equ 00000020h
LVS_EX_ONECLICKACTIVATE          equ 00000040h
LVS_EX_TWOCLICKACTIVATE          equ 00000080h
LVS_EX_FLATSB                    equ 00000100h
LVS_EX_REGIONAL                  equ 00000200h
LVS_EX_INFOTIP                   equ 00000400h
LVS_EX_UNDERLINEHOT              equ 00000800h
LVS_EX_UNDERLINECOLD             equ 00001000h
LVS_EX_MULTIWORKAREAS            equ 00002000h
LVS_EX_LABELTIP                  equ 00004000h
LVS_EX_BORDERSELECT              equ 00008000h
LVS_EX_DOUBLEBUFFER              equ 00010000h
LVS_EX_HIDELABELS                equ 00020000h
LVS_EX_SINGLEROW                 equ 00040000h
LVS_EX_SNAPTOGRID                equ 00080000h
LVS_EX_SIMPLESELECT              equ 00100000h
LVS_EX_JUSTIFYCOLUMNS            equ 00200000h
LVS_EX_TRANSPARENTBKGND          equ 00400000h
LVS_EX_TRANSPARENTSHADOWTEXT     equ 00800000h
LVS_EX_AUTOAUTOARRANGE           equ 01000000h
LVS_EX_HEADERINALLVIEWS          equ 02000000h
LVS_EX_AUTOCHECKSELECT           equ 08000000h
LVS_EX_AUTOSIZECOLUMNS           equ 10000000h
LVS_EX_COLUMNSNAPPOINTS          equ 40000000h
LVS_EX_COLUMNOVERFLOW            equ 80000000h

每個擴充風格以一個位元代表,因此當我們僅僅想改變某個擴充風格,而不影響其他擴充風格時,應將其他風格所代表之位元予以遮罩,而 dwExMask 表示除了此位元會被改變外,其餘位元均不改變。而 dwExStyle 則表示將設定此位元為 1。例如,在預設的情形下,使用者以滑鼠點選 details view 中某個項目時,畫面上只有該項目被高亮度圈住,如果要設定所有子項目也為高亮度所圈住,那麼就得設定 LVS_EX_FULLROWSELECT 擴充風格,如果您又想不影響其他擴充風格,那麼程式應寫成:

        INVOKE  SendMessage,hListView,LVM_SETEXTENDEDLISTVIEWSTYLE,LVS_EX_FULLROWSELECT,LVS_EX_FULLROWSELECT

當您不想讓清單檢視控制項有 LVS_EX_FULLROWSELECT 擴充風格,但又不影響原有的風格時,就可以用下面的程式達成:

        INVOKE  SendMessage,hListView,LVM_SETEXTENDEDLISTVIEWSTYLE,LVS_EX_FULLROWSELECT,0

當您想改變兩種或兩種以上的擴充風格時,dwExMask 與 dwExStyle 也可以是數個擴充風格以 or 運算連接,這樣就省事多了。LVM_SETEXTENDEDLISTVIEWSTYLE 訊息的返回值是原來的擴充風格。

底下小木偶提一提幾個擴充風格。

List view 擴充風格說   明
LVS_EX_BORDERSELECT 此擴充風格會在 details view 顯示方式下,在項目四周圍畫出邊框,您也可以對清單檢視控制項發出 LVM_SETOUTLINECOLOR 訊息改變邊框的顏色,方法是︰
INVOKE  SendMessage,hListView,LVM_SETOUTLINECOLOR,0,COLORREF
如果沒有 LVS_EX_BORDERSELECT 風格,即使發出 LVM_SETOUTLINECOLOR 訊息也無法顯示邊框。
LVS_EX_CHECKBOXES 這個擴充風格會在每個項目前面,顯示一個檢驗盒,可供使用者勾選。
LVS_EX_FULLROWSELECT 在預設的情形下,使用者以滑鼠點選 details view 中某個項目時,畫面上只有該項目被高亮度圈住,如果要設定被點選的子項目也為高亮度所圈住,那麼必須設定 LVS_EX_FULLROWSELECT 擴充風格。這個擴充風格只影響 details view。
LVS_EX_INFOTIP 具有 LVS_EX_INFOTIP 的清單檢視會在滑鼠游標停留在某個項目上時,顯示出工具提示 ( tool tip )。在顯示工具提示之前,會發出 LVN_GETINFOTIP 通知碼給父視窗,以讓父視窗決定要在工具提示上顯示出什麼樣的資料。
LVS_EX_GRIDLINES 會在 details view 顯示方式中,畫出每一列及每個子項目之間的分格線。
LVS_EX_HEADERDRAGDROP 此風格可以讓使用者在 details view 顯示方式下,把滑鼠游標移到欄位名稱,並壓住滑鼠左鍵不放,拖拉欄位名稱到適當位置,調整欄位名稱順序。
LVS_EX_ONECLICKACTIVATE 這個風格會讓滑鼠游標在任何一個項目上滑過時,不管這個項目是否被選定 ,都會改變游標,讓使用者更易察覺。另外,如果使用者以滑鼠點選某個項目,會使清單檢視控制項對父視窗發出 LVN_ITEMACTIVATE 通知碼。
LVS_EX_SUBITEMIMAGES 這個擴充風格能使子項目也能顯示圖示,不過只能在 details view 下看見其效果。方法是以 LVM_SETITEM 呼叫 SendMessage 時,LVITEM 中設定 iImage 及 LVIF_IMAGE。
LVS_EX_TWOCLICKACTIVATE 這個擴充風格能讓滑鼠游標在被選定的項目上滑過時,改變游標,讓使用者更易察覺。另外,如果使用者以滑鼠雙擊某個項目,會使清單檢視控制項對父視窗發出 LVN_ITEMACTIVATE 通知碼。
LVS_EX_TRACKSELECT 此擴充風格會造成「hot-track selection」,亦即滑鼠游標在某個項目上停留一段時間後,此項目就會被選取。至於這段時間是多久,可以發送 LVM_SETHOVERTIME 訊息設定;也可以以 SPI_GETMOUSEHOVERTIME 為參數呼叫 SystemParametersInfo 取得這段時間。系統並不會位每個清單檢視控制項單獨設定這段時間,而是每個清單檢視控制項都使用同樣的停留時間。當滑鼠游標移到某個項目上時會發出 LVN_HOTTRACK 通知碼給父視窗,我們只要檢查此通知碼,就可以知到是否發生「hot-track selection」。
LVS_EX_UNDERLINEHOT 這個風格會讓滑鼠游標在任何一個項目上滑過時,不管這個項目是否被選定 ,都會在項目名稱上添加底線,讓使用者更易察覺。此風格需要設定 LVS_EX_ONECLICKACTIVATE 才能顯出效果。

List View 的通知碼 ( Notification )

清單檢視控制項以通知碼的方式,將訊息傳給父視窗,事實上,不是只有清單檢視如此而已,大部分的通用控制項都以通知碼傳遞訊息給父視窗。前一章提過,請自行參閱。清單檢視的通知碼被包含在一個稱為 NMLISTVIEW 的結構體內 ( 也稱為 NM_LISTVIEW ),父視窗可以從清單檢視控制項傳來的 WM_NOTIFY 訊息中的得知其位址,WM_VOTIFY 的參數,lParam 數值其實就是 NMLISTVIEW 結構體的位址。我們先來看看 NMLISTVIEW 結構體的各欄位︰

NMLISTVIEW      STRUCT
  hdr           NMHDR   <>
  iItem         DWORD   ?       ;發出 WM_NOTIFY 訊息時的項目索引值,若為-1,表示沒有使用
  iSubItem      DWORD   ?       ;發出 WM_NOTIFY 訊息時的子項目索引值,若為 0,表示沒有使用
  uNewState     DWORD   ?       ;發出 WM_NOTIFY 訊息時,該項目的最新狀態
  uOldState     DWORD   ?       ;發出 WM_NOTIFY 訊息時,該項目的舊有狀態
  uChanged      DWORD   ?
  ptAction      POINT   <>
  lParam        LPARAM  ?
NMLISTVIEW      ENDS

NMHDR 結構體欄位如下︰

NMHDR           STRUCT
  hwndFrom      HWND    ?       ;發出 WM_NOTIFY 的清單檢視控制項代碼
  idFrom        UINT    ?       ;發出 WM_NOTIFY 的清單檢視控制項識別碼
  code          UINT    ?       ;通知碼
NMHDR           ENDS

NMHDR 結構體是每個通用控制項都具有的,請參考前一章。ptAction 是當事件發生時,例如以滑鼠點擊某個項目、或雙擊某個項目等等,滑鼠游標所在位置。uChanged 是指發出 WM_NOTIFY 時,某個項目的屬性變成為 uChanged 的內容,其內容和 LVITEM 的 imask 相同。

在組合語言中,要存取 NMLISTVIEW 結構體的內容,一般是以 ASSUME 假指令指定某個暫存器為 NMLISTVIEW 的位址為基準點,當然此步驟僅僅是假指令,並不是真正把記憶體位址存入暫存器堙A所以還須執行 mov 指令,把將此記憶體位址存入該暫存器堙C然後就可以用「[暫存器].hdr.hwndFrom」存取發出 WM_NOTIFY 的清單檢視控制項代碼,以「[暫存器].hdr.idFrom」存取發出 WM_NOTIFY 的清單檢視控制項識別碼。最後還要記得,以「ASSUME 暫存器:PTR NOTHING」恢復該暫存器不以任何位址為基準點。如下面的程式片段︰

.IF uMsg==WM_INITDIALOG

.ELSEIF uMsg==WM_NOTIFY
                push    ebx
                ASSUME  ebx:PTR NM_LISTVIEW     ;把 NMLISTVIEW 起始位址基準點設為 EBX
                mov     ebx,lParam              ;把 NMLISTVIEW 起始位址存入 EBX 
                mov     eax,[ebx].hdr.hwndFrom  ;取得清單檢視控制項代碼,存入 EAX
    .IF eax==hListView
        .IF [ebx].hdr.code==NM_CLICK            ;比較通知碼是否為 NM_CLICK
     
        .ENDIF
    .ENDIF
                ASSUME  ebx:PTR NOTHING         ;取消 EBX 為 NMLISTVIEW 起始位址基準點的設定
                pop     ebx

.ELSEIF uMsg==WM_CLOSE

底下小木偶介紹幾個常用清單檢視控制項的通知碼。

LVN_COLUMNCLICK

當使用者以滑鼠左鍵點按標題時,清單檢視就會發送含有 LVN_COLUMNCLICK 通知碼的 WM_NOTIFY 訊息給父視窗。WM_NOTIFY 訊息中的 lParam 指向一個稱為 NM_LISTVIEW 的結構體,此結構體中的 hdr 含有上面所說得各種資料。其中最重要的,也是父視窗最應該知道的,大概就是使用者按了標題的哪一個子項目呢?這個資料存放於 iSubItem 堙C而此時 iItem 欄位為-1,其他欄位則為 0。

NM_CLICK

當使用者以滑鼠左鍵點按清單檢視中的一個項目時,清單檢視就會發送含有 NM_CLICK 通知碼的 WM_NOTIFY 訊息給父視窗。而 WM_NOTIFY 中的 lParam 參數所指的位址是 NMITEMACTIVATE 結構體位址,它與 NMLISTVIEW 結構體幾乎相同,只多了最後一個欄位,uKeyFlags,如下所示︰

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

uKeyFlags 代表鍵盤上的 Alt、Ctrl、Shift 鍵是否被按下,如果被按下,則 uKeyFlags 之值分別是 LVKF_ALT、LVKF_CONTROL、LVKF_SHIFT。

如果使用者在 details view 中,以滑鼠左鍵點按子項目區域的話,雖也能發送 NM_CLICK 通知碼,但是 NMITEMACTIVATE 結構體中的 iItem 卻是-1,換句話說無法藉由點按子項目而得知使用者選取哪一個項目。如果也要使點按子項目也能選取項目,有兩種方法︰第一是使清單檢視控制項具有 LVS_EX_FULLROWSELECT 風格。第二是在 NM_CLICK 中檢查 iItem 是否為-1,如果是的話,再對清單檢視發出 LVM_SUBITEMHITTEST 訊息,檢查 NMITEMACTIVATE 中的 pcAction 落在哪一個項目中,請參考下面有關 LVM_SUBITEMHITTEST 的說明。


排序

發送 LVM_SORTITEMS 與比較函式 ( Comparison Function )

在建立清單檢視控制項時,可以用 LVS_SORTASCENDING 或 LVS_SORTDESCENDING 風格來指定排序方式,當然您也可以在任何時候,呼叫 SetWindowLong 來改變視窗風格,使之變成升序 ( 在越下面的項目越大 ) 或降序 ( 在越下面的項目越小 )排列。但是這兩種風格只能排序項目名稱,如果想要排序子項目,則必須發送 LVM_SORTITEMS 訊息來完成。在 details view 時,使用者通常可以在標題欄中的欄位上,以滑鼠左鍵點按一次,可以使得清單檢視控制項內的項目依所按欄位排序,在程式內就是發送 LVM_SORTITEMS 訊息,如下︰

        INVOKE  SendMessage,hListView,LVM_SORTITEMS,lParamSort,pfnCompare

lParamSort 是排序方式,這是程式定義的數值,一般就是以某一數代表升序,另一數則代表降序。pfnCompare 是程式設計師自行定義的比較函式位址,如果比較函式的名稱是 compare_func,那麼 pfnCompare 就應該是 OFFSET compare_func。比較函式是被系統所呼叫的副程式,其參數固定,只有三個,其原型是︰

compare_func    PROC    lParam1:PARAM, lParam2:PARAM, lParamSort:DWORD

前兩個參數︰lParam1、lParam2 是項目 LVITEM 堛 lParam 欄位。最後一個參數,lParamSort 是 LVM_SORTITEMS 訊息的倒數第二個參數。當程式執行到「INVOKE SendMessage,hListView,lParamSort,pfnCompare」時,系統會自行挑選兩個項目,並把這兩個項目的 lParam 以及 lParamSort 當做參數,呼叫比較函式,並且連續呼叫好幾次,直到每個項目都與其他項目為 lParam1、lParam2 兩參數呼叫比較函式一次為止。也就像泡沫排序法,把每個要比較的數,與另一數都比較過一次為止。而比較函式所要做的就是決定那一個項目排在前︰如果第一個項目排在前面,則傳回負值;第二個項目排在前面,則傳回正值;如果相等,則傳回 0。

例如,延續上面一個小公司的例子,假設想要對年齡做排序,那麼在視窗函式中部份程式碼如下:

     .IF [ebx].hdr.code==LVN_COLUMNCLICK        ;若使用者按下標題的某個子項目
        .IF [ebx].iSubItem==2   ;若使用者按下標題的「年齡」子項目
                test    flag,1  ;檢查現在是升序還是降序
           .IF ZERO?
              ;若為ZR,表示現在是清單檢視越下面,年齡越大,需變更成越下面,年齡越小
                or      flag,1
                mov     ecx,1   ;ECX=wParam=1,表示越下面,年齡越小
           .ELSE
              ;若為NZ,表示現在是清單檢視越下面,年齡越小,需變更成越下面,年齡越大
                and     flag,0fffffffeh
                mov     ecx,2   ;ECX=wParam=2,表示越下面,年齡越大
           .ENDIF
                INVOKE  SendMessage,hListView,LVM_SORTITEMS,ecx,OFFSET compare_func
        .ENDIF
                call    update_item
     .ENDIF

在此程式中,flag 變數為 0,表示升序;為 1,表示降序。處理 LVN_COLUMNCLICK 時,先檢查是否使用者按下「年齡」欄位,如果是的話,再檢查現在的排序方式是升序還是降序。如果是升序的話,則程式須改成降序排列,並在發送 LVM_SORT 訊息時,lParamSort 設為 1,表示要改成降序排列;若為降序,則要改成升序排列,在發送 LVM_SORT 訊息時,lParamSort 設為 2,表示要改成降序排列。比較函式如下:

compare_func    PROC    lp1:LPARAM,lp2:LPARAM,lParamSort:DWORD
                LOCAL   buffer[20]:BYTE
                lea     ecx,buffer
                mov     lvi.imask,LVIF_TEXT
                mov     lvi.pszText,ecx
                mov     lvi.cchTextMax,20
                mov     lvi.iSubItem,2
                INVOKE  SendMessage,hListView,LVM_GETITEMTEXT,lp1,ADDR lvi
                INVOKE  string_to_dword,ADDR buffer
                push    eax
                INVOKE  SendMessage,hListView,LVM_GETITEMTEXT,lp2,ADDR lvi
                INVOKE  string_to_dword,ADDR buffer
                pop     ecx     ;此時EAX=第二個員工的年齡;ECX=第一個員工的年齡
        .IF lParamSort==1       ;lParamSort=1,表示將要排序,變成越下面年齡越小
                sub     eax,ecx
        .ELSEIF lParamSort==2   ;lParamSort=2,表示將要排序,變成越下面年齡越大
                sub     ecx,eax
                mov     eax,ecx
        .ENDIF
                ret
compare_func    ENDP

在比較函式堙A小木偶呼叫兩次 SendMessage,都是把 LVM_GETITEMTEXT 訊息傳給清單檢視控制項,先後要求清單檢視控制項把系統傳來的兩個項目中,「年齡」的資料傳過來。這兩筆資料是以字串型態存在區域變數 buffer 堙A接著把它們轉換成十六進位數值 ( 由 string_to_dword 副程式負責,轉換後的十六進位數藉由 EAX 返回比較函式 ),第一個員工的年齡存於 ECX,第二個員工的年齡存於 EAX 堙C接下來,根據 lParamSort,決定排序方式是升序還是降序,如果是 1,表示要變更為降序,亦即越下面,年齡越小,以 EAX 減 ECX 得到的差如果為負值,表示第二個員工年齡較小,應該把第一個員工排在前面,依據前面所描述的,應該傳回負值,而 EAX 此時恰為負,所以只要傳回 EAX 並結束比較函式即可;但如果 EAX 減 ECX 之差為正值,表示第二個員工年齡較大,應該把第二個員工排在前面,依據前面所描述的,應該傳回正值,而 EAX 此時恰為正,所以只要傳回 EAX 並結束比較函式即可。反之亦然。

排序方式相減之結果哪一項在前傳回值
要變為降序 ( 越下面越小 )( 第二個 )-( 第一個 )>0 第二個在前
( 第二個 )-( 第一個 )<0第一個在前
要變為升序 ( 越下面越大 )( 第一個 )-( 第二個 )>0 第二個在前
( 第一個 )-( 第二個 )<0第一個在前

整理項目

當排序完成後,各項目的索引 iItem 與原先 lParam 就不再相同了,因此得重新調整。小木偶還是以上面一個小公司員工的例子說明,當排序完成後,結束處理 LVN_COLUMNCLICK 通知碼之前,程式還呼叫了 update_item 副程式,就是重新設定各項目的 lParam。其程式碼如下︰

update_item     PROC
                LOCAL   n:DWORD
                INVOKE  SendMessage,hListView,LVM_GETITEMCOUNT,0,0
                mov     n,eax
                mov     lvi.imask,LVIF_PARAM
                mov     lvi.iSubItem,0
                mov     lvi.iItem,0
        .WHILE n>0
                mov     eax,lvi.iItem
                mov     lvi.lParam,eax
                INVOKE  SendMessage,hListView,LVM_SETITEM,0,ADDR lvi
                inc     lvi.iItem
                dec     n
        .ENDW
                ret
update_item     ENDP

清單檢視控制項的部份訊息

上面已經介紹過幾個有關清單檢視控制項的訊息,如 LVM_SETVIEW 是用來改變顯示方式;LVM_GETCOLUMN、LVM_SETCOLUMN、LVM_DELETECOLUMN 訊息,分別取得、改變 ( 亦即設定 )、刪去清單檢視控制項的某個欄位;LVM_INSERTITEM、LVM_SETITEM、LVM_GETITEM,分別用來新增、設定、取得項目。事實上,還有許多有關清單檢視控制項的訊息,請查閱 MSDN,底下僅列出待會範例需要用到的訊息。List view 所有的訊息開頭四個字都是「LVM_」,表示 list view message 之意。

LVM_GETITEMCOUNT

這個訊息是用來統計清單檢視控制項埵陷X個項目,程式如下:

        INVOKE  SendMessage,hListView,LVM_GETITEMCOUNT,wParam,lParam

其中 wParam 和 lParam 都必須設為 0,傳回值存於 EAX,即項目個數。

LVM_GETITEMRECT

這個訊息是用來取得當前顯示方式的矩形邊界,如下面程式:

        INVOKE  SendMessage,hListView,LVM_GETITEMRECT,wParam,lParam

wParam 是項目的索引值,lParam 是 RECT 結構體位址。此訊息會把索引值為 wParam 的項目所佔用的矩形填入 lParam 所指的結構體堙CLVM_GETITEMRECT 還可以選擇是圖示或是項目名稱……所佔用的矩形,方法是呼叫 SendMessage 前,先設定 RECT 的 left 欄位。此欄位可以是下面四種數值:

  1. LVIR_BOUNDS:取得整個項目所佔的矩形,包含圖示及項目名稱。
  2. LVIR_ICON:僅取得圖示所佔的矩形大小。
  3. LVIR_LABEL:僅取得項目名稱所佔的矩形大小。
  4. LVIR_SELECTBOUNDS:如果沒有設置選中全行,那麼獲得的是圖示及項目名稱所佔的矩形;如果設置選中全行,那麼獲得的是整行的邊框 ( 包括圖標,但不包括小空白 )。

LVM_GETITEMTEXT

這個訊息是用來取得某個項目或某個子項目的名稱,方法是呼叫 SendMessage:

        INVOKE  SendMessage,hListView,LVM_GETITEMTEXT,wParam,lParam

其中 wParam 是要取得的項目索引值,lParam 是 LVITEM 結構體位址。如果要取得某個項目的名稱,LVITEM 的 iSuItem 必須為 0,iItem 可不填,因為在 wParam 中已指定,而 pszText、cchTextMax 分別代表要取得的名稱存放位址及該位址的長度,必須在呼叫 SendMessage 之前填好。如果要取得某個子項目的名稱,LVITEM 的 iSuItem 必須設為該子項目的索引值。

當然,您也可以用 LVM_GETITEM 來做 LVM_GETITEMTEXT 做的事,但是 LVITEM 中的 iItem 就必須填好適當的數值。此外 LV_GETITEM 所能獲得的資料比 LVM_GETITEMTEXT 多。

LVM_HITTEST

測試清單檢視內的某一點,落在哪一個項目上。方法是呼叫 SendMessage:

        INVOKE  SendMessage,hListView,LVM_HITTEST,wParam,lParam

參數 wParam 必須是零,lParam 是一個位址指標,指向 LVHITTESTINFO 結構體 ( 也稱為 LV_HITTESTINFO 結構體 ),LVHITTESTINFO 結構體的各欄位是:

LVHITTESTINFO   STRUCT
pt              POINT   <>
flags           DWORD   ?
iItem           DWORD   ?
iSubItem        DWORD   ?
LVHITTESTINFO   ENDS

若呼叫 SendMessage 後,成功返回,返回值為測試點所在之項目索引值;若失敗,返回-1。

LVM_SETICONSPACING

這個訊息是設定 icon view 中圖示的間隔距離,方法是呼叫 SendMessage:

        INVOKE  SendMessage,hListView,LVM_SETICONSPACING,wParam,lParam

參數 wParam 必須是零,lParam 是要設定的間隔距離,以點為單位,低字組的數值代表水平寬度 ( 也就是間隔 );高字組的數值代表垂直高度 ( 距離 )。這兩個數值是由項目所佔畫面的左上角開始算起,如果這兩個數值比圖示寬度、高度小的話,項目就會重疊在一起,因此最好要比圖示來得大些,另外也要考慮項目名稱的大小。

LVM_SUBITEMHITTEST

此訊息是用來檢測某個點落在清單檢視的哪一個子項目上。呼叫方式為︰

        INVOKE  SendMessage,hListView,LVM_SUBITEMHITTEST,wParam,lParam

其中 wParam 必須是 0,lParam 是指向一個稱為 LVHITTESTINFO 結構體的位址。LVHITTESTINFO 各欄位見 LVM_HITTEST。LVHITTESTINFO 結構體中的 pt 就是要測試的點座標,此座標是以清單檢視控制項的工作區為原點,如右圖紅色箭頭尖端所指之處。

在以 LVM_SUBITEMHITTEST 為參數呼叫 SendMessage 之前,應先填好 pt 欄位的測試點座標,呼叫成功後,系統會在 iItem、iSubItem、iGroup 填好該點落在哪一個項目、子項目、群組;若失敗,則這些欄位為-1。


一個例子︰SELICON

最後小木偶附上一個例子︰SELICON,它可以讓使用者選擇一個圖示檔、可執行檔,或是動態連結程式庫,然後 SELICON 會把該檔中所有圖示擷取出來,並顯示在清單檢視控制項堙A讓使用者在其中挑選一個圖示並存成圖示檔。其原始碼可按此處下載。


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