Tutorial 31: Listview Control

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

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:
  imask: D§ ?
  fmt: D§ ?
  lx: D§ ?
  pszText: D§ ?
  cchTextMax: D§ ?
  iSubItem: D§ ?
  iImage: D§ ?
  iOrder: D§ ?]
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.

 
InsertColumn:
    mov D§LVC_imask &LVCF_TEXT+&LVCF_WIDTH
    mov D§LVC_pszText Heading1
    mov D§LVC_lx 150
    call 'USER32.SendMessageA' D§hList &LVM_INSERTCOLUMN  0  LV_COLUMN
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:
  imask: D§ ?
  iItem: D§ ?
  iSubItem: D§ ?
  state: D§ ?
  stateMask: D§ ?
  pszText: D§ ?
  cchTextMax: D§ ?
  iImage: D§ ?
  lParam: D§ ?
  iIndent: D§ ?]
 
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.

_____________________________________________________________________________________
; Equates:

[M00_Menu  2000                  M00_View  2001                  M00_Icon_View  2002
 M00_Report_View  2003           M00_Small_Icon_View  2004       M00_List_View  2005]

_______________________________________________________________________________________
; Data:

[hList: 0  hMenu: 0  FileNameSortOrder: 0  SizeSortOrder: 0
 ClassName: 'ListViewWinClass' 0
 AppName:   'Testing a ListView Control' 0
 ListViewClassName: 'SysListView32' 0
 Heading1:  'Filename' 0
 Heading2:  'Size' 0
 FileNamePattern: '*.*' 0
 template:  '%lu' 0]
 

[WindowHandle: 0   WindowClassName: B§ 'Anything' 0
                   WindowCaption: 'Base App' 0]             ; Data
 
____________________________________________________________________________________________
____________________________________________________________________________________________

[FirstMessage: fAdressee: 0  fMessage: 0  fwParam: 0  flParam: 0 0 0 0 0 0 0]   ; Win message
[MenuHandle: 0]
[WindowX: 50  WindowY: 50  WindowW:650  WindowH:450]

[WindowClassEx:  len
 style: 0  lpfnWndProc: MainWindowProc    cbClsExtra: 0  cbWndExtra: 0
 hInstance: 0  hIcon: 0  hCursor: 0  hbrBackground: 6 ; COLOR_WINDOW+1
 lpszMenuName: M00_Menu  lpszClassName: ClassName  hIconSm: 0]
 
____________________________________________________________________________________________
____________________________________________________________________________________________

Main:

    call 'Kernel32.GetModuleHandleA' 0 | mov D§hInstance eax

    call 'User32.LoadIconA'  0  &IDI_APPLICATION | mov D§hIcon eax, D§hIconSm eax
 
    call 'User32.LoadCursorA' 0  &IDC_ARROW  | mov D§hCursor eax

    call 'User32.RegisterClassExA'  WindowClassEx

    call 'User32.CreateWindowExA' &NULL ClassName  AppName,
                                 &WS_OVERLAPPEDWINDOW,
                                 &CW_USEDEFAULT &CW_USEDEFAULT &CW_USEDEFAULT &CW_USEDEFAULT,
                                 0  0  D§hInstance  0
      mov D§WindowHandle eax
 
    call 'User32.ShowWindow'  D§WindowHandle &SW_SHOW
 
    call 'User32.UpdateWindow'  D§WindowHandle

 
L1: call 'User32.GetMessageA' FirstMessage 0 0 0 | cmp eax 0 | je L9>
        call 'User32.TranslateMessage'  FirstMessage
        call 'User32.DispatchMessageA'  FirstMessage
    jmp L1<

L9: call 'Kernel32.ExitProcess' 0
 
______________________________________________________________________________________

[LV_COLUMN:  LVC_imask: 0       LVC_fmt: 0      LVC_lx: 0    LVC_pszText: 0
             LVC_cchTextMax: 0  LVC_iSubItem: 0]

InsertColumn:
    mov D§LVC_imask &LVCF_TEXT+&LVCF_WIDTH
    mov D§LVC_pszText Heading1
    mov D§LVC_lx 150
    call 'USER32.SendMessageA' D§hList &LVM_INSERTCOLUMN  0  LV_COLUMN
    or D§LVC_imask &LVCF_FMT
    mov D§LVC_fmt &LVCFMT_RIGHT
    mov D§LVC_pszText Heading2
    mov D§LVC_lx 100
    call 'USER32.SendMessageA' D§hList &LVM_INSERTCOLUMN  1  LV_COLUMN
  ret

[Row: 0] [ShowFileBuffer: B§ 0 #200]

ShowFileInfo:
    mov D§LVI_imask &LVIF_TEXT+&LVIF_PARAM
    push D§row | pop D§LVI_iItem
    mov D§LVI_iSubItem 0,  D§LVI_pszText  WFD_cFileName
    push D§row | pop D§LVI_lParam
    call 'USER32.SendMessageA' D§hList  &LVM_INSERTITEM  0  LV_ITEM
 
    mov D§LVI_imask &LVIF_TEXT | inc D§LVI_iSubItem
    call 'USER32.wsprintfA'  ShowFilebuffer template  D§WFD_nFileSizeLow | pop eax eax eax
    mov D§LVI_pszText ShowFileBuffer
        call 'USER32.SendMessageA' D§hList  &LVM_SETITEM  0  LV_ITEM
    mov D§LVI_pszText Buffer1
  ret

_________________________________________________________________________________________

[WIN32_FIND_DATA:  WFD_dwFileAttributes: 0  WFD_ftCreationTime: 0 0
                   WFD_ftLastAccessTime: 0 0  WFD_ftLastWriteTime: 0 0
                   WFD_nFileSizeHigh: 0     WFD_nFileSizeLow: 0
                   WFD_dwReserved0: 0       WFD_dwReserved1: 0]
                  [WFD_cFileName: B§ 0#32] [WFD_cAlternate: B§ 0 #14]
                  [WFDlen: WFDlen-WIN32_FIND_DATA]

[FHandle: 0]
 

FillFileInfo:
    call 'KERNEL32.FindFirstFileA'  FileNamePattern WIN32_FIND_DATA
    .If eax ne &INVALID_HANDLE_VALUE
        mov D§FHandle eax, D§Row 0
        .While eax > 0
            mov eax D§WFD_dwFileAttributes | and eax &FILE_ATTRIBUTE_DIRECTORY
            If eax = 0
                call ShowFileInfo
                inc D§Row
            End_If
            call 'KERNEL32.FindNextFileA' D§FHandle WIN32_FIND_DATA
        .End_While
        call 'KERNEL32.FindClose' D§FHandle
    .End_If
  ret

; This translation for Ascii decimal to Binary vvalue in eax do not need any security test
; because it is an amount of bytes in a file; so:

String2Dword:
    mov eax 0, ecx 0

L0: mov cl B§esi | inc  esi
    cmp cl 0 | je  L9>
      mov edx 10 | mul edx
        sub  ecx '0'
          add  eax ecx | jmp  L0<
L9: ret
 

[SortType: 0  CF_Para1: 0  CF_Para2: 0]

ResetLV_ITEM:
    pushad
      mov edi LV_ITEM, eax 0, ecx 9 | rep stosd
      mov D§LVI_cchTextMax 256, D§LVI_imask &LVIF_TEXT, D§LVI_pszText Buffer1
    popad
  ret
 

; This is the CallBack Proc given in 'LVM_SORTITEMS' message: ( the direct POPs of
; messages parameters works fine in these simple cases):

CompareFunc:
 
    pop eax, D§CF_Para1, D§CF_Para2, D§LVI_iSubItem | push eax
 
    .If D§LVI_iSubItem = 1
        mov D§LVI_iSubItem 1
        call 'USER32.SendMessageA' D§hList &LVM_GETITEMTEXT D§CF_Para1 LV_ITEM
        mov esi Buffer1 | call String2Dword
        mov edi eax
        call 'USER32.SendMessageA' D§hList &LVM_GETITEMTEXT D§CF_Para2 LV_ITEM
        mov esi Buffer1 | call String2Dword
        sub edi eax
        mov eax,edi
    .Else_If D§LVI_iSubItem = 2
        mov D§LVI_iSubItem 1
        call 'USER32.SendMessageA' D§hList &LVM_GETITEMTEXT D§CF_Para1 LV_ITEM
        mov esi Buffer1 | call String2Dword
        mov edi eax
        call 'USER32.SendMessageA' D§hList &LVM_GETITEMTEXT D§CF_Para2 LV_ITEM
        mov esi Buffer1 | call String2Dword
        sub eax edi
    .Else_If D§LVI_iSubItem = 3
        mov D§LVI_iSubItem 0
        call 'USER32.SendMessageA' D§hList &LVM_GETITEMTEXT D§CF_Para1 LV_ITEM
        mov D§LVI_pszText buffer2
          call 'USER32.SendMessageA' D§hList &LVM_GETITEMTEXT D§CF_Para2 LV_ITEM
        mov D§LVI_pszText buffer1
        call 'Kernel32.lstrcmpi' buffer2 buffer1
    .Else
        mov D§LVI_iSubItem,0
        call 'USER32.SendMessageA' D§hList &LVM_GETITEMTEXT D§CF_Para1 LV_ITEM
        mov D§LVI_pszText buffer2
          call 'USER32.SendMessageA' D§hList &LVM_GETITEMTEXT D§CF_Para2 LV_ITEM
        mov D§LVI_pszText buffer1
        call 'Kernel32.lstrcmpi' buffer1 buffer2
    .End_If
 
    call ResetLV_ITEM
  ret
 
 

UpdatelParam:
    call 'USER32.SendMessageA' D§hList  &LVM_GETITEMCOUNT 0 0
    mov edi eax
    mov D§LVI_imask &LVIF_PARAM
    mov D§LVI_iItem 0
    .While edi a 0
        push D§LVI_iItem | pop D§LVI_lParam
        call 'USER32.SendMessageA' D§hList  &LVM_SETITEM 0  LV_ITEM
        inc D§LVI_iItem
        dec edi
    .End_While
  ret
 

[LV_ITEM: LVI_imask: &LVIF_TEXT     LVI_iItem: 0    LVI_iSubItem: 0    LVI_state: 0
          LVI_stateMask: 0  LVI_pszText: Buffer1  LVI_cchTextMax: 256  LVI_iImage: 0
          LVI_lParam: 0]

[Buffer1: B§ 0 #256] [Buffer2: B§ 0 #256]

 
ShowCurrentFocus:
    call 'USER32.SendMessageA' D§hList &LVM_GETNEXTITEM 0-1 &LVNI_FOCUSED
    mov D§LVI_iItem eax
    mov D§LVI_iMask &LVIF_TEXT
    call 'USER32.SendMessageA' D§hList  &LVM_GETITEM  0  LV_ITEM
    call 'USER32.MessageBoxA' 0  buffer1  AppName  0
  ret
 

__________________________________________________________________________________

; structure used only to set pointer to parameters in WM_NOTIFY down there:

[NM_LISTVIEW:
 NMHDR:  NMHDR_hwndFrom: 0  NMHDR_idfrom: 0  NMHDR_code: 0
 NML_iItem: 0
 NML_iSubItem: 0
 NML_uNewState: 0
 NML_uOldState: 0
 NML_uChanged: 0
 NML_ptAction: 0 0
 NML_lParam: 0]
 

Proc MainWindowProc:
    Arguments @Adressee, @Message, @wParam, @lParam

    pushad

    .If D@Message = &WM_CREATE
       call 'Comctl32.InitCommonControls'
 

       call 'USER32.CreateWindowExA' &NULL ListViewClassName &NULL,     ; This Child
                                     &LVS_REPORT+&WS_CHILD+&WS_VISIBLE,  ; Window is shown
                                     0 0 0 0,                         ; up by lower
                                     D@Adressee &NULL D§hInstance &NULL ; WM_SIZE Message case
       mov D§hList eax
 
       call InsertColumn
       call FillFileInfo
       RGB 255 255 255
       call 'USER32.SendMessageA' D§hList &LVM_SETTEXTCOLOR 0 eax
       RGB 0 0 0
       call 'USER32.SendMessageA' D§hList &LVM_SETBKCOLOR 0 eax
       RGB 0,0,0
       call 'USER32.SendMessageA' D§hList &LVM_SETTEXTBKCOLOR 0 eax
 
       call 'USER32.GetMenu' D@Adressee
       mov D§hMenu eax
       call 'USER32.CheckMenuRadioItem' D§hMenu M00_Icon_View  M00_Report_View,
                                        M00_Report_View,
                                        &MF_CHECKED
 
    .Else_If D@Message = &WM_COMMAND
        If D@lParam = 0
            call 'USER32.GetWindowLongA' D§hList &GWL_STYLE
            or eax &LVS_TYPEMASK | xor eax &LVS_TYPEMASK
            mov edx D@wParam | and edx 0FFFF
            sub edx 2002                           ; Makes the same as:
 
        ;  If edx = M00_Icon_View                ; 2002 > 0
        ;     mov edx LVS_ICON
        ;  Else_If edx = M00_Small_Icon_View     ; 2003 > 2
        ;     mov edx LVS_SMALLICON
        ;  Else_If edx = M00_List_View           ; 2004 > 3
        ;     mov edx LVS_LIST
        ;  Else_If edx = M00_Report_View         ; 2005 > 1
        ;     mov edx LVS_REPORT
        ;  End_If

            push edx
                or eax edx
                call 'USER32.SetWindowLongA' D§hList &GWL_STYLE eax
            pop edx
            call 'USER32.CheckMenuRadioItem' D§hMenu  M00_Icon_View  M00_Report_View,
                                             edx &MF_CHECKED
        End_If

    .Else_If D@Message = &WM_NOTIFY
        push edi
            mov edi D@lParam | sub edi NMHDR
            mov eax D§edi+NMHDR_hwndFrom
            ..If eax = D§hList
                ...If D§edi+NMHDR_code = &LVN_COLUMNCLICK
                    mov edi D@lParam | sub edi NM_LISTVIEW
                    If D§edi+NML_iSubItem = 1
                        On D§SizeSortOrder = 0, mov D§SizeSortOrder 2
                        xor D§SizeSortOrder 00_11
                        call 'USER32.SendMessageA' D§hList &LVM_SORTITEMS,
                                                   D§SizeSortOrder CompareFunc
                        call UpdatelParam

                     Else
                        On D§FileNameSortOrder = 0, mov D§FileNameSortOrder 4
                        xor D§FileNameSortOrder 00_111
                        call 'USER32.SendMessageA' D§hList &LVM_SORTITEMS,
                                                   D§FileNameSortOrder CompareFunc
                        call UpdatelParam
 
                     End_If

                ...Else_If D§NMHDR_code = &NM_DBLCLK
                     call ShowCurrentFocus
                ...End_If
            ..End_If
            pop edi
 
    .Else_If D@Message = &WM_SIZE
        mov eax D@lParam,  edx eax
        and eax 0ffff | shr edx 16
        call 'USER32.MoveWindow' D§hList 0 0 eax edx &TRUE
 
    .Else_If D@Message = &WM_DESTROY
        call 'USER32.PostQuitMessage' &NULL
 
    .Else
        popad
        call 'USER32.DefWindowProcA' D@Adressee D@Message D@Wparam D@Lparam
        Exit
 
    .endif
 
    popad | mov eax &FALSE
EndP
 
 

Analysis:

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

    .If D@Message = &WM_CREATE
       call 'Comctl32.InitCommonControls'
 

       call 'USER32.CreateWindowExA' &NULL ListViewClassName &NULL,     ; This Child
                                     &LVS_REPORT+&WS_CHILD+&WS_VISIBLE,  ; Window is shown
                                     0 0 0 0,                         ; up by lower
                                     D@Adressee &NULL D§hInstance &NULL ; WM_SIZE Message case
       mov D§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.

       call InsertColumn

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

InsertColumn:
    mov D§LVC_imask &LVCF_TEXT+&LVCF_WIDTH
    mov D§LVC_pszText Heading1
    mov D§LVC_lx 150
    call 'USER32.SendMessageA' D§hList &LVM_INSERTCOLUMN  0  LV_COLUMN

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 D§LVC_imask &LVCF_FMT
    mov D§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 D§LVC_pszText Heading2
    mov D§LVC_lx 100
    call 'USER32.SendMessageA' D§hList &LVM_INSERTCOLUMN  1  LV_COLUMN

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.

       call FillFileInfo

FillFileInfo has the following code.
 

FillFileInfo:
    call 'KERNEL32.FindFirstFileA'  FileNamePattern WIN32_FIND_DATA
 

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 ne &INVALID_HANDLE_VALUE
        mov D§FHandle eax, D§Row 0

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
            mov eax D§WFD_dwFileAttributes | and eax &FILE_ATTRIBUTE_DIRECTORY
            If eax = 0
 

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.
 

                call ShowFileInfo
                inc D§Row
            End_If
            call 'KERNEL32.FindNextFileA' D§FHandle WIN32_FIND_DATA
        .End_While

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).
 

        call 'KERNEL32.FindClose' D§FHandle
    .End_If

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:
 

Store the address of WIN32_FIND_DATA structure in edi.
 

    mov D§LVI_imask &LVIF_TEXT+&LVIF_PARAM
    push D§row | pop D§LVI_iItem
    mov D§LVI_iSubItem 0,  D§LVI_pszText  WFD_cFileName
 

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).
 

    push D§row | pop D§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.

    call 'USER32.SendMessageA' D§hList  &LVM_INSERTITEM  0  LV_ITEM
 
 

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 D§LVI_imask &LVIF_TEXT | inc D§LVI_iSubItem
    call 'USER32.wsprintfA'  ShowFilebuffer template  D§WFD_nFileSizeLow | pop eax eax eax
    mov D§LVI_pszText ShowFileBuffer
 

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.
 

        call 'USER32.SendMessageA' D§hList  &LVM_SETITEM  0  LV_ITEM
    mov D§LVI_pszText Buffer1
  ret

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
       call 'USER32.SendMessageA' D§hList &LVM_SETTEXTCOLOR 0 eax
       RGB 0 0 0
       call 'USER32.SendMessageA' D§hList &LVM_SETBKCOLOR 0 eax
       RGB 0,0,0
       call 'USER32.SendMessageA' D§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.
 

       call 'USER32.GetMenu' D@Adressee
       mov D§hMenu eax
       call 'USER32.CheckMenuRadioItem' D§hMenu M00_Icon_View  M00_Report_View,
                                        M00_Report_View,
                                        &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.
 

    .Else_If D@Message = &WM_SIZE
        mov eax D@lParam,  edx eax
        and eax 0ffff | shr edx 16
        call 'USER32.MoveWindow' D§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.
 

    .Else_If D@Message = &WM_COMMAND
        If D@lParam = 0
            call 'USER32.GetWindowLongA' D§hList &GWL_STYLE
            or eax &LVS_TYPEMASK | xor eax &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.
 
 

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 D@wParam | and edx 0FFFF
 
 
 

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.

                call 'USER32.SetWindowLongA' D§hList &GWL_STYLE eax
 

And set the new styles with SetWindowLong.
 

            pop edx
            call 'USER32.CheckMenuRadioItem' D§hMenu  M00_Icon_View  M00_Report_View,
                                             edx &MF_CHECKED
        End_If

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.
 

    .Else_If D@Message = &WM_NOTIFY
        push edi
            mov edi D@lParam | sub edi NMHDR
            mov eax D§edi+NMHDR_hwndFrom
            ..If eax = D§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 D§edi+NMHDR_code = &LVN_COLUMNCLICK
                    mov edi D@lParam | sub edi 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.
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.

                        call 'USER32.SendMessageA' D§hList &LVM_SORTITEMS,
                                                   D§SizeSortOrder 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:
 
    pop eax, D§CF_Para1, D§CF_Para2, D§LVI_iSubItem | push eax
 

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 D§LVI_iSubItem = 1
        mov D§LVI_iSubItem 1
        call 'USER32.SendMessageA' D§hList &LVM_GETITEMTEXT D§CF_Para1 LV_ITEM
 

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.
 

        mov esi Buffer1 | call String2Dword
        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.
 

        call 'USER32.SendMessageA' D§hList &LVM_GETITEMTEXT D§CF_Para2 LV_ITEM
        mov esi Buffer1 | call String2Dword
        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.
 

    .Else_If D§LVI_iSubItem = 2
        mov D§LVI_iSubItem 1
        call 'USER32.SendMessageA' D§hList &LVM_GETITEMTEXT D§CF_Para1 LV_ITEM
        mov esi Buffer1 | call String2Dword
        mov edi eax
        call 'USER32.SendMessageA' D§hList &LVM_GETITEMTEXT D§CF_Para2 LV_ITEM
        mov esi Buffer1 | call String2Dword
        sub eax edi

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.

            call UpdatelParam

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.

                ...Else_If D§NMHDR_code = &NM_DBLCLK
                     call ShowCurrentFocus

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:
    call 'USER32.SendMessageA' D§hList &LVM_GETNEXTITEM 0-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 D§LVI_iItem eax
    mov D§LVI_iMask &LVIF_TEXT
    call 'USER32.SendMessageA' D§hList  &LVM_GETITEM  0  LV_ITEM
 

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
    call 'USER32.MessageBoxA' 0  buffer1  AppName  0

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]