En este tutorial aprenderemos cómo crear y usar el control listview.
Descarga el ejemplo.
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:
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. 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. |
| lx | El ancho de la columna, en pixeles. Luego puedes cambiar el ancho de la columna con LVM_SETCOLUMNWIDTH. |
| pszText | Contiene 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. |
| cchTextMax | El 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. |
| iSubItem | Especifica 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 iOrder | Usado 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.
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 |
|---|---|
| imask | Una 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. |
| iItem | El í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. |
| iSubItem | El í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. |
| stateMask | Ya 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. |
| pszText | La 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. |
| cchTextMax | Este 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. |
| iImage | El í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. |
| lParam | Un 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.
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.
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.
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
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.
[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