Tutorial 31: Listview Control

We will learn how to create and use the listview control in this tutorial.

Download the example.

Theory:

A listview control is one of the common controls like treeview, richedit etc. You are familiar with it even if you may not know it by its name. For example, the right pane of Windows Explorer is a listview control. A listview control is good for displaying items. In this regard, it's like a listbox but with enhanced capabilities.
You can create a listview control in two ways. The first method is also the easiest one: create it with a resource editor. Just don't forget to call InitCommonControls within your asm source code. The other method is to call CreateWindowEx in your source code. You must specify the correct window class name for the control, ie. SysListView32. The window class "WC_LISTVIEW" is incorrect.
There are four methods of viewing data in a listview control: icon, small icon, list and report views. You can see examples of these views by selecting View->Large Icons (icon view), Small Icons (small icon view), List (list view) and Details (report view). Views are just data representation methods:they only affect the appearances of data. For example, you can have a lot of data in the listview control, but if you want, you can view only some of them. Report view is the most informative one while the remaining views give less info. You can specify the view you want when you create a listview control. You can later change the view by calling SetWindowLong, specifying GWL_STYLE flag.

Now that we know how to create a listview control, we will continue on how to use it. I'll focus on report view which can demonstrate many features of listview control. The steps in using a listview control are as follows:

  1. Create a listview control with CreateWindowEx, specifying SysListView32 as the class name. You can specify the initial view at this time.
  2. (if exists) Create and initialize image lists to be used with the listview items.
  3. Insert column(s) into the listview control. This step is necessary if the listview control will use report view.
  4. Insert items and subitems into the listview control.

Columns

In report view, there are one or more columns. You can think of the arrangement of data in the report view as a table: the data are arranged in rows and columns. You must have at least one column in your listview control (only in report view). In views other than report, you need not insert a column because there can be one and only one column in those views.
You can insert a column by sending LVM_INSERTCOLUMN to the listview control.

LVM_INSERTCOLUMN
wParam = iCol
lParam = pointer to a LV_COLUMN structure

iCol is the column number, starting from 0.
LV_COLUMN contains information about the column to be inserted. It has the following definition:

LV_COLUMN STRUCT
  imask dd ?
  fmt dd ?
  lx dd ?
  pszText dd ?
  cchTextMax dd ?
  iSubItem dd ?
  iImage dd ?
  iOrder dd ?
LV_COLUMN ENDS

Field name Meanings
imask

A collection of flags that governs which members in this structure are valid. The reason behind this member is that not all members in this structure are used at the same time. Some members are used in some situations. And this structure is used both for input and output. Thus it's important that you *mark* the members that are used in this call to Windows so Windows knows which members are valid. The available flags are:

LVCF_FMT = The fmt member is valid.
LVCF_SUBITEM = The iSubItem member is valid.
LVCF_TEXT = The pszText member is valid.
LVCF_WIDTH = The lx member is valid.

You can combine the above flags. For example, if you want to specify the text label of the column, you must supply the pointer to the string in pszText member. And you must tell Windows that pszText member contains data by specifying LVCF_TEXT flag in this field else Windows will ignore the value in pszText.

fmt

Specify the alignment of items/subitems in the column. The available values are:

LVCFMT_CENTER = Text is centered.
LVCFMT_LEFT = Text is left-aligned.
LVCFMT_RIGHT = Text is right-aligned.

lx The width of the column, in pixels. You can later change the width of the column with LVM_SETCOLUMNWIDTH.
pszText Contains a pointer to the name of the column if this structure is used to set the column's properties. If this structure is used to receive the properties of a column, this field contains a pointer to a buffer large enough to receive the name of the column that will be returned. In that case, you must give the size of the buffer in cchTextMax below. You can ignore cchTextMax if you want to set the name of the column because the name must be an ASCIIZ string which Windows can determine the length.
cchTextMax The size, in bytes, of the buffer specified in pszText above. This member is used only when you use this structure to receive info about a column. If you use this structure to set the properties of a column, this field is ignored.
iSubItem Specify the index of subitem associated with this column. This value is used as a marker which subitem this column is associated with. If you want, you can specify an absurd number in this field and your listview control will still run like a breeze. The use of this field is best demonstrated when you have the column number and need to know with which subitem this column is associated. You can query the listview control by sending LVM_GETCOLUMN message to it, specifying LVCF_SUBITEM in the imask member. The listview control will fill the iSubItem member with whatever value you specify in this field when the column is inserted. In order for this method to work, you need to input the correct subitem index into this field.
iImage and iOrder Used with Internet Explorer 3.0 upwards. I don't have info about them.

So after the listview control is created, you should insert one or more columns into it. Columns are not necessary if you don't plan to switch the listview control into report view. In order to insert a column, you need to create a LV_COLUMN structure, fill it with necessary information, specify the column number and then send the structure to the listview control with LVM_INSERTCOLUMN message.

   LOCAL lvc:LV_COLUMN
   mov lvc.imask,LVCF_TEXT+LVCF_WIDTH
   mov lvc.pszText,offset Heading1
   mov lvc.lx,150
   invoke SendMessage,hList, LVM_INSERTCOLUMN,0,addr lvc

The above code snippet demonstrates the process. It specifies the column header text and its width then send LVM_INSERTCOLUMN message to the listview control. It's that simple.

Items and subitems

Items are the main entries in the listview control. In views other than report view, you will only see items in the listview control. Subitems are details of the items. An item may have one or more associated subitems. For example, if the item is the name of a file, then you can have the file attributes, its size, the date of file creation as subitems. In report view, the leftmost column contains items and the remaining columns contain subitems. You can think of an item and its subitems as a database record. The item is the primary key of the record and the subitems are fields in the record.
At the bare minimum, you need some items in your listview control: subitems are not necessary. However, if you want to give the user more information about the items, you can associate items with subitems so the user can see the details in the report view.
You insert an item into a listview control by sending LVM_INSERTITEM message to it. You also need to pass the address of an LV_ITEM structure to it in lParam. LV_ITEM has the following definition:

LV_ITEM STRUCT
  imask dd ?
  iItem dd ?
  iSubItem dd ?
  state dd ?
  stateMask dd ?
  pszText dd ?
  cchTextMax dd ?
  iImage dd ?
  lParam dd ?
  iIndent dd ?
LV_ITEM ENDS

Field name Meanings
imask A collection of flags indicating which members in this structure are valid for this call. In general, this field is similar to imask member of LV_COLUMN above. Check your win32 api reference for more detail on the available flags.
iItem The index of the item this structure refers to. The index is zero-based. You can think of this field as containing the "row" number of a table.
iSubItem The index of the subitem associated with the item specified by iItem above. You can think of this field as containing the "column" of a table. For example, if you want to insert an item into a newly created listview control, the value in iItem would be 0 (because this item is the first one), and the value in iSubItem would also be 0 (we want to insert the item into the first column). If you want to specify a subitem associated with this item, the iItem would be the index of the item you want to associate with (in the above example, it's 0), the iSubItem would be 1 or greater, depending on which column you want to insert the subitem into. For example, if your listview control has 4 columns, the first column will contain the items. The remaining 3 columns are for subitems. If you want to insert a subitem into the 4th column, you need to specify the value 3 in iSubItem.
state

This member contains flags that reflect the status of the item. The state of an item can change because of the user's actions or it can be modified by our program. The state includes whether the item has the focus/is hilited/is selected for cut operation/is selected. In addition to the state flags, It can also contains one-based index into the overlay image/state image for use by the item.

stateMask Since the state member above can contain the state flags, overlay image index , and state image index, we need to tell Windows which value we want to set or retrieve. The value in this field is for such use.
pszText The address of an ASCIIZ string that will be used as the label of the item in the case we want to set/insert the item. In the case that we use this structure to retrieve the item's property, this member must contain the address of a buffer that will be filled with the label of the item.
cchTextMax This field is used only when you use this structure to receive info about an item. In this case, this field contains the size in bytes of the buffer specified in pszText above.
iImage The index into the imagelist containing the icons for the listview control. This index points to the icon to be used with this item.
lParam A user-defined value that will be used when you sort items in the listview control. In short, when you tell the listview control to sort the items, the listview control will compare the items in pairs. It will send the lParam values of both items to you so you can decide which of the two should be listed first. If you're still hazy about this, don't worry. You'll learn more about sorting later.

Let's summarize the steps in inserting an item/subitem into a listview control.

  1. Create a variable of type LV_ITEM structure
  2. Fill it with necessary information
  3. Send LVM_INSERTITEM message to the listview control if you want to insert an item. Or if you want to *insert* a subitem, send LVM_SETITEM instead. This is rather confusing if you don't understand the relationship between an item and its subitems. Subitems are considered as properties of an item. Thus you can insert items but not subitems and you can't have a subitem without an associated item. That's why you need to send LVM_SETITEM message to add a subitem instead of LVM_INSERTITEM.

ListView Messages/Notifications

Now that you know how to create and populate a listview control, the next step is to communicate with it. A listview control communicates with the parent window via messages and notifications. The parent window can control the listview control by sending messages to it. The listview control notifies the parent of important/interesting events via WM_NOTIFY message, just like other common controls.

Sorting items/subitems

You can specify the default sorting order of a listview control by specifying LVS_SORTASCENDING or LVS_SORTDESCENDING styles in CreateWindowEx. These two styles order the items using their labels only. If you want to sort the items in other ways, you need to send LVM_SORTITEMS message to the listview control.

LVM_SORTITEMS
wParam = lParamSort
lParam = pCompareFunction

lParamSort is a user-defined value that will be passed to the compare function. You can use this value in any way you want.
pCompareFunction is the address of the user-defined function that will decide the outcome of the comparison of items in the listview control. The function has the following prototype:

CompareFunc proto lParam1:DWORD, lParam2:DWORD, lParamSort:DWORD

lParam1 and lParam2 are the values in lParam member of LV_ITEM that you specify when you insert the items into the listview control.
lParamSort is the value in wParam you sent with LVM_SORTITEMS

When the listview control receives LVM_SORTITEMS message, it calls the compare function specified in lParam of the message when it needs to ask us for the result of comparison between two items. In short, the comparison function will decide which of the two items sent to it will precede the other. The rule is simple: if the function returns a negative value, the first item (represented by lParam1) should precede the other. If the function returns a positive value, the second item (represented by lParam2) should precede the first one. If both items are equal, it must return zero.

What makes this method work is the value in lParam of LV_ITEM structure. If you need to sort the items (such as when the user clicks on a column header), you need to think of a sorting scheme that makes use of the values in lParam member. In the example, I put the index of the item in this field so I can obtain other information about the item by sending LVM_GETITEM message. Note that when the items are rearranged, their indexes also change. So when the sorting is done in my example, I need to update the values in lParam to reflect the new indexes. If you want to sort the items when the user clicks on a column header, you need to process LVN_COLUMNCLICK notification message in your window procedure. LVN_COLUMNCLICK is passed to your window proc via WM_NOTIFY message.

Example:

This example creates a listview control and fills it with the names and sizes of the files in the current folder. The default view is the report one. In the report view, you can click on the column heads and the items will be sorted in ascending/descending order. You can select the view you want via the menu. When you double-click on an item, a message box showing the label of the item is displayed.

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

IDM_MAINMENU equ 10000
IDM_ICON equ LVS_ICON
IDM_SMALLICON equ LVS_SMALLICON
IDM_LIST equ LVS_LIST
IDM_REPORT equ LVS_REPORT

RGB macro red,green,blue
  xor eax,eax
  mov ah,blue
  shl eax,8
  mov ah,green
  mov al,red
endm

.data
ClassName db "ListViewWinClass",0
AppName db "Testing a ListView Control",0
ListViewClassName db "SysListView32",0
Heading1 db "Filename",0
Heading2 db "Size",0
FileNamePattern db "*.*",0
FileNameSortOrder dd 0
SizeSortOrder dd 0
template db "%lu",0


.data?
hInstance HINSTANCE ?
hList dd ?
hMenu dd ?

.code
start:
  invoke GetModuleHandle, NULL
  mov hInstance,eax
  invoke WinMain, hInstance,NULL, NULL, SW_SHOWDEFAULT
  invoke ExitProcess,eax
  invoke InitCommonControls
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
  LOCAL wc:WNDCLASSEX
  LOCAL msg:MSG
  LOCAL hwnd:HWND

  mov wc.cbSize,SIZEOF WNDCLASSEX
  mov wc.style, NULL
  mov wc.lpfnWndProc, OFFSET WndProc
  mov wc.cbClsExtra,NULL
  mov wc.cbWndExtra,NULL
  push hInstance
  pop wc.hInstance
  mov wc.hbrBackground,COLOR_WINDOW+1
  mov wc.lpszMenuName,IDM_MAINMENU
  mov wc.lpszClassName,OFFSET ClassName
  invoke LoadIcon,NULL,IDI_APPLICATION
  mov wc.hIcon,eax
  mov wc.hIconSm,eax
  invoke LoadCursor,NULL,IDC_ARROW
  mov wc.hCursor,eax
  invoke RegisterClassEx, addr wc
  invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInst,NULL
  mov hwnd,eax
  invoke ShowWindow, hwnd,SW_SHOWNORMAL
  invoke UpdateWindow, hwnd
  .while TRUE
    invoke GetMessage, ADDR msg,NULL,0,0
    .break .if (!eax)
      invoke TranslateMessage, ADDR msg
      invoke DispatchMessage, ADDR msg
  .endw
  mov eax,msg.wParam
  ret
WinMain endp

InsertColumn proc
  LOCAL lvc:LV_COLUMN

  mov lvc.imask,LVCF_TEXT+LVCF_WIDTH
  mov lvc.pszText,offset Heading1
  mov lvc.lx,150
  invoke SendMessage,hList, LVM_INSERTCOLUMN, 0, addr lvc
 
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

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
  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
  invoke SendMessage,hList, LVM_INSERTITEM,0, addr lvi
  mov lvi.imask,LVIF_TEXT
  inc lvi.iSubItem
  invoke wsprintf,addr buffer, addr template,[edi].nFileSizeLow
  lea eax,buffer
  mov lvi.pszText,eax
  invoke SendMessage,hList,LVM_SETITEM, 0,addr lvi
  assume edi:nothing
  ret
ShowFileInfo 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
    .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

String2Dword proc uses ecx edi edx esi String:DWORD
  LOCAL Result:DWORD

  mov Result,0
  mov edi,String
  invoke lstrlen,String
  .while eax!=0
    xor edx,edx
    mov dl,byte ptr [edi]
    sub dl,"0"
    mov esi,eax
    dec esi
    push eax
    mov eax,edx
    push ebx
    mov ebx,10
    .while esi > 0
      mul ebx
      dec esi
    .endw
    pop ebx
    add Result,eax
    pop eax
    inc edi
    dec eax
  .endw
  mov eax,Result
  ret
String2Dword endp

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
    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
    mov eax,edi
  .elseif SortType==2
    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
    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
    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

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

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

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
  .if uMsg==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
    invoke InsertColumn
    invoke FillFileInfo
    RGB 255,255,255
    invoke SendMessage,hList,LVM_SETTEXTCOLOR,0,eax
    RGB 0,0,0
    invoke SendMessage,hList,LVM_SETBKCOLOR,0,eax
    RGB 0,0,0
    invoke SendMessage,hList,LVM_SETTEXTBKCOLOR,0,eax
    invoke GetMenu,hWnd
    mov hMenu,eax
    invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, IDM_REPORT,MF_CHECKED
  .elseif uMsg==WM_COMMAND
    .if lParam==0
      invoke GetWindowLong,hList,GWL_STYLE
      and eax,not LVS_TYPEMASK
      mov edx,wParam
      and edx,0FFFFh
      push edx
      or eax,edx
      invoke SetWindowLong,hList,GWL_STYLE,eax
      pop edx
      invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, edx,MF_CHECKED
    .endif
  .elseif uMsg==WM_NOTIFY
    push edi
    mov edi,lParam
    assume edi:ptr NMHDR
    mov eax,[edi].hwndFrom
    .if eax==hList
      .if [edi].code==LVN_COLUMNCLICK
        assume edi:ptr NM_LISTVIEW
        .if [edi].iSubItem==1
          .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
          .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
        assume edi:ptr NMHDR
      .elseif [edi].code==NM_DBLCLK
        invoke ShowCurrentFocus
      .endif
    .endif
    pop edi
  .elseif uMsg==WM_SIZE
   
mov eax,lParam
    mov edx,eax
    and eax,0ffffh
    shr edx,16
    invoke MoveWindow,hList, 0, 0, eax,edx,TRUE
  .elseif uMsg==WM_DESTROY
    invoke PostQuitMessage,NULL
  .else
    invoke DefWindowProc,hWnd,uMsg,wParam,lParam
    ret
  .endif
  xor eax,eax
  ret
WndProc endp
end start

Analysis:

The first thing the program does when the main window is created is to create a listview control.

  .if uMsg==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

We call CreateWindowEx, passing itthe name of the window class "SysListView32". The default view is the report view as specified by LVS_REPORT style.

    invoke InsertColumn

After the listview control is created, we insert columns into it.

  LOCAL lvc:LV_COLUMN

  mov lvc.imask,LVCF_TEXT+LVCF_WIDTH
  mov lvc.pszText,offset Heading1
  mov lvc.lx,150
  invoke SendMessage,hList, LVM_INSERTCOLUMN, 0, addr lvc

We specify the label and the width of the first column, for storing the names of the files, in LV_COLUMN structure thus we need to set imask with LVCF_TEXT and LVCF_WIDTH flags. We fill pszText with the address of the label and lx with the width of the column, in pixels. When all is done, we send LVM_INSERTCOLUMN message to the listview control, passing the structure to it.

  or lvc.imask,LVCF_FMT
  mov lvc.fmt,LVCFMT_RIGHT

When we are done with the insertion of the first column, we insert another column for storing the sizes of the files. Since we need the sizes to right-align in the column, we need to specify a flag in fmt member, LVCFMT_RIGHT. We must also specify LVCF_FMT flag in imask, in addition to LVCF_TEXT and LVCF_WIDTH.

  mov lvc.pszText,offset Heading2
  mov lvc.lx,100
  invoke SendMessage,hList, LVM_INSERTCOLUMN, 1 ,addr lvc

The remaining code is simple. Put the address of the label in pszText and the width in lx. Then send LVM_INSERTCOLUMN message to the listview control, specifying the column number and the address of the structure.

When the columns are inserted, we can fill items in the listview control.

    invoke FillFileInfo

FillFileInfo has the following code.

FillFileInfo proc uses edi
  LOCAL finddata:WIN32_FIND_DATA
  LOCAL FHandle:DWORD

  invoke FindFirstFile,addr FileNamePattern,addr finddata

We call FindFirstFile to obtain the information of the first file that matches the search criteria. FindFirstFile has the following prototype:

FindFirstFile proto pFileName:DWORD, pWin32_Find_Data:DWORD

pFileName is the address of the filename to search for. This string can contain wildcards. In our example, we use *.*, which amounts to search for all the files in the current folder.
pWin32_Find_Data is the address of the WIN32_FIND_DATA structure that will be filled with information about the file (if found).

This function returns INVALID_HANDLE_VALUE in eax if no matching file is found. Otherwise it returns a search handle that will be used in subsequent FindNextFile calls.

  .if eax!=INVALID_HANDLE_VALUE
    mov FHandle,eax
    xor edi,edi

If a file is found, we store the search handle in a variable and then zero out edi which will be used as the index into the items (row number).

    .while eax!=0
      test finddata.dwFileAttributes,FILE_ATTRIBUTE_DIRECTORY
      .if ZERO?

In this tutorial, I don't want to deal with the folders yet so I filter them out by checking dwFileAttributes for files which have FILE_ATTRIBUTE_DIRECTORY flag set. If they are found, I skip to call FindNextFile.

          invoke ShowFileInfo,edi, addr finddata
          inc edi
      .endif

      invoke FindNextFile,FHandle,addr finddata     
    .endw

We insert the name and size of the file into the listview control by calling ShowFileInfo function. Then we increase the current row number in edi. Lastly we proceed to call FindNextFile to search for the next file in the current folder until FindNextFile returns 0 (meaning no more file is found).

    invoke FindClose,FHandle
  .endif
  ret
FillFileInfo endp

When all files in the current folder are enumerated, we must close the search handle.

Now let's look at the ShowFileInfo function. This function accepts two parameters, the index of the item (row number) and the address of WIN32_FIND_DATA structure.

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

Store the address of WIN32_FIND_DATA structure in edi.

  mov lvi.imask,LVIF_TEXT+LVIF_PARAM
  push row
  pop lvi.iItem
  mov lvi.iSubItem,0

We will supply the label of the item and the value in lParam so we put LVIF_TEXT and LVIF_PARAM flags into imask. Next we set the iItem to the row number passed to the function and since this is the main item, we must filliSubItem with 0 (column 0).

  lea eax,[edi].cFileName
  mov lvi.pszText,eax
  push row
  pop lvi.lParam

Next we put the address of the label, in this case, the name of the file in WIN32_FIND_DATA structure, into pszText. Because we will implement sorting in the listview control, we must fill lParam with a value. I choose to put the row number into this member so I can retrieve the item info by its index.

  invoke SendMessage,hList, LVM_INSERTITEM,0, addr lvi

When all necessary fields in LV_ITEM are filled, we send LVM_INSERTITEM message to the listview control to insert the item into it.

  mov lvi.imask,LVIF_TEXT
  inc lvi.iSubItem
  invoke wsprintf,addr buffer, addr template,[edi].nFileSizeLow
  lea eax,buffer
  mov lvi.pszText,eax

We will set the subitem associated with the item just inserted into the second column. A subitem can only have a label. Thus we specify LVIF_TEXT in imask. Then we specify the column that the subitem should reside in iSubItem. In this case, we set it to 1 by incrementing iSubItem. The label we will use is the size of the file. However, we must convert it to a string first by calling wsprintf. Then we put the address of the string into pszText.

  invoke SendMessage,hList,LVM_SETITEM, 0,addr lvi
  assume edi:nothing
  ret
ShowFileInfo endp

When all necessary fields in LV_ITEM are filled, we send LVM_SETITEM message to the listview control, passing to it the address of the LV_ITEM structure. Note that we use LVM_SETITEM, not LVM_INSERTITEM because a subitem is considered as a property of an item. Thus we *set* the property of the item, not inserting a new item.

When all items are inserted into the listview control, we set the text and background colors of the listview control.

    RGB 255,255,255
    invoke SendMessage,hList,LVM_SETTEXTCOLOR,0,eax
    RGB 0,0,0
    invoke SendMessage,hList,LVM_SETBKCOLOR,0,eax
    RGB 0,0,0
    invoke SendMessage,hList,LVM_SETTEXTBKCOLOR,0,eax

I use RGB macro to convert the red, green,blue values into eax and use it to specify the color we need. We set the foreground and background colors of the text with LVM_SETTEXTCOLOR and LVM_SETTEXTBKCOLOR messages. We set the background color of the listview control by sending LVM_SETBKCOLOR message to the listview control.

    invoke GetMenu,hWnd
    mov hMenu,eax
    invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, IDM_REPORT,MF_CHECKED

We will let the user chooses the views he wants via the menu. Thus we must obtain the menu handle first. To help the user track the current view, we put a radio button system in our menu. The menu item that reflects the current view will be preceded by a radio button. That's why we call CheckMenuRadioItem. This function will put a radio button before a menu item.

Note that we create the listview control with width and height equal to 0. It will be resized later whenever the parent window is resized. This way, we can ensure that the size of the listview control will always match that of the parent window. In our example, we want the listview control to fill the whole client area of the parent window.

  .elseif uMsg==WM_SIZE
   
mov eax,lParam
    mov edx,eax
    and eax,0ffffh
    shr edx,16
    invoke MoveWindow,hList, 0, 0, eax,edx,TRUE

When the parent window receives WM_SIZE message, the low word of lParam contains the new width of the client area and the high word the new height. Then we call MoveWindow to resize the listview control to cover the whole client area of the parent window.

When the user selects a view in the menu. We must change the view in the listview control accordingly. We accomplish this by setting a new style in the listview control with SetWindowLong.

  .elseif uMsg==WM_COMMAND
    .if lParam==0
      invoke GetWindowLong,hList,GWL_STYLE
      and eax,not LVS_TYPEMASK

The first thing we do is to obtain the current styles of the listview control. Then we clear the old view style from the returned style flags. LVS_TYPEMASK is a constant that is the combined value of all 4 view style constants (LVS_ICON+LVS_SMALLICON+LVS_LIST+LVS_REPORT). Thus when we perform and operation on the current style flags with the value "not LVS_TYPEMASK", it amounts to clearing away the current view style.

In designing the menu, I cheat a little. I use the view style constants as the menu IDs.

IDM_ICON equ LVS_ICON
IDM_SMALLICON equ LVS_SMALLICON
IDM_LIST equ LVS_LIST
IDM_REPORT equ LVS_REPORT

Thus when the parent window receives WM_COMMAND message, the desired view style is in the low word of wParam as the menu ID.

      mov edx,wParam
      and edx,0FFFFh

We have the desired view style in the low word of wParam. All we have to do is to zero out the high word.

      push edx
      or eax,edx

And add the desired view style to the existing styles (minus the current view style) of the listview control.

      invoke SetWindowLong,hList,GWL_STYLE,eax

And set the new styles with SetWindowLong.

      pop edx
      invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, edx,MF_CHECKED    
   .endif

We also need to put the radio button in front of the selected view menu item. Thus we call CheckMenuRadioItem, passing the current view style (double as menu ID) to it.

When the user clicks on the column headers in the report view, we want to sort the items in the listview control. We must respond to WM_NOTIFY message.

  .elseif uMsg==WM_NOTIFY
    push edi
    mov edi,lParam
    assume edi:ptr NMHDR
    mov eax,[edi].hwndFrom
    .if eax==hList

When we receive WM_NOTIFY message, lParam contains the pointer to an NMHDR structure. We can check if this message is from the listview control by comparing the hwndFrom member of NMHDR to the handle to the listview control. If they match, we can assume that the notification came from the listview control.

      .if [edi].code==LVN_COLUMNCLICK
        assume edi:ptr NM_LISTVIEW

If the notification is from the listview control, we check if the code is LVN_COLUMNCLICK. If it is, it means the user clicks on a column header. In the case that the code is LVN_COLUMNCLICK, we can assume that lParam contains the pointer to an NM_LISTVIEW structure which is a superset of the NMHDR structure. We then need to know on which column header the user clicks. Examination of iSubItem member reveals this info. The value in iSubItem can be treated as the column number, starting from 0.

        .if [edi].iSubItem==1
          .if SizeSortOrder==0 || SizeSortOrder==2

In the case iSubItem is 1, it means the user clicks on the second column, size. We use state variables to keep the current status of the sorting order. 0 means "no sorting yet", 1 means "sort ascending", 2 means "sort descending". If the items/subitems in the column are not sorted before, or sorted descending, we set the sorting order to ascending.

            invoke SendMessage,hList,LVM_SORTITEMS,1,addr CompareFunc

We send LVM_SORTITEMS message to the listview control, passing 1 in wParam and the address of our comparison function in lParam. Note that the value in wParam is user-defined, you can use it in any way you like. I use it as the sorting method in this example. We will take a look at the comparison function first.

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

In the comparison function, the listview control will pass lParams (in LV_ITEM) of the two items it needs to compare to us in lParam1 and lParam2. You'll recall that we put the index of the item in lParam. Thus we can obtain information about the items by querying the listview control using the indexes. The info we need is the labels of the items/subitems being sorted. Thus we prepare an LV_ITEM structure for such purpose, specifying LVIF_TEXT in imask and the address of the buffer in pszText and the size of the buffer in cchTextMax.

  .if SortType==1
    mov lvi.iSubItem,1
    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi

If the value in SortType is 1 or 2, we know that the size column is clicked. 1 means sort the items according to their sizes in ascending order. 2 means the reverse. Thus we specify iSubItem as 1 ( to specify the size column) and send LVM_GETITEMTEXT message to the listview control to obtain the label (size string) of the subitem.

    invoke String2Dword,addr buffer
    mov edi,eax

Covert the size string into a dword value with String2Dword which is the function I wrote. It returns the dword value in eax. We store it in edi for comparison later.

    invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr lvi
    invoke String2Dword,addr buffer
    sub edi,eax
    mov eax,edi

Do likewise with the value in lParam2. When we have the sizes of the two files, we can then compare them.
The rule of the comparison function is as follows:

In this case, we want to sort the items according to their sizes in ascending order. Thus we can simply subtract the size of the first item with that of the second one and return the result in eax.

  .elseif SortType==3
    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

In case the user clicks the filename column, we must compare the names of the files. We first obtain the filenames and then compare them with lstrcmpi function. We can return the return value of lstrcmpi without any modification since it also uses the same rule of comparison, eg. negative value in eax if the first string is less than the second string.

When the items were sorted, we need to update the lParam values of all items to reflect the new indexes by calling UpdatelParam function.

            invoke UpdatelParam
            mov SizeSortOrder,1

This function simply enumerates all items in the listview control and updates the values in lParam with the new indexes. We need to do this else the next sort will not work as expected because our assumption is that the value in lParam is the index of the item.

      .elseif [edi].code==NM_DBLCLK
        invoke ShowCurrentFocus
      .endif

When the user double-clicks at an item, we want to display a message box with the label of the item on it. We must check if the code in NMHDR is NM_DBLCLK. If it is, we can proceed to obtain the label and display it in a message box.

ShowCurrentFocus proc
   LOCAL lvi:LV_ITEM
   LOCAL buffer[256]:BYTE

   invoke SendMessage,hList,LVM_GETNEXTITEM,-1, LVNI_FOCUSED

How do we know which item is double-clicked? When an item is clicked or double-clicked, its state is set to "focused". Even if many items are hilited (selected), only one of them has got the focus. Our job than is to find the item that has the focus. We do this by sending LVM_GETNEXTITEM message to the listview control, specifying the desired state in lParam. -1 in wParam means search all items. The index of the item is returned in eax.

   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

We then proceed to obtain the label by sending LVM_GETITEM message to the listview control.

   invoke MessageBox,0, addr buffer,addr AppName,MB_OK

Lastly, we display the label in a message box.

If you want to know how to use icons in the listview control, you can read about it in my treeview tutorial. The steps are just about the same.


[Iczelion's Win32 Assembly Homepage]