Tutorial 31: Control Listview

En este tutorial aprenderemos cómo crear y usar el control listview.

Descarga el ejemplo.

Teoría:

El control listview es uno de los controles comunes, treeview, richedit etc. Ya estás familiarizado con él, aunque no lo conzcas por su nombre. Por ejemplo, el panel derecho del Explorador de Windows es un control listview. Un control listview es bueno para desplegar ítems. A este respecto, es como un control listbox, pero con capacidades ampliadass
Puedes crear un control listview de dos maneras. El primer método también es el más fácil: crearlo con un editor de recursos. No hay que olvidar llamar a InitCommonControls dentro de tu código fuente asm. El otro método es llamar a CreateWindowEx en tu código fuente. Debes especificar el nombre correcto de la clase de ventana para el control, ie. SysListView32. La clase de ventana "WC_LISTVIEW" es incorrecta.
Hay cuatro métodos para ver los datos en un control listview: vistas de icono, icono pequeño, lista y de reporte. Puedes ver ejemplos de estas vistas seleccionando Ver->Iconos grandes (vista de icono), Iconos Pequeños (vista de iconos pequeños), Lista (vista de lista) y Detalles (vista de reporte). Las vistas son métodos de representación de datos: sólo afectan las apariencias de los datos. Por ejemplo, puedes tener varios datos en un control listview, pero si quieres, puedes ver sólo algunos de ellos. La vista de reporte es la más informativa, mientras que las otras ofrecen menos información. Puedes especificar la vista que quieras cuando creas un control listview. Luego puedes cambiar la lista cambiando a SetWindowLong, especificando la bandera GWL_STYLE .

Ahora que sabemos crear un control listview, veremos ahora cómo usarlo. Me concentraré en la vista de reporte, la cual puede demostrar muchas características del control listview. Los pasos para usar un control listview son los siguientes:

  1. Crear un control listview con CreateWindowEx, especificando SysListView32 como nombre de la clase.  En este momento puedes especificar la vista inicial.
  2. (si existe) Crear e inicializar las listas de imágenes a usar en los ítems de listview.
  3. Insertar la(s) columna(s) dentro del control listview. Este paso es necesario si el control listview va a usar vista de reporte.
  4. Insertar ítems u subítems dentro del control listview.

Columnas

En la vista de reporte, hay una o más columnas. Puedes pensar en el orden de datos dentro de la vista de reporte como una tabla: los datos ordenados en filas y columnas. Debes tener al menos una columna en tu control listview (sólo en la vista de reporte). En las vistas que no son la de reporte, necesitas insertar una columna porque sólo puede haber una y sólo una columna en esas vistas.
Puedes insertar una columna enviando LVM_INSERTCOLUMN al control listview.

LVM_INSERTCOLUMN
wParam = iCol
lParam = puntero a la estructura LV_COLUMN

iCol es el número de columna, comenzando desde 0.
LV_COLUMN contiene información acerca de la columna a ser insertada. Tiene la siguiente definición:

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

Nombre del Campo Significados
imask

Una colección de banderas (flags) que gobiernan cuáles miembros de la estrcutura son válidos. La razón de este miembro es que no todos los miembros de la estructura son usados al mismo tiempo. En cada situación sólo se usan algunos. Y esta estructura es usada para entrada y salida. Así que es importante *marcar* los miembros usados en esta llamada a Windows para que el sistema seoa cuáles miembros son válidos. Las banderas disponibles son:

LVCF_FMT = El miembro fmt es válido.
LVCF_SUBITEM = El miembro iSubItem es válido.
LVCF_TEXT = El miembro pszText es válido.
LVCF_WIDTH =El miembro lx es válido.

Puedes combinar las banderas de arriba. Por ejemplo, si quieres especificar la etiqueta del texto de una columna, debes suministrar el punteo a la cadena en el miembro pszText. Y debes decir a Windows que el miembro pszText contiene datos, especificando la bandera LVCF_TEXT en este campo, sino Windows ignorará el valor en pszText.

fmt

Especifica la alineación de los ítems/subítems [items/subitems] ien la columnaT Los valores disponibles son:

LVCFMT_CENTER = El texto está centrado.
LVCFMT_LEFT = El texto está alineado a la izquierda.
LVCFMT_RIGHT = El texto está alineado a la derecha.

lxEl ancho de la columna, en pixeles. Luego puedes cambiar el ancho de la columna con LVM_SETCOLUMNWIDTH.
pszTextContiene un puntero al nombre de la columna si esta estructura es usada para establecer las propiedades de la columna. Sui esta estrcutura es usada para recibir ls propiedades de una columna, este campo contiene un puntero a un buffer lo suficientemente grande para recibir el nombre de la columna que será regresado. En este caso, debes proporcionar el tamaño del buffer en cchTextMax. Puedes ignorar cchTextMax si quieres establecer el nombre de la columna porque el nombre debe ser una cadena ASCIIZ cuyo largo puede ser determinado por Windows.
cchTextMaxEl tamaño, en bytes, del buffer especificado en pszText. Este miembro es usado sólo cuando usas esta estructura para recibir info acerca de una columna. Si usas esta estructura para establecer las propiedades de una columna, este campo es ignorado.
iSubItemEspecifica el índice del subítem associado a esta columna. Este valor es usado como un marcador cuyo subítem está asociado con esta columna. Si quieres, puedes especificar un número absurdo en este campo y tu control listview seguirá corriendo como una brisa. El uso de este campo es mejor demostrado cuando tienes el número de columna y necesitas saber con cual subítem está asociada esta columna. Puedes solicitar al control listview enviandole el mensaje LVM_GETCOLUMN a él, especificando LVCF_SUBITEM en el miembro imask. El control listview llenará el miembro iSubItem con cualquier valor que especifiques en este campo cuando la columna sea insertada. Con el fin de que este método trabaje, necesitas inroducir el índice correcto de subítem en este campo.
iImage and iOrderUsado con Internet Explorer 3.0 y versiones superiores. No tengo info sobre este campo.

Así que después de que el control listview es creado, deberías insertar una o más columnas en él. Las columnas no son necesarias si no planificas cambiar el control listview a vista de reporte. Con el fin de insertar una columna, necsitas crear una estructura LV_COLUMN, llenarla con info necesaria, especificar el número de columna y luego enviar la estructura al control listview con el mensaje LVM_INSERTCOLUMN.

   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

El código de arriba muestra el proceso. Especifica el texto del encabezado de la columna y su ancho, luego envía el mensaje LVM_INSERTCOLUMN al control listview . Es simple.

 

Ítems y subítems

Los ítems son las entradas principales en el control listview. En otras vistas diferentes a las de reporte, sólo verás ítems en el control listview. Los subítems son detalles de los ítems. Un ítem puede tener uno o más subítems asociados. Por ejemplo, si el ítem es el nombre de un archivo, entonces puedes tener los atributos de este archivo, su tamaño, la fecha de creación del archivo, como subítems. En la vista de reporte, la columna en el extremo izquierdo contene ítems y las restantes columnas contienen subítems. Puedes pensar un ítem y un subítem como los RECORD de una base de datos. El ítem es la clave primaria del RECORD y los subítems son los campos en el RECORD.
Lo mínimo que necesitas son algunos ítems en tu control listview: los subítems no son indispensables. Sin embargo, si quieres dar más información al usuario acerca de los ítems, puedes asociarlos con subítems de manera que el usuario pueda ver los detalles en la vista de reporte [report view].
Insertas un ítem en el control listview enviándole el mensaje LVM_INSERTITEM. También necesitas pasarle la dirección de una estructura LV_ITEM en lParam. LV_ITEM tiene la siguiente definición:

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

Nombre del Campo Significados
imaskUna colección de banderas [flags] que indican cuales miembros son válidos en esta estructura para esta llamada. En general, este campo es similar al miembro imask de LV_COLUMN. Revisa tu referencia de la api de win32 para más detalles acerca de las banderas disponibles.
iItemEl índice del ítem a que refiere esta estructura. El índice es de base cero. Puedes pensar eque este campo contiene el número de "fila" de una tabla.
iSubItemEl índice del subítem asociado con el ítem especificado por el miembro iItem. Puedes pensar que este campo contiene la "columna" de una tabla. Por ejemplo, si quieres insertar un ítem dentro de un control listview nuevo que acabas de crear, el valor en iItem debería ser 0 (porque este item es el primero), y el valor en iSubItem también debería ser 0 (queremos insertar el ítem en la primera columna). Si quieres especificar un subítem asociado a este índice, el iItem debería ser el índice del ítem que quieres asociar (en el ejemplo es 0), el iSubItem debería ser 1 o más grande, dependiendo de la columna donde quieras insertar el subíndice. Por ejemplo, si tu control listview tiene 4 columnas, la primera columna contendrá los ítems. Las restantes tres columnas son subítems. Si quieres insertar un subítem dentro de la 4ta. columna, necesitas insertar el valor 3 en iSubItem.
state

Este miembro contiene banderas [flags] que reflejan el estado del ítem. El estado de un ítem puede cambiar debido a las acciones del usuario o puede ser modificado por nuestro programa. El estado incluye si el ítem tiene el foco / si está remarcado [hilited] / si es seleccionado por operaciones de corte / si está seleccionado. Además de las banderas de estados, también contiene un índice con base uno dentro de la imagen superpuesta [overlay] / imagen estado a ser usada por el ítem.

stateMaskYa que el miembro state de arriba puede contener las banderas de estado [state], el índice de la imagen superpuesta [overlay], y el índice de la imagen del estado, necesitamos decirle a Windows cuál valor queremos establecer o recuperar. El valor de este campo está reservado para tal uso.
pszTextLa dirección de una cadena ASCIIZ que será usada como la etiqueta del ítem en el caso del ítem que queremos establecer/insertar. En el caso que usemos esta estructura para recuperer la propiedad del ítem, este miembro debe contener la dirección de un buffer que será llenado con la etiqueta del ítem.
cchTextMaxEste campo es usado sólo cuando empleas esta estructura para recibir información acerca de un ítem. En este caso, este campo contiene el tamaño en bytes del buffer especificado en pszText.
iImageEl índice dentro de la lista de imágenes que contienen los iconos para el control listview. Este índice apunta al icono que será usado con este ítem.
lParamUn valor definido por el usuario que será empleado cuando quieras ordenar los ítems en el control listview. En pocas palabras, cuando dices al control listview que ordene los ítems, el control listview comparará los ítems en pares. Se te enviarán los valores lParam de los ítems de manera que puedas decidir cuál de los dos debería ser puesto primero en la lista. Si todavía estote confunde, no te preocupes. Más adelante aprenderás más acerca del ordenamiento.

Resumamos los pasos para insertar un ítem/subítem en un control listview.

  1. Crear una variable del tipo estructura LV_ITEM
  2. Llenarla con la información necesaria
  3. Enviar el mensaje LVM_INSERTITEM al control listview si quieres insertar un ítem. O si quieres *insertar* un subítem, enviar más bien el mensaje LVM_SETITEM. Esto puede más bien resultar confuso si no entiendes la inetrrelación entre un ítem y sus subítems. Los subítems son considerados como propiedades de un ítem. Así que puedes insertar subítems y no puedes tener un subítem sin un ítem asociado. Por ese motivo necesitas enviar el mensaje LVM_SETITEM para agregar un subítem en vez de LVM_INSERTITEM.

Mensajes /Notificaciones del control ListView

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.

Ordenando ítems/subítems

Puedes especificar el orden por defecto de un listview especificando los estilos LVS_SORTASCENDING o LVS_SORTDESCENDING en CreateWindowEx. Estos dos estilos ordenan los ítems usando solamente sus etiquetas. Si quieres ordenar los ítems de otra manera, necesitas enviar el mensaje LVM_SORTITEMS al control listview.

LVM_SORTITEMS
wParam = lParamSort
lParam = pCompareFunction

lParamSort es un valor definido por el usuario que será pasado para la función que compara. Puedes usar este valor de la manera que quieras.
pCompareFunction es la dirección de la función definida por el usuario que decidirá la salida [outcome] de la comparación de los ítems en el control listview. La función tiene el siguiente prototipo:

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

lParam1 y lParam2 son los valores en el miembro lParam de LV_ITEM tque especificas cuando insert as los ítems dentro del control listview.
lParamSort es el valor en wParam que envías con LVM_SORTITEMS

Cuando el control listview recibe el mensaje LVM_SORTITEMS, llama la función de comparación especificada en el parámetro lParam del mensaje cuando necesita preguntarnos por el resultado de la comparación etre dos ítems. En pocas palabras, la función comparación decidirá cual de los dos ítems enviados a él precederá al otro. La regla es simple: si la función regresa un valor negativo, el primer ítem (representado por lParam1) debería preceder al otro. Si la función regresa un valor positivo, el segundo ítem (representado por lParam2) debería preceder al primero. Si los ítems son iguales, se debe regresar cero.

Lo que hace que este método trabaje es el valor en lParam de la estructura LV_ITEM. Si necesitas ordenar los ítems (tal como cuando el usuario pulsa o hace cliack sobre el encabezado de una columna), necesitas pensar en un esquema de ordenamiento que haga uso de los valores en el miembro lParam. EN el ejemplo, pongo el índice del ítem en este campo, de manera que pueda obtener otra información acerca del ítem enviando el mensaje LVM_GETITEM. Nota que cuando los ítems son rearreglados, sus índices también cambian. Así que cuando el ordenamiento esté hecho en mi ejemplo, necesito actualizar los valores en lParam para reflejar los nuevos índices. Si quieres ordenar los ítems cuando el usuario hace click sobre el encabezado de una columna, necesitas procesar el mensaje de notificación LVN_COLUMNCLICK en tu procedimiento de ventana. LVN_COLUMNCLICK es pasado a tu procedimiento de ventana a través del mensaje WM_NOTIFY.

Ejemplo:

Este ejemplo crea un control listview y lo llena con los nombres y tamaños de los archivos en la carpeta actual. La vista por defecto es la de reporte. En la vista de reporte puedes hacer click sobre los encabezados de las columnas y los ítems serán ordenados en orden ascendente/descendente. Puedes seleccionar la vista que quieras a través del menú. Cuando haces doble click sobre un ítem, se desplegará un cuadro de mensaje mostrando la etiqueta del ítem.

.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

Análisis:

Lo primero que el programa hace cuando se crea la ventana principal es crear un control listview.

  .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

Llamamos CreateWindowEx, pasando el nombre de la clase de ventana "SysListView32". La vista por defecto es la vista de reporte, como se specificó por el estilo LVS_REPORT.

    invoke InsertColumn

Después de que el control listview es creado, insertamos columnas en él.

  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

Especificamos la etiqueta y el ancho de la primera columna, para almacenar los nombres de los archivos, en la estructura LV_COLUMN necesitamos establecer imask con las banderas LVCF_TEXT y LVCF_WIDTH . Llenamos el miembro szText con la dirección de la etiqueta y lx con el ancho de la columna, en pixeles. Cuando todo ya esté hecho, enviamos el mensaje  LVM_INSERTCOLUMN al control listview pasándole la estructura.

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

Cuando ya hayamos hecho la inserción de la primera columna, insertamos otra almacenando los tamaños de los archivos. Ya que necesitamos los tamaños alineados a la derecha de la columna, necesitamos especificar una bandera en el miembro fmt, LVCFMT_RIGHT. También debemos especificar la bandera LVCF_FMT en imask, además de LVCF_TEXT y LVCF_WIDTH.

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

El resto del código es simple. Ponemos la dirección de la etiqueta en pszText y el ancho en lx. Luego enviamos el mensaje LVM_INSERTCOLUMN al control listview, especificando el número de columna y la dirección de la estructura.

Cuando las columnas han sido insertadas, podemos llenar los ítems en el cotrol listview.

    invoke FillFileInfo

FillFileInfo tiene el siguiente código.

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

  invoke FindFirstFile,addr FileNamePattern,addr finddata

Llamamos a FindFirstFile para ibtener la información del primer archivo que coincida con el criterio de búsqueda. FindFirstFile tiene el siguiente prototipo:

FindFirstFile proto pFileName:DWORD, pWin32_Find_Data:DWORD

pFileName es la dirección del nombre del archivo que se va a buscar. Esta cadena puede contener comodines [wildcards]. En nuestro ejemplo, usamos *.*, que lleva a buscar todos los archivos en el directorio actual.
pWin32_Find_Data es la dirección de la estructura WIN32_FIND_DATA que será lleneda con la información acerca del archivo (si se encuentra).

Esta función regresa INVALID_HANDLE_VALUE en eax si no se encuentra un archivo que coincida. Sino regresa un handle de búsqueda que será usado en subsecuentes llamadas a FindNextFile.

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

Si se consigue un archivo, almacenamos el handle de búsqueda en una variable y luego limpiamos edi que será usado como el índice a los ítems (número de columna).

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

En este tutorial, todavía no quiero tratar con las carpetas, así que las filtro chequeando dwFileAttributes para archivos que tengan puesta la bandera FILE_ATTRIBUTE_DIRECTORY. Si son encontrados, salto a la llamada a FindNextFile.

          invoke ShowFileInfo,edi, addr finddata
          inc edi
      .endif

      invoke FindNextFile,FHandle,addr finddata     
    .endw

Insertamos el nombre y tamaño del archivo en el control listview llamando a la función ShowFileInfo. Luego incrementamos el número de fila actual en edi. Por último, procedemos a llamar a FindNextFile para buscar el siguiente archivo en la carpeta actual hasta que FindNextFile regrese 0 (que significa que no se han encontrado más archivos).

    invoke FindClose,FHandle
  .endif
  ret
FillFileInfo endp

Cuando todos los archivos en la carpeta actual estén enumerados, debemos cerrar el handle de búsqueda.

Ahora veamos en la función ShowFileInfo. Esta función acepta dos parámetros, el índice del ítem (número de fila) y la dirección de la estructura WIN32_FIND_DATA.

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

Se almacena la dirección de la estructura WIN32_FIND_DATA en edi.

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

Suninistraremos la etiqueta del ítem en lParam, así que ponemos las banderas LVIF_TEXT y LVIF_PARAM fdentro de imask. Luego ponemos el ítem [iItem] al número de fila pasado a la función y como este es el ítem principal, debemos llenar iSubItem con 0 (columna 0).

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

Luego ponemos la dirección de la etiqueta, en este caso, el nombre del archivo en la estructura WIN32_FIND_DATA, dentro de pszText. Como implementaremos ordenamiento en el control listview, debemos llenar lParam con un valor. Elijo poner el número de fila en este miembro de manera que pueda recuperar info sobre el ítem por su índice.

  invoke SendMessage,hList, LVM_INSERTITEM,0, addr lvi

Cuando estén llenos todos los campos necesarios en LV_ITEM, enviamos el menssaje LVM_INSERTITEM al control listview para insertar el ítem en él.

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

Estableceremos el subítem asociado con el ítem insertado dentro de la segunda columna. Un subítem sólo puede tener una etiqueta. Así que especificamos LVIF_TEXT en imask. Luego especificamos la columna en la que el subítem debería residir en iSubItem. En este caso, le ponemos 1 por incremento de iSubItem. La etiqueta que usaremos es el tamaño del archivo. Sin embargo, debemos convertirla primero en una cadena llamando a wsprintf. Luego ponemos la dirección de la cadena dentro de pszText.

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

Cuando estén llenos todos los campos necesarios en LV_ITEM, enviamos el mensaje LVM_SETITEM al control listview , pasándole la dirección de la estructura LV_ITEM. Nota que usamos LVM_SETITEM, no LVM_INSERTITEM porque consideramos un subítem como una propiedad de un ítem. Así que *ponemos* la propiedad del ítem, no insertamos un nuevo ítem.

Cuando todos los ítems estén insertos dentro del control listview, establecemos el color del texto y del fondo del control listview.

    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

Uso la macro RGB para convertir los valores rojo, verde y azul dentro de eax y usarlo para especificar el color que necesitamos. Ponemos los colores de la superficie y del fondo del texto con los mensajes LVM_SETTEXTCOLOR y LVM_SETTEXTBKCOLOR. Ponemos el control de fondo del control enviando el mensaje LVM_SETBKCOLOR al control listview.

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

Dejamos que el usuario elija las vistas que quiera a través del menú. Así que debemos obtener primero el handle del menú. Para ayudar al usuario seguir la pista de la vista actual, ponemos un botón de radio. Por eso llamamos a CheckMenuRadioItem. Esta función pondrá un botón de radio antes del ítem del menú.

Nota que creamos el control listview con ancho y altura igual a 0. El tamaño será adaptado luego cada vez que la ventana padre cambie de tamaño. De esta manera, podemos asegurar que el tamaño del control listview control siempre coincidirá con el de la ventana padre. En nuestro ejemplo, queremos que el control listview control llene toda el área cliente de la ventana padre.

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

Cuando la ventana padre recibe el mensaje WM_SIZE, la palabra baja de lParam contiene el nuevo ancho del area cliente de la ventana y la palabra alta la nueva altura. Luego llamamos a MoveWindow para cambiar el tamaño del control listview para cubrir toda el área cliente de la ventana padre.

Cuando el usuario seleccione una vista en el menú, debemos cambiar la vista en el control de acuerdo a la elección. Esto lo hacemos enviando un nuevo estilo en el control listview con SetWindowLong.

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

Lo primero que hacemos es obtener los estilos actuales del control listview. Luego limpiamos elanterior estilo de vista regresado en las banderas de estilo. LVS_TYPEMASK es una constante que es el valor combinado de todas las 4 constantes de estilos de vista (LVS_ICON+LVS_SMALLICON+LVS_LIST+LVS_REPORT). Así que cuando ejecutemos la operación and sobre las actuales banderas de estilo con el valor "not LVS_TYPEMASK", equivale a suprimir y limpiar el actual estilo de vista.

Al diseñar el menú, I cheat a little. uso las constantes del estilo de vista como IDs de menú.

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

Así que cuando la ventana padre recibe el mensaje WM_COMMAND, el estilo de vista deseado está en la palabra baja de wParam como el ID del menú.

      mov edx,wParam
      and edx,0FFFFh

Tenemos el estilo de vista deseado en la palabra baja de wParam. Todo lo que tenemos que hacer es limpiar con cero la palabra alta.

      push edx
      or eax,edx

Y agregamos el estilo de vista deseado a los estilos existentes (menos el estilo de vista actual) del control listview.

      invoke SetWindowLong,hList,GWL_STYLE,eax

Y ponemos los nuevos estilos con SetWindowLong.

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

También necesitamos poner el botón de radio en frente del ítem de menú selected view menu item. Así que llamamos a CheckMenuRadioItem, pasándole el estilo de vista actual (doble como ID de menú).

Cuando el ususario hace click sobre los encabezados de la columna en la vista de reporte, queremos ordenar los ítems del control listview. Debemos responder al mensaje WM_NOTIFY.

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

Cuando recibimos el mensaje WM_NOTIFY, lParam contiene el puntero a una estructura NMHDR. Podemos chequear si este mensaje es del control listview comparando el miembro hwndFrom de NMHDR al handle tal control listview.  Si coinciden, podemos asumir que el mensaje vino del control listview.

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

Si la notificación viene del control listview, chequeamos si el código es VN_COLUMNCLICK. Si lo es , significa que el usuario ha hecho un click sobre el encabezado del control listview. En el caso de que el código sea LVN_COLUMNCLICK, podemos asumir que lParam contiene el puntero a una estructura n NM_LISTVIEW sque es un superconjunto de la estructura NMHDR. Luego necesutamos saber sobre cual encabezado de columna el usuario ha hecho click. Un examen del miembro iSubItem revela esta info. El valor en iSubItem puede ser tratado como el número de la columna , comenzando desde 0.

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

En el caso de que el iSubItem sea 1, significa que el usuario ha hecgho click sobre la segunda columna, tamaño. Usamos las variables de estado para mantener el estado actual del ordenamiento,. 0 significa "no ordenar todavía ", 1 significa "ordenar en sentido ascendente ", 2 significa "ordenar en sentido descendente". Si los ítems/subítems en no han sido ordenados antes, ponemos el orden en sentido ascendente.

            invoke SendMessage,hList,LVM_SORTITEMS,1,addr CompareFunc

Enviamos el mensaje LVM_SORTITEMS al control listview, pasando 1 en wParam y la dirección de nuestra función de comparación en lParam. Nota que el valor en wParam es usuario-definido, puedes usarlo en la manera que te guste. Yo lo uso como método de ordenamiento en este ejemplo. Echemos una mirada a la primera función de comparación.

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

En la función de comparación (CompareFunc), el control listview control pasará el valor lParam (en LV_ITEM) de los dos ítems que necesita comparar en lParam1 y lParam2. Recordarás que ponemos el índice del ítem en lParam. Así que podemos obtener información acerca de los ítems solicitándolo al control listview usando los índices. La info que necesitamos es la etiqueta de los ítems/subítems que están siendo ordenados. Así que preparamos una estructura LV_ITEM para este propósito, especificando LVIF_TEXT en imask,la dirección del buffer en pszText y el tamaño del buffer en cchTextMax.

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

Si el valor en SortType es 1 o 2, sabemos que la columna de tamaño ha sido objeto de un click. 1 significa ordenar los ítems de acuerdo a sus tamaños en sentido ascendente. 2 significa lo inverso. Así que especificamos iSubItem como 1 (para especificar el tamaño de la columna) y enviamos el mensaje LVM_GETITEMTEXT al control listview para obtener la etiqueta (cadena del tamaño) del subítem.

    invoke String2Dword,addr buffer
    mov edi,eax

Se convierte el tamaño de la cadena en un valor dword con String2Dword que es la función que escribí. Regresa el valor dword en eax. Lo almacenamos en edi para compararlo luego.

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

Asímismo se hace con el valor en n lParam2. Cuando tenemos los tamaños de los dos archivos, podemos compararlos.
La regla de la función de comparación es como sigue:

En este caso, queremos ordenar los ítems de acuerdo a sus tamaños en orden ascendente. Así que simplemente podemos sustraer el tamaño del primer ítem al del segundo y regresar el resultado en 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

En el caso que el usuario haga click sobre el nombre del archivo, debemos comparar los nombres de los archivos. Primero obtenemos el nombre de los archivos y luego los comparamos con la función lstrcmpi. Podemos regresar el valor de retorno de lstrcmpi sin ninguna modificación, ya que también usa la misma regla de comparación, por ejemplo. el valor negativo en eax si la primera cadena es menor que la segunda.

Cuando los ítems hay sido ordenados, necesitamos actualizar los valores de lParam para que todos los ítems reflejen los nuevos índices llamando a la función UpdatelParam.

            invoke UpdatelParam
            mov SizeSortOrder,1

Esta función simplemente enumera todos los ítems en el control listview y actualiza los valores en lParam con los nuevos índices. Necesitamos hacer esto porque si no el siguiente ordenamiento no trabajará como se espera debido a que el valor en lParam es el índice del ítem.

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

Cuando el usuario hace doble click en un ítem, queremos desplegar un cuadro de mensaje con la etiqueta del ítem en él. Debemos chequear si el código en NMHDR es NM_DBLCLK. Si lo es, podemosproceder a obtener la etiqueta y desplegarla en un cuadro de mensajes.

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

   invoke SendMessage,hList,LVM_GETNEXTITEM,-1, LVNI_FOCUSED

¿Cómo sabemos cuál ítem ha sido objeto de un doble click? Cuando un ítem es objeto de un click o de un doble click, su estado es puesto en "enfocado". Incluso si muchos ítems son remarcados (seleccionados), sólo uno de ellos tendrá el foco. Nuestra tarea consiste en encontrar el ítem que tiene el foco. Esto lo hacemos enviando el mensaje LVM_GETNEXTITEM al control listview, especificando el estado deseado en lParam. -1 en wParam significa buscar todos los ítems. El índice del ítem es regresado en 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

Luego procedemos a obtener la etiqueta enviando el mensaje LVM_GETITEM al control listview.

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

Por último, desplegamos la etiqueta en un cuadro de mensajes.

Si quieres saber cómo usar los iconos en el control listview, puedes leer sobre ello en mi tutorial sobre el control treeview. Los pasos son exactamente los mismos.


Index

Next

[Iczelion's Win32 Assembly Homepage]

n u M I T_o r's   Programming Page

Este tutorial, original de Iczelion, ha sido traducido por:   n u M I T_o r

 

 

www.000webhost.com