Page 1 of 1

MASM - Using a Listview control

#1 GunnerInc  Icon User is offline

  • "Hurry up and wait"
  • member icon




Reputation: 858
  • View blog
  • Posts: 2,279
  • Joined: 28-March 11

Post icon  Posted 25 March 2012 - 05:00 PM

The ListView control is a cool control with its many different "Views"
Icon - Displays single items with a large icon and optional text label.
Small icon - same as Icon view but displays a small icon.
Report - Displays data in different columns.
List - Displays a list with small icons.
and with newer versions of Windows, different layouts.

I will break up this tutorial into a few parts. This will be part one, and we will discuss loading the listview, adding columns, displaying icons from the System Imagelist,
displaying a watermark, and deleting selected items.

The following is from MASM32 so all members of structures might not be present, more info on the listview can be found here

You can create a listview control with CreateWindow/Ex and pass it the class name of SysListView32 or you can create one in a resource file, I will use a resource file here.

The listview control has quite a few structures to interact with it for different things, we will be using LVBKIMAGE, LVCOLUMN, LVITEM, and LVHITTESTINFO.

LVBKIMAGE: (Used with LVM_GETBKIMAGE and LVM_SETBKIMAGE - Get/Set Listview background image)
LVBKIMAGEA STRUCT
  ulFlags           DWORD  ?
  hbm               DWORD  ?
  pszImage          DWORD  ?
  cchImageMax       DWORD  ?
  xOffsetPercent    DWORD  ?
  yOffsetPercent    DWORD  ?
LVBKIMAGEA ENDS

This structure is used to display a watermark or background image. A funny thing with MS, they say hbm is NOT used but I will show how to use it.

Flags usually tell Windows what you are doing with the control, setting info or getting info.
ulFlags:
  • LVBKIF_SOURCE_NONE - This is self explanatory, the listview has no background image. You use this member to remove a background image.
  • LVBKIF_SOURCE_HBITMAP - hbm is a handle to a bitmap.
  • LVBKIF_SOURCE_URL - pszImage contains the URL or file path of the image. File path is in format file://driveletter/pathtofile/filename.bmp
  • LVBKIF_STYLE_NORMAL - background image is displayed normally.
  • LVBKIF_STYLE_TILE - image will be tiled to fit entire background of listview.
  • LVBKIF_FLAG_TILEOFFSET - sets the coords of the first tile.
  • LVBKIF_TYPE_WATERMARK - displays the image as a watermark in the bottom right corner.
  • LVBKIF_FLAG_ALPHABLEND - tells listview the bitmap contains a valid alpha channel.

hbm:
Handle of the bitmap to use for the watermark

pszImage:
Address of NULL terminated string containing the path of the image to use.

cchImageMax:
Size of buffer in pszImage. Used only when retrieving the path from listview.

xOffsetPercent:
Horizontal offset % of image. Version 6 of the comctl32.dll uses pixels instead.

yOffsetPercent:
Same as xOffsetPercent but Vertical.


LVCOLUMN: (Used with LVM_GETCOLUMN, LVM_SETCOLUMN, LVM_INSERTCOLUMN, LVM_DELETECOLUMN)
LV_COLUMN STRUCT
  imask             DWORD  ?
  fmt               DWORD  ?
  lx                DWORD  ?
  pszText           DWORD  ?
  cchTextMax        DWORD  ?
  iSubItem          DWORD  ?
  iImage            DWORD  ?
  iOrder            DWORD  ?
LV_COLUMN ENDS

This structure is used to insert/delete columns and get info about columns in listview report mode.
imask: (Tells which structure members contain information)
  • LVCF_FMT
  • LVCF_WIDTH
  • LVCF_TEXT
  • LVCF_SUBITEM
  • LVCF_IMAGE
  • LVCF_ORDER
  • LVCF_MINWIDTH
  • LVCF_DEFAULTWIDTH
  • LVCF_IDEALWIDTH


fmt: (Alignment of header text of columns)
  • LVCFMT_LEFT
  • LVCFMT_RIGHT
  • LVCFMT_CENTER
  • LVCFMT_JUSTIFYMASK
  • LVCFMT_IMAGE
  • LVCFMT_BITMAP_ON_RIGHT
  • LVCFMT_COL_HAS_IMAGES
  • LVCFMT_FIXED_WIDTH
  • LVCFMT_NO_DPI_SCALE
  • LVCFMT_FIXED_RATIO
  • LVCFMT_LINE_BREAK
  • LVCFMT_FILL
  • LVCFMT_WRAP
  • LVCFMT_NO_TITLE
  • LVCFMT_SPLITBUTTON

lx:
Width of colum in pixels

pszText:
Pointer to a NULL terminated string to display as the column text or pointer to buffer to receive text when getting column text.

cchTextMax:
Size of pszText buffer when receiving text.

iSubItem:
Index of subitem to associate with columns

iImage:
zero based index of the image to display from image list

iOrder:
Zero based column offset (zero indicates leftmost column)


LVITEM: (Used with LVM_GETITEM, LVM_SETITEM, LVM_INSERTITEM, and LVM_DELETEITEM )
LV_ITEM STRUCT
  imask             DWORD  ?
  iItem             DWORD  ?
  iSubItem          DWORD  ?
  state             DWORD  ?
  stateMask         DWORD  ?
  pszText           DWORD  ?
  cchTextMax        DWORD  ?
  iImage            DWORD  ?
  lParam            DWORD  ?
  iIndent           DWORD  ?
LV_ITEM ENDS


imask: (Which members contain data or which members are being requested)
LVIF_DI_SETITEM - Used with a Virtual listview, OS stores info and does not request it again.
LVIF_IMAGE
LVIF_INDENT
LVIF_NORECOMPUTE - Used with a Virtual listview, listview will not send LVN_GETDISPINFO instead pszText will contain LPSTR_TEXTCALLBACK
LVIF_PARAM
LVIF_STATE
LVIF_TEXT

iItem:
Zero based index of item structure info is for.

iSubItem:
One based index of subitem or zero if working with an item
Column1                 Column2                 Column3     
iItem=0/iSubitem=0      iItem=0/iSubitem=1      iItem=0/iSubitem=2
iItem=1/iSubitem=0      iItem=1/iSubitem=1      iItem=1/iSubitem=2
iItem=2/iSubitem=0      iItem=2/iSubitem=1      iItem=2/iSubitem=2
iItem=3/iSubitem=0      iItem=3/iSubitem=1      iItem=3/iSubitem=2


state:
State of item, overlay image, and state image.

stateMask:
Specifies which bits of the state flag we are interested in.

pszText:
Pointer to a NULL terminated string to display as the item text or pointer to buffer to receive text when getting item text.

cchTextMax:
Size of pszText buffer when receiving text.

iImage:
Zero based index of image in image list to display for item.

lParam:
Item value used with LVM_SORTITEMS, if not using then lParam is just a DWORD sized variable.

iIndent:
Indent width of item. Each indent is the width of an image. 1 = indent 1 image width, 2 = indent 2 image widths etc... Only valid for items not subitems


LVHITTESTINFO: (Used with LVM_HITTEST and LVM_SUBITEMHITTEST - members receive info)
LV_HITTESTINFO STRUCT
  pt                POINT  <>
  flags             DWORD  ?
  iItem             DWORD  ?
  iSubItem          DWORD  ?
LV_HITTESTINFO ENDS


pt:
Client coords to hit test on.

flags: (variable that will hold the result of a Hit Test)
LVHT_ABOVE
LVHT_BELOW
LVHT_NOWHERE
LVHT_ONITEMICON
LVHT_ONITEMLABEL
LVHT_ONITEMSTATEICON
LVHT_TOLEFT
LVHT_TORIGHT

iItem:
This will be the index of matching item, if hit testing on a subitem, this will be subitems parent index

iSubItem:
Index of subitem. Will be zero for a parent item


Listview.asm
    invoke  CoInitialize, 0
    call    InitSysImageList    

    ; Make sure we can theme our app
    invoke  LoadLibrary, offset szuxtheme
    .if eax !=0
        mov     hThemeLib, eax
        invoke  GetProcAddress, eax, offset szThemeProc
        mov     hThemeProc, eax        
    .else
        mov     hThemeLib, FALSE
    .endif
    
    invoke  GetModuleHandle, NULL
    mov     hInst, eax
    
    invoke  DialogBoxParam, eax, IDD_MAIN, HWND_DESKTOP, offset ProcMainDlg, NULL
    invoke  CoUninitialize
    invoke  ImageList_Destroy, himlReport
    
    .if hThemeLib
        invoke  FreeLibrary, hThemeLib
    .endif
    invoke  ExitProcess, 0


To display background images, the listview uses COM so we must make sure the COM library is initialized for our thread with CoInitialize. Although this does not seem to be needed on my Win7 machine, we do it to be compatible with earlier versions of Windows.

In order for all controls in our window to look "uniform" on XP and above, we "Theme" our window. We do this by trying to load the uxtheme.dll, if we can load it, then it is on the users system so get the address of the EnableThemeDialogTexture procedure.

Before we exit, we free up what we created.

InitSysImageList proc
    invoke  LoadLibrary, offset szShell32
    xchg    eax, ebx
    
    invoke  GetProcAddress, ebx, FILEICONINIT
    .if eax !=0
        push    TRUE
        call    eax ; FileIconInit
    .endif
    
    invoke  GetProcAddress, ebx, SHELL_GETIMAGELISTS   
    .if eax !=0
        push    offset himlSysSm
        push    offset himlSysLg
        call    eax ; Shell_GetImageLists

    .endif
    ret
InitSysImageList endp

Sure, you could use SHGetFileInfo to get a handle to the system imagelist, but it will not contain all images. This is where our InitSysImageList proc comes in.
FileIconInit is only available on NT and must be called by ordinal - 660, it just initializes the system image list for us.
Next we call Shell_GetImageLists which returns the handle for the large and small system imagelists - ordinal 71
Now that we have handles to the large and small system imagelists, we can use almost all ImageList_ API calls. I say almost all because you should NEVER add or remove icons from the system imagelist nor should you ever delete the system imagelist.

OPTION  PROLOGUE:NONE
OPTION  EPILOGUE:NONE
LoadNormalIcons proc Count:DWORD
    push    ebx
    push    esi
    push    edi
    push    ebp
    
    mov     edi, himlSysLg
    mov     esi, hLVIcon
    
    invoke  ImageList_GetImageCount, edi
    xchg    eax, ebx

    dec     dword ptr [esp + 20]
    mov     ebp, offset lvi
    mov     (LVITEM ptr[ebp]).imask, LVIF_IMAGE 
    mov     Counter, 0
    
NextIcon:
    invoke  ImageList_GetIcon, edi, ebx, 0
    test    eax, eax
    jz      BadHandle
    invoke  DestroyIcon, eax

    invoke  GetDlgItemInt, hMain, TOTAL_GOOD_ICONS_STC, NULL, FALSE
    inc     eax
    invoke  SetDlgItemInt, hMain, TOTAL_GOOD_ICONS_STC, eax, FALSE

    mov     (LVITEM ptr[ebp]).iItem, ebx
    mov     (LVITEM ptr[ebp]).iImage, ebx
    invoke  SendMessage, esi, LVM_INSERTITEM, 0, ebp
    
    jmp     @F
    
BadHandle:
    inc     dword ptr [esp + 20]
    mov     ecx, [esp + 20]
    invoke  SetDlgItemInt, hMain, TOTAL_BAD_ICONS_STC, ecx, FALSE
    
@@:
    invoke  SetDlgItemInt, hMain, TOTAL_ICONS_STC, Counter, FALSE

    inc     Counter
	dec     ebx	
    jns     NextIcon

    invoke  InvalidateRect, hMain, NULL, FALSE
    pop     ebp
    pop     edi
    pop     esi
    pop     ebx
    ret     4
LoadNormalIcons endp

OPTION  PROLOGUE:NONE
OPTION  EPILOGUE:NONE

This turns off the creation of Prologue and Epilogue code. Any procedures following this, will not have Pro/Epilogue code created.
LoadNormalIcons is where we will enumerate the icons in the system imagelist and add to the listview.
    mov     edi, himlSysLg
    mov     esi, hLVIcon
    
    invoke  ImageList_GetImageCount, edi
    xchg    eax, ebx

    dec     dword ptr [esp + 20]
    mov     ebp, offset lvi
    mov     (LVITEM ptr[ebp]).imask, LVIF_IMAGE 
    mov     Counter, 0


We are setting edi to the handle of the large system image listview
esi will be the handle of the Icon listview.

Then we get the count of icons in the imagelist and save the count to ebx

Since I want to use ebp in code, I won't use "locals", instead I pushed a param on the stack and will use that [esp + 20]. This will be our var that will hold the number of bad icon handles.

Next we will move the address of the lvi structure into ebp and tell the listview to expect an index of the icon to display.

Counter is the current index of iItem

NextIcon:
    invoke  ImageList_GetIcon, edi, ebx, 0
    test    eax, eax
    jz      BadHandle
    invoke  DestroyIcon, eax

    invoke  GetDlgItemInt, hMain, TOTAL_GOOD_ICONS_STC, NULL, FALSE
    inc     eax
    invoke  SetDlgItemInt, hMain, TOTAL_GOOD_ICONS_STC, eax, FALSE

    mov     (LVITEM ptr[ebp]).iItem, ebx
    mov     (LVITEM ptr[ebp]).iImage, ebx
    invoke  SendMessage, esi, LVM_INSERTITEM, 0, ebp
    
    jmp     @F
    
BadHandle:
    inc     dword ptr [esp + 20]
    mov     ecx, [esp + 20]
    invoke  SetDlgItemInt, hMain, TOTAL_BAD_ICONS_STC, ecx, FALSE
    
@@:

    invoke  SetDlgItemInt, hMain, TOTAL_ICONS_STC, Counter, FALSE

    inc     Counter
    dec     ebx	
    jns     NextIcon

This is our loop to enumerate the icons in the system imagelist.
I do not know why, but there some images that are black in my system imagelist, so I call ImageList_GetIcon to get the handle of the icon at the current index, and if the handle is zero I skip it
and increase our bad handle counter at [esp + 20]. We are responsible for freeing the returned icon handle, so we do that with DestroyIcon.

Next, we get the int being displayed for total good icons, and increase the value and redisplay it.

Next, we set iItem and iImage to current value in ebx and display it in the listview with LVM_INSERTITEM.

BadHandle is where we increase our counter and the total bad handle count display.

Once we are done loading the listview, we force a redraw to display the watermark correctly.

GetMetrics proc 
    push    ebx
    push    esi
    push    edi
    push    ebp
    
    mov     lvi2.imask, LVIF_TEXT

    xor     ebx, ebx
    mov     esi, offset szLabel    
    mov     edi, offset szDesc
    lea     ebp, iIndex
    
NextItem:
    mov     lvi2.iSubItem, 0
    mov     lvi2.iItem, ebx
    mov     eax, [esi +  4 * ebx]
    mov     lvi2.pszText, eax
    invoke  SendMessage, hLVReport, LVM_INSERTITEM, 0, offset lvi2

    inc     lvi2.iSubItem
    mov     eax, [edi +  4 * ebx]
    mov     lvi2.pszText, eax
    invoke  SendMessage, hLVReport, LVM_SETITEM, 0, offset lvi2	    

    push    [ebp + 4 * ebx]
    call    GetSystemMetrics
    invoke  wsprintf, offset lpBuffer, offset szFormat, eax
    inc     lvi2.iSubItem
    mov     lvi2.pszText, offset lpBuffer
    invoke  SendMessage, hLVReport, LVM_SETITEM, 0, offset lvi2	    
    
    inc     ebx
    cmp     ebx, NUMLINES
    jna     NextItem

	invoke  SendMessage, hLVReport, LVM_SETCOLUMNWIDTH, 0, LVSCW_AUTOSIZE_USEHEADER
	invoke  SendMessage, hLVReport, LVM_SETCOLUMNWIDTH, 1, LVSCW_AUTOSIZE_USEHEADER
	invoke  SendMessage, hLVReport, LVM_SETCOLUMNWIDTH, 2, LVSCW_AUTOSIZE_USEHEADER
	
    pop     ebp
    pop     edi
    pop     esi
    pop     ebx    
    ret
GetMetrics endp
OPTION  PROLOGUE:PrologueDef
OPTION  EPILOGUE:EpilogueDef


We have 3 arrays, 2 are arrays of pointers to strings (szLabel, and szDesc) and an array of dwords (iIndex) that are constants for GetSystemMetrics.
In our loop (NextItem), we loop through eacy array item displaying the string. The 3rd array contained in ebp, we pass the value to GetSystemMetrics, convert the return to a string, and display.

The last 3 invokes are the interesting bit. When you pass a listview in report mode the message LVM_SETCOLUMNWIDTH, and use the special value - LVSCW_AUTOSIZE_USEHEADER, the listview will autosize each column
to fit its longest string, for the last column, it will fill the rest of the listview width.
OPTION PROLOGUE:PrologueDef
OPTION EPILOGUE:EpilogueDef
This turns back on the default Pro/Epilogue.

Dialog Procedures.asm
ProcTabIcon:
        mov     lvbi.ulFlags, LVBKIF_TYPE_WATERMARK
        invoke  LoadImage, NULL, offset szLVBitmap, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION or LR_LOADFROMFILE
        mov     lvbi.hbm, eax
        invoke  SendMessage, hLVIcon, LVM_SETBKIMAGE, 0, addr lvbi
        
        invoke  SendMessage, hLVIcon, LVM_SETIMAGELIST, LVSIL_NORMAL, himlSysLg
        invoke  SendMessage, hLVIcon, LVM_SETIMAGELIST, LVSIL_SMALL, himlSysSm
        invoke  SendMessage, hLVIcon, LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_DOUBLEBUFFER, LVS_EX_DOUBLEBUFFER

Here we are setting the watermark for the listview.
Tell the listview we want a watermark with the flag LVBKIF_TYPE_WATERMARK.
You can use a bitmap from the resource section, or from a file. Here we are using a file. szLVBitmap == dic.bmp and it is in the apps directory. Load with LoadImage, put the returned handle into the hbm member
of LVBKIMAGE and set it with SendMessage. The listview takes ownership of the bitmap, so no need to delete the handle.

For our Icon listview, we need to set the large and small imageists to it. I also set an extended listview style of Double Buffer, so the images won't flicker when scrolling.

What is not shown here and is VERY IMPORTANT, is to tell the listview to "share imagelists" (since we are using the system imagelist), by setting the LVS_SHAREIMAGELISTS style bit in the resource file as I did, or use that style setting when using CreateWindowEx.
If we don't use this style, then once the listview is destroyed, it will destroy the imagelist also.

There are 3 radio buttons that will change the "views" of the listview. We change the view with the message LVM_SETVIEW.

ProcTabReport:
Right click an item in the Report view listview and a menu will show up to allow you to delete an item.
That popup is created with the following:
	    invoke  CreatePopupMenu
	    xchg    eax, esi
	    mov     hReportPopup, esi
	    invoke  AppendMenu, esi, MF_POPUP, IDM_DELETE, offset szDelete


we use the ID IDM_DELETE to handle the menu selection in WM_COMMAND.
    .elseif eax == WM_NOTIFY
        .if wParam == LSV_REPORT
            mov     edi, lParam
            mov     ecx, (NMHDR ptr[edi]).code
            .if ecx == NM_RCLICK
                invoke  GetCursorPos, addr pt
                invoke  ScreenToClient, (NMHDR ptr [edi]).hwndFrom, addr pt
                push    pt.x
                pop     lvhti.pt.x
                push    pt.y
                pop     lvhti.pt.y
                invoke  SendMessage, (NMHDR ptr [edi]).hwndFrom, LVM_HITTEST, 0, addr lvhti
                test    eax, eax
                js      @F
                mov     dwItemIndex, eax
                invoke  GetCursorPos, addr pt
                invoke  TrackPopupMenu, hReportPopup, TPM_LEFTBUTTON, pt.x, pt.y, NULL, hWin, NULL
            .endif            
        .endif
        @@:

To show the right click popup menu, we handle the WM_NOTIFY and check to see if the message is from our listview. If it is, then we check for the NM_RCLICK code. Once we receive this code, we call GetCursor pos then convert the POINT coords to client coords and pass these converted coords to this listview to get the index of the item that was right clicked. We save that index to the variable dwItemIndex so we know which item to, well, delete.

We get the Cursor position again and display the right click menu with TrackPopUpMenu and we use TPM_LEFTBUTTON, I do this because I only want the left mouse button to "fire" the menu selection.

Attached Image

This should help get started understanding the Listview control. As usual, exe and full source is attatched. Modify, learn, and just have fun with it!

Attached File(s)


This post has been edited by GunnerInc: 31 March 2012 - 06:11 PM


Is This A Good Question/Topic? 1
  • +

Replies To: MASM - Using a Listview control

#2 erkant  Icon User is offline

  • D.I.C Head

Reputation: 2
  • View blog
  • Posts: 108
  • Joined: 26-October 10

Posted 26 March 2012 - 04:18 AM

It's amazing. Thanks for sharing it!
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1