Dans ce tutorial, nous allons voir comment créer et employer un listview control.
Downloadez l'exemple.
Un listview control est aussi un 'common controls' (tutorial 18) tout comme le treeview (tutorial 19), richedit etc. En fait vous l'utilisez couramment sans même connaître son nom. Par exemple, la fenêtre de droite de l'Explorateur Windows est un listview control. Un listview control sert à afficher sous forme d'une liste un groupe de fichiers. En fait, c'est un peu comme une liste, mais avec certaines capacités en plus.
Vous pouvez créer un listview control de deux façons. La première méthode est aussi la plus simple : créez-le avec un éditeur de ressource. Simplement, n'oubliez pas ensuite d'appeler InitCommonControls dans votre code asm source. L'autre méthode c'est d'appeler CreateWindowEx dans votre code source. Vous devez spécifier le nom de la 'Window Class' (classe de fenêtre) correcte de ce control, c'est-à-dire SysListView32. La 'Window Class' "WC_LISTVIEW" n'est pas correcte.
Il y a quatre types de vues des données, dans un listview control : une vue sous forme d'icônes, de petites icônes, sous forme de texte inscrit et des vues sous forme de détails. Vous pouvez voir les exemples de ces vues en choisissant Affichage->Grandes icônes (la vue d'icônes), Affichage->Petites icônes (la vue en petites icônes), Affichage-> Liste (la vue de la liste) et Affichage-> Détails (la vue sous forme de détails). Ces vues sont juste une méthode de représentation des données : Elles affectent seulement la présentation des données. Par exemple, vous pouvez avoir beaucoup de données dans un listview control, mais si vous préférez, vous pouvez voir certaines d'entre elles. La vue détail est la plus informative alors que les vues restantes donnent moins de renseignements. Vous pouvez spécifier la vue que vous voulez quand vous créez un listview control. Vous pourrez même changer de vue plus tard en appelant SetWindowLong, en spécifiant flag GWL_STYLE.
Maintenant que nous savons comment créer un listview control, nous continuerons pour voir comment on peut l'employer. Je me concentrerai de la vue sous forme de détails qui fait appelle à pas mal des particularités du listview control. Les étapes dans l'utilisation d'un listview control sont les suivantes :
Dans la vue 'détails', il y a une ou plusieurs colonnes. Ces données qui sont mises dans différentes colonnes (en vue détails), représentes en fait un tableau de données : les données sont arrangées dans des rangées et des colonnes. Vous devez avoir au moins une colonne dans votre listview control (uniquement vrai pour la vue 'détails'). Pour les autres vues, vous n'avez pas besoin d'insérer de colonne parce qu'il nous faut une et seulement une seule colonne pour ces vues là.
Vous pouvez insérer une colonne en envoyant LVM_INSERTCOLUMN au listview control.
LVM_INSERTCOLUMN
wParam = iCol (nombre de colonnes)
lParam = pointe sur la structure LV_COLUMN
iCol est le nombre de colonnes, en partant de 0.
LV_COLUMN contient l'information sur la colonne qui doit être insérée. Voici sa définition :
LV_COLUMN STRUCT
imask dd ?
fmt dd ?
lx dd ?
pszText dd ?
cchTextMax dd ?
iSubItem dd ?
iImage dd ?
iOrder dd ?
LV_COLUMN ENDS
Noms | Définitions |
---|---|
imask |
Représente une collection de flags qui indique quels membres dans cette structure sont valides. Pourquoi utiliser ce membre ?, c'est pour ne pas que tous les membres dans cette structure soient employés en même temps. Seulement quelques membres sont employés dans certaines situations. Et cette structure est utilisée, à la fois pour les Entrées et les Sorties. C'est pourquoi il est important que vous *marquiez* les membres qui seront employés dans cet appel de Windows pour qu'il sache que ces membres sont valides. Voici les flags disponibles : LVCF_FMT
= Le membre fmt est valide. Vous pouvez combiner ces flags. Par exemple, si vous voulez déclarer le titre de la colonne (comme 'taille' par exemple, ou bien 'nom' ou 'modifié'), vous devez donner l'indicateur qui pointe sur la chaîne de caractères dans le membre pszText. Et vous devez dire à Windows que le membre pszText contient des données en rajoutant le flag LVCF_TEXT dans ce champ, sinon Windows ne tiendra pas compte de la valeur (du texte) dans pszText. |
fmt |
Indique à quel endroit doivent être placées les items et sous-items à l'intérieur des colonnes. Les valeurs disponibles sont : LVCFMT_CENTER = Texte centé. |
lx | Est la largeur de la colonne, en pixels. Vous pourrez plus tard la changer avec LVM_SETCOLUMNWIDTH. |
pszText | Dans le cas où cette structure est employée pour afficher les propriétés de la colonne, elle contient le pointer qui est sur le nom de la colonne. Par contre si cette structure est employée pour recupérer les propriétés d'une colonne, ce champ contient la valeur d'un pointer qui 'pointe' sur un assez grand buffer pour qu'il puisse recevoir le nom de la colonne qui sera renvoyée. Dans ce cas, vous devez indiquer la taille de ce buffer dans cchTextMax ci-dessous. Vous pouvez ignorer |
cchTextMax | Est la taille, en octets, du buffer indiqué, ci-dessus, dans pszText. Ce membre est seulement utilisé lorsque vous employez cette structure pour récupérer les renseignements d'une colonne. Si vous employez cette structure pour afficher les propriétés d'une colonne, ce champ est simplement ignoré. |
iSubItem | indique l'index du sous-item associé à cette colonne. Cette valeur est utilisée un peu comme un marqueur pour lequel le sous-item de cette colonne est associée. Si vous le souhaitez, même en mettant un nombre sans aucun sens dans ce paramètre votre listview control fonctionnera toujours les doits dans le nez. La meilleure démonstration de l'utilisation de ce paramètre, c'est quand vous avez un numéro de colonne et que vous avez besoin de savoir avec quel sous-item cette colonne est associée. Vous pouvez l'interroger le listview control en envoyant le message LVM_GETCOLUMN, en spécifiant LVCF_SUBITEM dans le membre imask. Le listview control remplira le membre iSubItem avec n'importe quelle valeur que vous avez spécifié dans ce paramètre quand la colonne a été insérée. Pour que cette méthode fonctionne, vous avez besoin que d'entrer dans ce paramètre le correcte index du sous-item. |
iImage et iOrder | Est utilisé avec 'Internet Explorateur 3.0' et plus. Je n'ai pas plus de renseignements à leur sujet. |
Ainsi après que le listview control est été créé, vous devez y insérer une ou plusieurs colonnes. Les colonnes ne sont pas nécessaires si vous ne projetez pas de faire fonctionner le listview control en vue 'détails'. Pour insérer une colonne, vous avez besoin de créer la structure LV_COLUMN, la remplir de l'information nécessaire, et indiquer le nombre de colonnes puis envoyer ensuite cette structure au listview control avec LVM_INSERTCOLUMN message.
LOCAL lvc:LV_COLUMN
mov lvc.imask,LVCF_TEXT+LVCF_WIDTH
mov lvc.pszText,offset Heading1
mov lvc.lx,150
invoke SendMessage,hList, LVM_INSERTCOLUMN,0,addr lvc
Ce petit bout de code vous montre comment faire. Il détermine le texte d'en-tête de la colonne ainsi que sa largeur à envoyer au listview control, grâce au message LVM_INSERTCOLUMN. C'est aussi simple que ça.
Les items sont les principales éléments dans un listview control. Dans les vues autres, que la vue 'détails', vous ne verrez que les items. Les sous-items sont les détails des items. Un unique item peut avoir un ou plus sous-items associés. Par exemple, si l'item est le nom d'un fichier, alors vous pouvez regarder (en tant que sous-items) ses attributs de fichier comme, sa taille, ou sa date de création. Dans la vue 'détails', la colonne extrême gauche contient les items et les colonnes restantes contiennent les sous-items. Vous pouvez vous représenter les items et leurs sous-items un peu comme une base de données. L'item est la clef principale du tableau alors que les sous-items sont ses éléments de description dans ce même tableau.
Au minimum, il y aura quelques Items dans votre listview control : les sous-items ne sont pas nécessaires. Cependant, si vous voulez présenter plus d'informations à l'utilisateur à propos de ces Items, vous devrez leurs associer leurs sous-items respectifs. De cette façon, l'utilisateur pourra voir les détails dans la vue 'détails'.
On insère un item dans le listview control en envoyant le message LVM_INSERTITEM. Vous avez aussi besoin de lui passer l'adresse de la structure LV_ITEM dans lParam. LV_ITEM a la définition suivante :
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
Nom | Significations |
---|---|
imask | Est un groupe de flags indiquant que les membres dans cette structure sont valides pour cet appel. En général, ce paramètre est semblable au membre imask de la structure LV_COLUMN vue plus haut. Référez vous à votre 'référence win32 api' pour plus de détail à propos des différents flags disponibles. |
iItem | Est l'index de l'item auquel cette structure se réfère. L'index de base est zéro. Vous pouvez vous représenter ce paramètre comme le numéro de la "rangée" d'un tableau. |
iSubItem | Est l'index du sous-item associé à l'item indiqué par iItem ci-dessus. Vous pouvez vous représenter ce paramètre comme étant le numéro de la "colonne" d'un tableau. Par exemple, si vous souhaitez insérer un item dans un tout nouveau 'listview control' juste créé, la valeur à mettre dans iItem serait 0 (parce que cet item est le premier) et la valeur dans iSubItem serait aussi 0 (car nous voulons insérer cet item dans la première colonne). Si vous souhaitez associer un sous-item à cet item, alors le iItem représenterait l'index de l'item avec lequel vous voulez l'associer (dans cet exemple, c'est 0). Le iSubItem ne pourrait être que 1 ou plus que 1, suivant la colonne où vous voulez insérer ce sous-item. Par exemple, si votre listview control a 4 colonnes, la première colonne contiendra tous les Items. Les 3 colonnes restantes sont utilisées par les sous-items. Si vous voulez insérer un sous-item dans la 4ème colonne, vous devez mettre valeur 3 dans iSubItem. |
state |
Ce membre contient les flags qui reflètent l'état de l'item. L'état de l'item peut changer à cause des actions de l'utilisateur, ou bien il peut être modifié par notre programme lui-même. The state includes whether the item has the focus/is hilited/is selected for cut operation/is selected. In addition to the state flags, It can also contains one-based index into the overlay image/state image for use by the item. |
stateMask | Puisque le membre 'state' ci-dessus peut contenir les flags qui définissent l'état de nos items, nous avons besoin de dire à Windows quelle valeur nous souhaitons mettre (ou remplacer si on a déjà définit son état). La valeur de ce paramètre sert à ça. |
pszText | Est l'adresse de la chaîne de caractère qui sera utilisée en tant que titre pour votre item dans le cas où vous souhaitez mettre ou insérer un item. Si nous nous servons de cette structure pour récupérer la propriété d'un item donné, ce membre doit contenir l'adresse du buffer qui sera rempli avec le titre de cet item. |
cchTextMax | Ce paramètre est uniquement utilisé lorsque vous employez cette structure pour recevoir des renseignements à propos de l'item. Dans ce cas, ce paramètre contient la taille en octets du buffer indiqué dans pszText, vu ci-dessus. |
iImage | Est l'index d'une icône contenue dans l'imagelist. Cet index indique quelle icône doit être employé et associée avec votre item. |
lParam | C'est une valeur définie par l'utilisateur, laquelle sera réutilisée lorsque vous trierez les Items de votre listview control. Bref, lorsque vous direz au listview control de trier vos Items, celui-ci comparera les Items deux par deux. Il vous renverra les valeurs des lParam des deux Items, et vous pourrez ainsi décider lequel des deux doit être inscrit en premier. Si vous êtes encore un peu dans le coaltar avec ça, ne vous inquiéter pas. Vous en apprendrez plus tard, bien plus du triage. |
On va récapituler les étapes clef pour l'ajout d'un item ou sous-item dans le listview control.
Maintenant que vous savez comment créer et remplir un ListView control, l'étape suivante c'est de communiquer avec lui. Le ListView control communique avec la fenêtre parente grâce à des messages et des avis. La fenêtre parente peut commander le ListView control en lui envoyant des messages. En retour, le ListView control fait le contraire, il informe sa fenêtre parente des événements importants ou intéressants grâce à WM_NOTIFY, comme les autres 'common controls'.
Vous pouvez spécifier le type de triage par défaut dans le listview control en indiquant les types de tris LVS_SORTASCENDING ou bien LVS_SORTDESCENDING dans CreateWindowEx. Ces deux types classent les Items d'après leurs noms uniquement. Si vous voulez les classer selon un autre critère, vous devez mettre le message LVM_SORTITEMS à l'intention du listview control.
LVM_SORTITEMS
wParam = lParamSort
lParam = pCompareFunction
lParamSort est une valeur définie par l'utilisateur laquelle on passera à la fonction de comparaison. Vous pouvez employer cette valeur de n'importe quelle façon, c'est à vous de voir.
pCompareFunction est l'adresse de la fonction(type de tri) que l'utilisateur à défini pour son listview control. Cette fonction a le prototype suivant :
CompareFunc proto lParam1:DWORD, lParam2:DWORD, lParamSort:DWORD
lParam1
et lParam2 sont les valeurs dans le membre de lParam de LV_item Lequel vous avez défini lorsque vous avez inséré vos Items dans le listview control.
lParamSort est la valeur de wParam que vous avez envoyée avec LVM_SORTITEMS
Quand le listview control reçoit le message LVM_SORTITEMS, il fait appel à la fonction de tri (ou de comparaison) indiquée par lParam, ainsi il nous retourne le résultat de la comparaison entre deux items. Bref, la fonction de comparaison servira à savoir lequel de deux items lui ayant été envoyé sera placé en premier par rapport à l'autre. La règle est simple : si la fonction renvoie une valeur négative, premier item (représenté par lParam1) doit être placé après l'autre. Si la fonction renvoie une valeur positive, cet item doit être placé en premier. Si les deux items sont égaux, un NULL sera renvoyé.
Ce qui fait que cette méthode fonctionne, c'est la valeur de lParam qui se trouve dans la structure LV_item. Si vous avez besoin de trier des Items (comme lorsque l'utilisateur clique sur l'en-tête d'une colonne, essayez ça marche !), vous avez besoin d'imaginer une méthode de tri qui se servira des valeurs dans le membre lParam (IParam1 et IParam2). Dans cet exemple, j'ai mis l'index de l'item dans ce paramètre donc je peux obtenir d'autres informations à propos de ces items en envoyant le message LVM_GETITEM. Remarquez que lorsque les Items sont réarrangés, leurs index changent aussi . De cette façon, quand le tri est fini dans mon exemple, j'ai encore besoin de remettre à jour les valeurs dans lParam pour retrouver les nouveaux index. Si vous voulez trier les Items lorsque l'utilisateur clique sur l'en-tête d'une colonne, vous avez besoin de traiter le message d'avis LVN_COLUMNCLICK dans votre procédure de fenêtre. On passe LVN_COLUMNCLICK à votre proc de fenêtre grâce au message WM_NOTIFY.
Cet exemple crée un 'listview control' et le remplit avec les noms et les tailles des fichiers du dossier actuel où vous êtes. La vue par défaut sera la vue 'détails'. Dans la vue 'détails', vous pouvez cliquer sur les en-têtes de colonne et les Items seront classés par ordre croissant/décroissant (soit par nom, soit par taille). Vous pouvez choisir la vue que vous souhaitez grâce au menu. Quand vous double cliquez sur un item, une boîte de message affichera le nom de l'item que vous venez de sélectionner.
.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
La première chose que le programme fait lorsque la fenêtre principale est créée, c'est de créer notre listview control.
.if uMsg==WM_CREATE
invoke CreateWindowEx, NULL, addr ListViewClassName, NULL,
LVS_REPORT+WS_CHILD+WS_VISIBLE, 0,0,0,0,hWnd, NULL, hInstance, NULL
mov hList, eax
On appelle CreateWindowEx, en lui passant le nom de sa window class soit : "SysListView32". La vue par défaut est la vue 'détails' définie par le style LVS_REPORT.
invoke InsertColumn
Après que le listview control est été créé, nous y insérons des colonnes.
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
Nous initialisons le label (son nom) et la largeur de la première colonne, qui sert au stockage des noms de fichiers, dans la structure LV_COLUMN. Donc nous avons besoin de mettre les flags LVCF_TEXT et LVCF_WIDTH dans imask . On rempli pszText avec l'adresse du label et lx avec la largeur de la colonne, en pixels. Quand tout est prêt, nous envoyons le message LVM_INSERTCOLUMN au listview control, en lui passant cette structure.
or lvc.imask,LVCF_FMT
mov lvc.fmt,LVCFMT_RIGHT
Quand on en a fini avec l'insertion de la première colonne, nous en insérons une autre servant cette fois-ci au stockage des tailles de fichiers. Puisque nous avons besoin des tailles pour les aligner à droite dans la colonne, nous avons besoin de mettre le flag LVCFMT_RIGHT dans le membre fmt. Nous devons aussi mettre le flag LVCF_FMT dans imask, en plus de LVCF_TEXT et LVCF_WIDTH.
mov lvc.pszText,offset Heading2
mov lvc.lx,100
invoke SendMessage,hList, LVM_INSERTCOLUMN, 1 ,addr lvc
Le code restant est simple. On Met l'adresse du label (du titre de la colonne) dans pszText et sa largeur dans lx. On envoie alors le message LVM_INSERTCOLUMN au listview control, en indiquant le nombre de colonnes et l'adresse de la structure.
Quand les colonnes sont insérées, nous pouvons placer les Items dans notre listview control.
invoke FillFileInfo
FillFileInfo présente le code suivant.
FillFileInfo proc uses edi
LOCAL finddata:WIN32_FIND_DATA
LOCAL FHandle:DWORD
invoke FindFirstFile,addr FileNamePattern,addr finddata
Nous appelons FindFirstFile pour obtenir l'information sur le premier fichier qui correspond à nos critères de recherche. FindFirstFile possède le prototype de fonction suivant :
FindFirstFile proto pFileName:DWORD, pWin32_Find_Data:DWORD
pFileName
est l'adresse du nom du fichier qu'on chercher. Cette chaîne de caractères peut contenir des caractères de substitutions. Dans notre exemple, nous employons *.*, ce qui fait qu'on mène une recherche sur tous les fichiers du dossier actuel où on se trouve.
pWin32_Find_Data est l'adresse de la structure WIN32_FIND_DATA laquelle sera remplie de l'information du fichier (si on l'a trouvé).
Cette fonction renvoie INVALID_handle_VALUE dans eax si aucun fichier correspondant à ce que l'on cherche n'a été trouvé. Sinon l'handle de la recherche est renvoyée, lequel sera utilisé dans les appels suivant FindNextFile.
.if eax!=INVALID_HANDLE_VALUE
mov FHandle,eax
xor edi,edi
Si un fichier est trouvé, on stocke l'handle de la recherche dans une variable et ensuite nous mettons edi à zéro pour pouvoir l'employé en tant qu'index pour les Items (c'est le numéro de rangée).
.while eax!=0
test finddata.dwFileAttributes,FILE_ATTRIBUTE_DIRECTORY
.if ZERO?
Dans cet exemple, je ne souhaite rien toucher dans les autres dossiers, donc je les filtre en vérifiant dwFileAttributes. Comme ça, si on trouve des fichiers qui ont un flag FILE_ATTRIBUTE_DIRECTORY associés, on saute par dessus grâce à l'appel FindNextFile.
invoke ShowFileInfo,edi, addr finddata
inc edi
.endif
invoke FindNextFile,FHandle,addr finddata
.endw
Nous insérons le nom et la taille du fichier dans le listview control en appelant la fonction ShowFileInfo. Donc on augmente le numéro de la rangée actuelle (dans edi). Finalement on continue à appeler FindNextFile pour chercher le fichier suivant dans le dossier actuel jusqu'à ce que FindNextFile retourne un 0 (Ce qui signifie qu'il n'y a plus de fichiers à chercher).
invoke FindClose,FHandle
.endif
ret
FillFileInfo endp
Quand tous les fichiers dans le dossier actuel ont étés passés à la loupe, on doit refermer l'handle de la recherche.
Maintenant on va regarder la fonction ShowFileInfo. Cette fonction accepte deux paramètres, l'index de l'item (son numéro de rangée) et l'adresse de la structure 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
On stocke l'adresse de la structure WIN32_FIND_DATA dans edi.
mov lvi.imask,LVIF_TEXT+LVIF_PARAM
push row
pop lvi.iItem
mov lvi.iSubItem,0
Nous donne le nom de l'item et sa valeur dans lParam donc nous mettons les flags LVIF_TEXT et LVIF_PARAM dans imask. Ensuite nous plaçons l'item dans la bonne rangée (dont on avait passé son numéro à la fonction) et puisque c'est l'item principal, on doit mettre un 0 dans iSubItem (car la première colonne, c'est la colonne 0).
lea eax,[edi].cFileName
mov lvi.pszText,eax
push row
pop lvi.lParam
Ensuite on met l'adresse de son nom, (dans ce cas), le nom du fichier dans pszText de la structure WIN32_FIND_DATA. Puisque nous effectuons un tri dans le listview control, nous devons remplir lParam avec une valeur. Je souhaite mettre le numéro de rangée dans ce membre donc je peux me servir des renseignements de l'item grâce à son index pour le retrouver.
invoke SendMessage,hList, LVM_INSERTITEM,0, addr lvi
Lorsque tous les paramètres nécessaires dans LV_item sont remplis, on envoie le message LVM_INSERTITEM au listview control pour y insérer un item.
mov lvi.imask,LVIF_TEXT
inc lvi.iSubItem
invoke wsprintf,addr buffer, addr template,[edi].nFileSizeLow
lea eax,buffer
mov lvi.pszText,eax
On place, dans la deuxième colonne, le sous-item associé à l'item qu'on vient juste d'insérer. Un sous-item peut seulement posséder un label (un titre). Donc on met LVIF_TEXT dans imask. Ensuite dans iSubItem, on indique la colonne dans laquelle le sous-item doit se trouver. Dans notre cas, nous met iSubItem à 1 en l'incrémentant. Le label que nous utiliserons représentera 'la taille du fichier'. Cependant, nous devons d'abord le convertir en une chaîne de caractères en appelant wsprintf. C'est pourquoi nous mettons l'adresse de la chaîne de caractères dans pszText .
invoke SendMessage,hList,LVM_SETITEM, 0,addr lvi
assume edi:nothing
ret
ShowFileInfo endp
Quand tous les paramètres nécessaires à LV_item sont remplis, nous envoyons le message LVM_SETITEM à notre listview control, en lui passant l'adresse de la structure LV_ITEM. Remarquez que nous utilisons LVM_SETITEM, et non pas LVM_INSERTITEM parce qu'un sous-item est considéré comme étant une propriété de l'item. Donc rappelez vous de ça, c'est important, on *pose* (set) la propriété de l'item, mais on n'insère pas un nouvel item.
Lorsque tous les Items sont insérés dans le listview control, on défini le type de texte et les couleurs de fond de notre listview control.
RGB 255,255,255
invoke SendMessage,hList,LVM_SETTEXTCOLOR,0,eax
RGB 0,0,0
invoke SendMessage,hList,LVM_SETBKCOLOR,0,eax
RGB 0,0,0
invoke SendMessage,hList,LVM_SETTEXTBKCOLOR,0,eax
J'emploie la macro RGB pour convertir les valeurs rouge, verte, est bleue dans eax et les employer pour définir la couleur réelle que je souhaite obtenir. On défini les couleurs de premier plan et d'arrière plan du texte avec les messages LVM_SETTEXTCOLOR et LVM_SETTEXTBKCOLOR. On défini la couleur de fond de notre listview control en lui envoyant le message LVM_SETBKCOLOR.
invoke GetMenu,hWnd
mov hMenu,eax
invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST, IDM_REPORT,MF_CHECKED
On laisse l'utilisateur choisit ses vues grâce au menu. Donc on doit d'abord récupérer l'handle du menu. Pour aider l'utilisateur à retrouver la vue actuelle, on incorpore un système de bouton radio dans notre menu. L'item du menu qui est associé à la vue actuelle sera ainsi précédé d'un bouton tout rond. Pour faire ça, on doit appeler CheckMenuRadioItem. Cette fonction affichera un bouton radio juste devant l'item voulu dans votre menu.
Remarquez qu'on crée un listview control d'une largeur et d'une hauteur égales à 0. Elle sera redimensionnée plus tard à chaque fois que la fenêtre parente sera elle-même redimensionnée. De cette façon, on peut être sûr que la taille de notre listview control correspondra toujours à celle de la fenêtre parente. Dans notre exemple, on souhaite que le listview control remplisse la totalité du secteur de client de la fenêtre parente.
.elseif uMsg==WM_SIZE
mov eax,lParam
mov edx,eax
and eax,0ffffh
shr edx,16
invoke MoveWindow,hList, 0, 0, eax,edx,TRUE
Quand la fenêtre parente reçoit le message WM_SIZE, le mot de poids faible de lParam contient la nouvelle largeur du secteur client et le mot de poids fort la nouvelle hauteur. Ensuite on appelle MoveWindow pour redimensionner le listview control pour qu'il recouvre le secteur client de la fenêtre parente, en entier.
Lorsque l'utilisateur sélectionne une des vue dans le menu. On doit faire en sorte de changer la vue du listview control en conséquence. On fait ça en mettant un nouveau style dans le listview control avec SetWindowLong.
.elseif uMsg==WM_COMMAND
.if lParam==0
invoke GetWindowLong,hList,GWL_STYLE
and eax,not LVS_TYPEMASK
La première chose à faire, c'est obtenir les styles actuels du listview control. Donc on purifie le vieux type de vue renvoyé dans les flags. LVS_TYPEMASK est une constante qui représente la valeur combinée des 4 types de vue (LVS_ICON+LVS_SMALLICON+LVS_LIST+LVS_REPORT). Ainsi quand on exécute l'opération and sur les types actuels de flags avec la valeur "not LVS_TYPEMASK", ça équivaut à nettoyer le type de vue actuel.
Dans la conception du menu, je triche un peu. J'emploie le type de vue des constantes en tant qu'IDs du menu.
IDM_ICON equ LVS_ICON
IDM_SMALLICON equ LVS_SMALLICON
IDM_LIST equ LVS_LIST
IDM_REPORT equ LVS_REPORT
Ainsi quand la fenêtre parente reçoit le message WM_COMMAND, le type de vue voulu est dans le mot de poids faible de wParam pour représenter l'ID du menu.
mov edx,wParam
and edx,0FFFFh
On retrouvera le type de vue souhaité dans le mot de poids faible de wParam. Tout ce que nous devons faire c'est de mettre le mot de poids fort à 0 (je parle de wParam).
push edx
or eax,edx
Et ajoutez le type de vue désiré aux styles déjà existants (moins le style de vue actuel) du listview control.
invoke SetWindowLong,hList,GWL_STYLE,eax
Et mettez les nouveaux types avec SetWindowLong.
pop edx
invoke CheckMenuRadioItem,hMenu,IDM_ICON,IDM_LIST,
edx,MF_CHECKED
.endif
Nous avons aussi besoin de mettre le bouton radio devant l'item choisi dans notre menu. Ainsi on appelle CheckMenuRadioItem, en lui passant le type de vue actuel (en double, en tant qu'ID du menu).
Lorsque l'utilisateur clique sur l'en-tête de la colonne dans la vue 'détails', on fait en sorte de déclencher le comportement de triage des Items dans notre listview control. Nous devons répondre au message WM_NOTIFY.
.elseif uMsg==WM_NOTIFY
push edi
mov edi,lParam
assume edi:ptr NMHDR
mov eax,[edi].hwndFrom
.if eax==hList
Quand on reçoit le message WM_NOTIFY, lParam contient le pointer qui est sur la structure NMHDR. On peut vérifier si ce message provient de notre listview control en comparant le membre hwndFrom dans NMHDR à l'handle de notre listview control. S'ils correspondent, alors on est sûr que cet avis provient bien de notre listview control.
.if [edi].code==LVN_COLUMNCLICK
assume edi:ptr NM_LISTVIEW
S'il provient bien du listview control, on vérifie si le code est LVN_COLUMNCLICK. Et si c'est ça, ça signifie que l'utilisateur a cliqué sur l'en-tête de la colonne. Dans le cas où le code serait LVN_COLUMNCLICK, on est sûr que lParam contient le pointer qui est sur la structure NM_LISTVIEW laquelle est un 'superset' de la structure NMHDR. On aura alors besoin de savoir sur lequel des en-tête de colonne, l'utilisateur à cliqué. L'examen du membre iSubItem nous révèle ces renseignements. La valeur dans iSubItem peut être considérée comme le numéro de colonne, en commençant par 0 pour la première colonne.
.if [edi].iSubItem==1
.if SizeSortOrder==0
|| SizeSortOrder==2
Dans le cas où iSubItem est égal à 1, ça signifie que l'utilisateur à cliqué sur la deuxième colonne, (la taille). On utilise des variables d'état pour récupérer le statut actuel de l'ordre de triage. 0 signifie "Pas encore trié", 1 signifie qu'"on tri l'item en le faisant monté d'une place dans la hiérarchie", 2 signifie qu'"on tri l'item en le faisant descendre dans la hiérarchie" des items. Si les Items ou sous-items de la colonne n'ont pas été trié auparavant, ou on été trié en ayant perdu des places, on impose un tri pour les remonter.
invoke SendMessage,hList,LVM_SORTITEMS,1,addr CompareFunc
On envoie le message LVM_SORTITEMS à notre listview control, en lui passant 1 dans wParam et l'adresse de notre fonction de tri dans lParam. Remarquez que la valeur dans wParam est définie par l'utilisateur, vous pouvez l'employer de la façon que vous le souhaitez. Je l'emploie en tant que méthode de triage dans cet exemple. Bon, on va d'abord jeter un coup d'oeil à la fonction de tri.
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
Dans la fonction de tri, le listview control passera les lParams (provenant de LV_item) de deux Items dont il a besoin de comparer pour les triés l'un par rapport à l'autre, soit lParam1 et lParam2. Vous vous rappellerez qu'on a mis l'index des l'items dans lParam. Ainsi on peut récupérer l'information des Items en interrogeant le listview control en employant ces index. Les renseignements dont nous avons besoin sont les labels (leurs noms) des Items / sous-items étant triés. Ainsi on prépare la structure LV_ITEM dans ce but, en déclarant LVIF_TEXT dans imask et l'adresse du buffer dans pszText et la taille du buffer dans cchTextMax.
.if SortType==1
mov lvi.iSubItem,1
invoke SendMessage,hList,LVM_GETITEMTEXT,lParam1,addr lvi
Si la valeur dans SortType est 1 ou 2, on sait que la colonne (de taille) a été cliquée. 1 signifie que les Items ont été triés selon leurs tailles dans un ordre montant. 2 signifie le contraire. Ainsi, on impose iSubItem à 1 (pour spécifier la colonne de taille) et on envoie le message LVM_GETITEMTEXT à notre listview control pour obtenir le label (le nom) de la chaîne de caractères (de taille) du sous-item.
invoke String2Dword,addr buffer
mov edi,eax
On cache plus ou moins la taille de la chaîne de caractères dans une valeur Dword avec la fonction String2Dword que j'ai écrite. Elle renvoie cette valeur Dword dans eax. Et en plus on la stocke dans edi pour la trier par la suite.
invoke SendMessage,hList,LVM_GETITEMTEXT,lParam2,addr
lvi
invoke String2Dword,addr buffer
sub edi,eax
mov eax,edi
Faites de même avec la valeur dans lParam2. Quand nous avons les tailles des deux fichiers, nous pouvons alors les comparer.
La règle de la fonction de tri est définie ainsi :
Dans ce cas, on souhaite trier les Items selon leurs critères de tailles par ordre croissant. Donc on peut utiliser la ruse d'une simple soustraction des tailles des deux items et renvoyer le résultat dans 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
Dans le cas où l'utilisateur clique sur la colonne 'de nom de fichier', nous devons forcément comparer les noms des fichiers. Il nous faut d'abord récupérer les noms de fichier et on les compare ensuite avec la fonction 'lstrcmpi'. On peut renvoyé la valeur de retour de lstrcmpi sans aucune modification puisqu'elle emploie aussi la même règle de tri. C'est à dire qu'on aura une valeur négative dans eax si la première chaîne de caractères est moindre (B
Une fois que les Items ont été triés, nous avons besoin de remettre à jour les valeurs de lParam de chaques Items pour refléter les nouveaux index en appelant la fonction UpdatelParam.
invoke UpdatelParam
mov SizeSortOrder,1
Cette fonction énumère simplement tout les Items du listview control et met à jour les valeurs dans lParam avec les nouveaux index. On doit absolument faire ça sinon le tri suivant ne marchera pas comme attendu, parce qu'on suppose que la valeur dans lParam est l'index de l'item.
.elseif [edi].code==NM_DBLCLK
invoke ShowCurrentFocus
.endif
Quand l'utilisateur double clique sur l'item, on souhaite que son nom s'affiche dans une boîte de message. On doit donc vérifier si le code dans NMHDR est NM_DBLCLK. Si c'est ça, on peut continuer pour récupérer son nom et le montrer dans la MessageBox.
ShowCurrentFocus proc
LOCAL lvi:LV_ITEM
LOCAL buffer[256]:BYTE
invoke SendMessage,hList,LVM_GETNEXTITEM,-1, LVNI_FOCUSED
Comment savons-nous quel item vient d'être double cliqué ? Et bien, lorsque un item est cliqué ou double cliqué, son état est remis à "jour". Même si beaucoup d'Items sont (hilited) sélectionnés avec un encadré pointillé, uniquement un d'entre eux a le centre (est réellement sélectionné). Notre travail, c'est de retrouver l'item qui a le centre. Nous faisons ça en envoyant le message LVM_GETNEXTITEM à notre listview control, en spécifiant l'état désiré dans lParam. -1 dans wParam signifie qu'on recherche tous les Items. L'index de l'item est renvoyé dans 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
On continue alors pour récupérer son titre (son nom) en envoyant le message LVM_GETITEM au listview control.
invoke MessageBox,0, addr buffer,addr AppName,MB_OK
Finalement, nous affichons le nom du fichier dans une MessageBox.
Si vous souhaitez savoir comment utiliser des icônes dans votre listview control, vous pouvez lire ça dans mon tutorial sur le treeview (Tutorial 19). Les étapes sont à peu près les mêmes.
[Iczelion's Win32 Assembly Homepage]