ListView 是 Common Controls 中最強大的列表呈現控件,支援四種檢視模式:
本文聚焦 Report 模式,實作「列出當前目錄檔案 (檔名/大小)、點擊欄位標題排序、選單切換檢視模式、雙擊顯示檔名」完整範例。
SysListView32,風格含 LVS_REPORT。LVM_INSERTCOLUMN + LV_COLUMN 結構 (標題、寬度、對齊)。LVM_INSERTITEM (主項目, iSubItem=0) + LVM_SETITEM (子項目, iSubItem>0)。WM_NOTIFY 接收 LVN_COLUMNCLICK (排序)、NM_DBLCLK (雙擊)。LV_COLUMN STRUCT
imask DWORD ? ; 有效旗標: LVCF_TEXT, LVCF_WIDTH, LVCF_FMT, LVCF_SUBITEM
fmt DWORD ? ; 對齊: LVCFMT_LEFT, CENTER, RIGHT
lx DWORD ? ; 欄位寬度 (像素)
pszText DWORD ? ; 標題字串指標
cchTextMax DWORD ? ; 查詢時緩衝區大小
iSubItem DWORD ? ; 關聯子項目索引
iImage DWORD ? ; 圖示索引
iOrder DWORD ? ; IE3+ 相容
LV_COLUMN ENDS
LV_ITEM STRUCT
imask DWORD ? ; LVIF_TEXT, LVIF_PARAM, LVIF_IMAGE, LVIF_STATE...
iItem DWORD ? ; 項目索引 (行號, 0-based)
iSubItem DWORD ? ; 子項目索引 (欄號, 0=主項目)
state DWORD ? ; 狀態旗標
stateMask DWORD ? ; 狀態遮罩
pszText DWORD ? ; 文字指標
cchTextMax DWORD ? ; 緩衝區大小
iImage DWORD ? ; 圖示索引
lParam DWORD ? ; 應用自訂資料 (排序關鍵!)
iIndent DWORD ? ; 縮排
LV_ITEM ENDS
.data
ListViewClassName db "SysListView32",0
Heading1 db "Filename",0
Heading2 db "Size",0
.code
; WM_CREATE 中
invoke CreateWindowEx, NULL, addr ListViewClassName, NULL, \
LVS_REPORT+WS_CHILD+WS_VISIBLE, 0,0,0,0, hWnd, NULL, hInstance, NULL
mov hList, eax
; InsertColumn 程序
InsertColumn proc
LOCAL lvc:LV_COLUMN
; 欄位 0: Filename (左對齊, 150px)
mov lvc.imask, LVCF_TEXT+LVCF_WIDTH
mov lvc.pszText, offset Heading1
mov lvc.lx, 150
invoke SendMessage, hList, LVM_INSERTCOLUMN, 0, addr lvc
; 欄位 1: Size (右對齊, 100px) -> 需加 LVCF_FMT
or lvc.imask, LVCF_FMT
mov lvc.fmt, LVCFMT_RIGHT
mov lvc.pszText, offset Heading2
mov lvc.lx, 100
invoke SendMessage, hList, LVM_INSERTCOLUMN, 1, addr lvc
ret
InsertColumn endp
FillFileInfo proc uses edi
LOCAL finddata:WIN32_FIND_DATA
LOCAL FHandle:DWORD
invoke FindFirstFile, addr FileNamePattern, addr finddata ; "*.*"
.if eax != INVALID_HANDLE_VALUE
mov FHandle, eax
xor edi, edi ; edi = 行號 (iItem)
.while eax != 0
test finddata.dwFileAttributes, FILE_ATTRIBUTE_DIRECTORY
.if ZERO? ; 略過目錄,只顯示檔案
invoke ShowFileInfo, edi, addr finddata
inc edi
.endif
invoke FindNextFile, FHandle, addr finddata
.endw
invoke FindClose, FHandle
.endif
ret
FillFileInfo endp
ShowFileInfo proc uses edi row:DWORD, lpFind:DWORD
LOCAL lvi:LV_ITEM
LOCAL buffer[20]:BYTE
mov edi, lpFind
assume edi:ptr WIN32_FIND_DATA
; --- 插入主項目 (檔名, 第 0 欄) ---
mov lvi.imask, LVIF_TEXT+LVIF_PARAM
push row
pop lvi.iItem
mov lvi.iSubItem, 0
lea eax, [edi].cFileName
mov lvi.pszText, eax
push row
pop lvi.lParam ; lParam 存行號,供排序時查詢用
invoke SendMessage, hList, LVM_INSERTITEM, 0, addr lvi
; --- 插入子項目 (大小, 第 1 欄) ---
mov lvi.imask, LVIF_TEXT
inc lvi.iSubItem ; = 1
invoke wsprintf, addr buffer, addr template, [edi].nFileSizeLow ; template = "%lu"
lea eax, buffer
mov lvi.pszText, eax
invoke SendMessage, hList, LVM_SETITEM, 0, addr lvi
assume edi:nothing
ret
ShowFileInfo endp
點擊欄位標題觸發 LVN_COLUMNCLICK,呼叫 LVM_SORTITEMS 傳入比較函式指標。比較函式接收兩個項目的 lParam,透過 LVM_GETITEMTEXT 取得文字比較。
; WM_NOTIFY -> LVN_COLUMNCLICK 處理
.if [edi].code == LVN_COLUMNCLICK
assume edi:ptr NM_LISTVIEW
.if [edi].iSubItem == 1 ; 點擊 Size 欄
.if SizeSortOrder == 0 || SizeSortOrder == 2
invoke SendMessage, hList, LVM_SORTITEMS, 1, addr CompareFunc ; 升序
invoke UpdatelParam
mov SizeSortOrder, 1
.else
invoke SendMessage, hList, LVM_SORTITEMS, 2, addr CompareFunc ; 降序
invoke UpdatelParam
mov SizeSortOrder, 2
.endif
.else ; 點擊 Filename 欄
.if FileNameSortOrder == 0 || FileNameSortOrder == 4
invoke SendMessage, hList, LVM_SORTITEMS, 3, addr CompareFunc ; 升序
invoke UpdatelParam
mov FileNameSortOrder, 3
.else
invoke SendMessage, hList, LVM_SORTITEMS, 4, addr CompareFunc ; 降序
invoke UpdatelParam
mov FileNameSortOrder, 4
.endif
.endif
.endif
;--------------------------------------------------
; CompareFunc: 依 SortType (wParam) 決定比較邏輯
;--------------------------------------------------
CompareFunc proc uses edi lParam1:DWORD, lParam2:DWORD, SortType:DWORD
LOCAL buffer[256]:BYTE
LOCAL buffer1[256]:BYTE
LOCAL lvi:LV_ITEM
mov lvi.imask, LVIF_TEXT
lea eax, buffer
mov lvi.pszText, eax
mov lvi.cchTextMax, 256
.if SortType == 1 ; Size 升序
mov lvi.iSubItem, 1
invoke SendMessage, hList, LVM_GETITEMTEXT, lParam1, addr lvi
invoke String2Dword, addr buffer
mov edi, eax
invoke SendMessage, hList, LVM_GETITEMTEXT, lParam2, addr lvi
invoke String2Dword, addr buffer
sub edi, eax ; edi - eax < 0 表示第一個較小 -> 排前面
mov eax, edi
.elseif SortType == 2 ; Size 降序
mov lvi.iSubItem, 1
invoke SendMessage, hList, LVM_GETITEMTEXT, lParam1, addr lvi
invoke String2Dword, addr buffer
mov edi, eax
invoke SendMessage, hList, LVM_GETITEMTEXT, lParam2, addr lvi
invoke String2Dword, addr buffer
sub eax, edi ; 反向
.elseif SortType == 3 ; Filename 升序 (不分大小寫)
mov lvi.iSubItem, 0
invoke SendMessage, hList, LVM_GETITEMTEXT, lParam1, addr lvi
invoke lstrcpy, addr buffer1, addr buffer
invoke SendMessage, hList, LVM_GETITEMTEXT, lParam2, addr lvi
invoke lstrcmpi, addr buffer1, addr buffer
.else ; SortType == 4 Filename 降序
mov lvi.iSubItem, 0
invoke SendMessage, hList, LVM_GETITEMTEXT, lParam1, addr lvi
invoke lstrcpy, addr buffer1, addr buffer
invoke SendMessage, hList, LVM_GETITEMTEXT, lParam2, addr lvi
invoke lstrcmpi, addr buffer, addr buffer1
.endif
ret
CompareFunc endp
; 排序後必須更新所有項目 lParam 為新索引,否則下次排序索引錯誤
UpdatelParam proc uses edi
LOCAL lvi:LV_ITEM
invoke SendMessage, hList, LVM_GETITEMCOUNT, 0, 0
mov edi, eax
mov lvi.imask, LVIF_PARAM
mov lvi.iSubItem, 0
mov lvi.iItem, 0
.while edi > 0
push lvi.iItem
pop lvi.lParam
invoke SendMessage, hList, LVM_SETITEM, 0, addr lvi
inc lvi.iItem
dec edi
.endw
ret
UpdatelParam endp
.elseif [edi].code == NM_DBLCLK
invoke ShowCurrentFocus
ShowCurrentFocus proc
LOCAL lvi:LV_ITEM
LOCAL buffer[256]:BYTE
; 取得具有焦點的項目索引
invoke SendMessage, hList, LVM_GETNEXTITEM, -1, LVNI_FOCUSED
mov lvi.iItem, eax
mov lvi.iSubItem, 0
mov lvi.imask, LVIF_TEXT
lea eax, buffer
mov lvi.pszText, eax
mov lvi.cchTextMax, 256
invoke SendMessage, hList, LVM_GETITEM, 0, addr lvi
invoke MessageBox, 0, addr buffer, addr AppName, MB_OK
ret
ShowCurrentFocus endp
.elseif uMsg == WM_COMMAND
.if lParam == 0
invoke GetWindowLong, hList, GWL_STYLE
and eax, not LVS_TYPEMASK ; 清除舊檢視風格
mov edx, wParam
and edx, 0FFFFh ; 低字 = 新風格 (菜單 ID 即風格值)
push edx
or eax, edx
invoke SetWindowLong, hList, GWL_STYLE, eax
pop edx
invoke CheckMenuRadioItem, hMenu, IDM_ICON, IDM_LIST, edx, MF_CHECKED
.endif
技巧:菜單 ID 直接定義為風格常數 (IDM_ICON equ LVS_ICON 等),wParam 直接拿來用。
.elseif uMsg == WM_SIZE
mov eax, lParam
mov edx, eax
and eax, 0FFFFh ; 寬度
shr edx, 16 ; 高度
invoke MoveWindow, hList, 0, 0, eax, edx, TRUE
| 需求 | 關鍵 API/訊息 | 備註 |
|---|---|---|
| 大量資料效能 | LVS_OWNERDATA (虛擬模式) | 不儲存資料於控件,透過 LVN_GETDISPINFO 按需提供,支援百萬列。 |
| 全選/反選 | LVM_SETITEMSTATE + LVIS_SELECTED | 配合 LVM_GETNEXTITEM 迴圈。 |
| 拖曳重排欄位 | LVM_SETCOLUMNORDERARRAY | 需 LVS_EX_HEADERDRAGDROP 擴充風格。 |
| 自訂繪製 | NM_CUSTOMDRAW | 回應 CDDS_ITEMPREPAINT 修改字體/顏色/背景。 |
| 圖示支援 | ImageList_Create + LVM_SETIMAGELIST | 同 TreeView 圖示列表建立流程。 |