![]() |
Wyjście Spis treści Poprzedni Następny
Autor:
©Iczelion |
©2008 mgr Jerzy Wałaszek I LO w Tarnowie
|
Na tej lekcji nauczymy się tworzenia i wykorzystywania kontrolki widoku listy (listview control).

Załaduj {ten przykład}
Kontrolka widoku listy jest jedną ze wspólnych kontrolek jak kontrolki widoku drzewa, tekstu formatowanego, itp. Znasz ją, nawet jeśli nie wiedziałeś jaką ma nazwę. Na przykład prawy panel Eksplorera Windows jest kontrolka widoku listy. Nadaje się ona dobrze do wyświetlania elementów. W tym sensie przypomina pole listy (listbox), lecz o większych możliwościach.
Kontrolkę widoku listy możesz utworzyć na dwa sposoby. Pierwsza metoda jest jednocześnie najłatwiejsza: tworzymy ją za pomocą edytora zasobów. Tylko nie zapomnij o wywołaniu funkcji InitCommonControls z wnętrza swojego kodu źródłowego. Druga metoda wymaga wywołania w swoim kodzie funkcji CreateWindowEx. Dla kontrolki musisz określić właściwą nazwę klasy, tj. SysListView32. Klasa okna "WC_LISTVIEW" jest niepoprawna.
Istnieje cztery metody przeglądania danych w kontrolce widoku listy: widok ikon, małych ikon, listy oraz raportu. Każdy z tych widoków możesz zobaczyć wybierając w programie odpowiednią opcję z menu Widok. Widoki są po prostu metodami prezentowania danych i wpływają jedynie na ich wygląd. Na przykład, możesz umieścić dużą ilość danych w kontrolce widoku listy, lecz jeśli zechcesz, zostaną pokazane tylko niektóre z nich. Widok raportu przekazuje najwięcej informacji, a pozostałe widoki mniej. Rodzaj pożądanego widoku możesz określić w momencie tworzenia kontrolki widoku listy. Później widok ten możesz zmienić wywołując SetWindowLong ze znacznikiem GWL_STYLE.
Teraz gdy już wiemy, jak utworzyć kontrolkę listy, przejdziemy do sposobów jej wykorzystania. Skupię się na widoku raportu, który jest w stanie ukazać wiele cech kontrolki widoku listy. Kolejne kroki wykorzystania kontrolki są następujące:
Na widoku raportu występuje jedna lub więcej kolumn. Możesz potraktować układ danych na widoku raportu jak tablicę: dane są ułożone w rzędy i kolumny. Musisz posiadać przynajmniej jedną kolumnę w swojej kontrolce widoku listy (tylko na widoku raportu). Na innych widokach nie musisz wstawiać kolumn, ponieważ i tak może być w nich dokładnie jedna kolumna.
Kolumnę możesz wstawić do kontrolki wysyłając wiadomość LVM_INSERTCOLUMN.
| Wiadomość LVM_INSERTCOLUMN | |
|---|---|
| wParam | iCol - jest numerem kolumny, poczynając od 0. |
| lParam | wskazanie do struktury LV_COLUMN |
Struktura LV_COLUMN zawiera informacje na temat wstawianej kolumny. Posiada ona następującą definicję:
LV_COLUMN STRUCT
imask DD ?
fmt DD ?
lx DD ?
pszText DD ?
cchTextMax DD ?
iSubItem DD ?
LV_COLUMN ENDS
Powyższe znaczniki można ze sobą łączyć. Na przykład, jeśli chcesz określić etykietę tekstową kolumny, musisz podać wskazanie łańcucha tekstowego w polu pszText. Następnie informujesz system Windows, iż pole to zawiera dane określając znacznik LVCF_TEXT, w przeciwnym razie wprowadzona do pszText wartość wskazania zostanie zignorowana.
Zatem po utworzeniu kontrolki widoku listy powinieneś wstawić do niej jedną lub więcej kolumn. Kolumny nie są konieczne, jeśli nie planujesz przełączania na widok raportu. Aby wstawić kolumnę, należy utworzyć strukturę LV_COLUMN, wypełnić ją konieczną informacją, określić numer kolumny, a następnie przesłać tę strukturę do kontrolki widoku listy za pomocą wiadomości LVM_INSERTCOLUMN.
.DATA
...
Heading1 DB "Nazwisko",0
...
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
...
Powyższy fragment kodu demonstruje ten proces. Określa on tekst nagłówka kolumny oraz jej szerokość, a następnie wysyła wiadomość LVM_INSERTCOLUMN do kontrolki widoku listy. To jest właśnie tak proste.
Elementy są głównymi danymi dla kontrolki widoku listy. Na wszystkich widokach, za wyjątkiem widoku raportu, widoczne są tylko elementy. Podelementy są szczegółami opisu elementów. Dany element może posiadać jeden lub więcej powiązanych z nim podelementów. Na przykład, jeśli element jest nazwą pliku, to możesz skojarzyć z nim podelementy atrybutów pliku, jego rozmiaru, daty utworzenia, itp. Na widoku raportu pierwsza kolumna po lewej stronie zawiera elementy, a pozostałe kolumny zawierają podelementy. Możesz potraktować element z jego podelementami jako rekord bazy danych. Element jest podstawowym kluczem rekordu, a podelementy są polami w rekordzie.
Jako niezbędne minimum potrzebujesz w swojej kontrolce widoku listy kilka elementów: podelementy nie są konieczne. Jednakże, gdy planujesz przekazać użytkownikowi więcej informacji o elementach, możesz skojarzyć je z podelementami, aby użytkownik miał sposobność ujrzenia tych szczegółów na widoku raportu.
Element wstawia się do kontrolki widoku listy przesyłając do niej wiadomość LVM_INSERTITEM. Wraz z wiadomością należy w parametrze lParam przesłać adres struktury LV_ITEM. Struktura LV_ITEM posiada następującą definicję:
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 ENDSPoniższy znacznik bitowy używany jest z wiadomością powiadomienia LVN_GETDISPINFO:
Bity od 8 do 11 pola state określają indeks (zliczany począwszy od 1) obrazka nakładkowego na liście obrazków kontrolki. Zarówno lista obrazków ikon o pełnym rozmiarze jak i lista małych obrazków ikon może posiadać obrazki nakładkowe. Obrazek ten jest nakładany na podstawowy obraz ikony elementu. Jeśli bity te mają wartość zero, to element nie posiada obrazka nakładkowego. Do wyodrębnienia opisanych bitów użyj maski LVIS_OVERLAYMASK.
Bity od 12 do 15 pola state określają indeks (zliczany od 1) obrazka na liście obrazków stanu kontrolki. Obrazek stanu jest wyświetlany obok ikony elementu, aby zaznaczyć stan zdefiniowany przez aplikację. Jeśli bity te mają wartość 0, to element nie posiada obrazka stanu. Bity wyodrębniamy za pomocą maski LVIS_STATEIMAGEMASK.
Podsumujmy kroki niezbędne do wstawienia elementu / podelementu do kontrolki widoku listy.
Teraz gdy już wiesz, jak tworzyć i wypełniać elementami kontrolkę widoku listy, następnym krokiem będzie komunikowanie się z nią. Kontrolka widoku listy porozumiewa się z oknem nadrzędnym za pomocą wiadomości i powiadomień. Okno główne może nią kierować wysyłając do niej wiadomości. Kontrolka widoku listy powiadamia okno nadrzędne o ważnych lub interesujących zdarzeniach poprzez wiadomość WM_NOTIFY, tak jak i inne wspólne kontrolki.
Standardowy sposób sortowania można określić poprzez style LVS_SORTASCENDING (kolejność rosnąca) oraz LVS_SORTDESCENDING (kolejność malejąca) w wywołaniu funkcji CreateWindowEx. Te dwa style sortują elementy wykorzystując jedynie ich etykiety. Jeśli masz zamiar posortować elementy wg innych kryteriów, musisz wysłać wiadomość LVM_SORTITEMS do kontrolki widoku listy.
| Wiadomość LVM_SORTITEMS | |
|---|---|
| wParam | lParamSort - jest wartością zdefiniowaną przez użytkownika, która będzie przekazywana do funkcji porównującej. Możesz wykorzystać ją w dowolny sposób. |
| lParam | pCompareFunction - jest adresem funkcji zdefiniowanej przez użytkownika, która decyduje o wyniku porównania elementów w kontrolce widoku listy. |
Funkcja porównująca ma następujący prototyp:
CompareFunc PROTO lParam1:DWORD, lParam2:DWORD, lParamSort:DWORD
Gdy kontrolka widoku listy otrzyma wiadomość LVM_SORTITEMS, wywołuje funkcję porównującą określoną w parametrze lParam wiadomości, gdy potrzebny jej jest wynik porównania dwóch elementów listy. W skrócie, funkcja porównująca decyduje, który z dwóch elementów przesłanych do niej będzie poprzedzał drugiego. Zasady są bardzo proste: jeśli funkcja zwraca wartość ujemną, to pierwszy element (reprezentowany przez iParam1) powinien poprzedzać drugi. Jeśli funkcja zwraca wartość dodatnią, to drugi element (reprezentowany przez lParam2) powinien poprzedzać ten pierwszy. Jeśli oba elementy są równe, to funkcja musi zwrócić zero.
Sercem tej metody jest zawartość pola lParam w strukturze LV_ITEM. Jeśli planujesz sortować elementy (np. gdy użytkownik klika na nagłówku kolumny), musisz obmyślić schemat sortowania wykorzystujący zawartości pól lParam. W tym przykładzie umieszczam tam indeks elementu, aby otrzymywać inne informacje o elemencie wysyłając wiadomość LVM_GETITEM. Zwróć uwagę, iż po zmianie uporządkowania elementów zmianie ulegają również ich indeksy. Zatem po sortowaniu w moim przykładzie muszę uaktualnić wartości w polach lParam, aby odzwierciedlały nowe indeksy. Jeśli chcesz sortować elementy, gdy użytkownik klika w nagłówek kolumny, obsłuż wiadomość powiadomienia LVN_COLUMNCLICK w swojej procedurze okna. Powiadomienie LVN_COLUMNCLICK jest przekazywane do twojej procedury okna poprzez wiadomość WM_NOTIFY.
Prezentowany przykład tworzy kontrolkę widoku listy i wypełnia ją nazwami i rozmiarami plików w bieżącym katalogu. Standardowym widokiem jest widok raportu. Na widoku tym możesz klikać w nagłówki kolumn w celu sortowania elementów rosnąco i malejąco. Poprzez menu można zmieniać widoki. Gdy dwukrotnie klikniesz na jakimś elemencie, pojawi się okno wiadomości z etykietą tego elementu.
.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
RGB MACRO red, green, blue
mov eax, red + (green SHL 8) + (blue SHL 16)
ENDM
.CONST
IDM_MAINMENU EQU 10000
IDM_ICON EQU LVS_ICON
IDM_SMALLICON EQU LVS_SMALLICON
IDM_LIST EQU LVS_LIST
IDM_REPORT EQU LVS_REPORT
.DATA
ClassName DB "ListViewWinClass", 0
AppName DB "Testowanie kontrolki widoku listy", 0
ListViewClassName DB "SysListView32", 0
Heading1 DB "Nazwa pliku", 0
Heading2 DB "Rozmiar", 0
FileNamePattern DB "*.*", 0
template DB "%lu", 0
FileNameSortOrder DD 0
SizeSortOrder DD 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, 400, 300, 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 vi.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 esi String:DWORD
mov esi, String
xor eax, eax
lp1:
movzx ecx, BYTE PTR [esi]
inc esi
or ecx, ecx
jz exit
imul eax, 10
add eax, ecx
sub eax, "0"
jmp lp1
exit:
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) || (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
.IF SortType==1
sub edi, eax
mov eax, edi
.ELSE
sub eax, edi
.ENDIF
.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
.IF SortType==3
INVOKE lstrcmpi, ADDR buffer1, ADDR buffer
.ELSE
INVOKE lstrcmpi, ADDR buffer, ADDR buffer1
.ENDIF
.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
or ax, WORD PTR wParam
INVOKE SetWindowLong, hList, GWL_STYLE, eax
movzx eax, WORD PTR wParam
INVOKE CheckMenuRadioItem, hMenu, IDM_ICON,
IDM_LIST, eax, 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
movzx eax, WORD PTR lParam
movzx edx, WORD PTR lParam + 2
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
Pierwszą rzeczą wykonywaną przez program po utworzeniu głównego okna jest stworzenie kontrolki widoku listy.
.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
Wywołujemy funkcję CreateWindowEx przekazując jej nazwę klasy okna "SysListView32". Styl LVS_REPORT ustala standardowy widok kontrolki na widok raportu.
INVOKE InsertColumn
Po utworzeniu kontrolki widoku listy wstawiamy do niej kolumny.
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
Określamy etykietę oraz szerokość pierwszej kolumny, która ma przechowywać nazwy plików. Robimy to poprzez strukturę LV_COLUMN, stąd musimy ustawić w polu imask znaczniki LVCF_TEXT oraz LVCF_WIDTH. Pole pszText wypełniamy adresem etykiety, a do pola lx wprowadzamy szerokość kolumny w pikselach. Gdy wszystko jest gotowe, wysyłamy wiadomość LVM_INSERTCOLUMN do kontrolki widoku listy przekazując jej tę strukturę.
or lvc.imask, LVCF_FMT
mov lvc.fmt, LVCFMT_RIGHT
Po wstawieniu pierwszej kolumny wstawiamy drugą, która ma przechowywać rozmiary plików. Ponieważ będziemy je wyrównywać do prawego marginesu kolumny, określamy znacznik LVCFMT_RIGHT w polu fmt. Musimy również dodać znacznik LVCF_FMT w imask do znaczników LVCF_TEXT i LVCF_WIDTH.
mov lvc.pszText, OFFSET Heading2
mov lvc.lx, 100
INVOKE SendMessage, hList, LVM_INSERTCOLUMN, 1 , ADDR lvc
ret
InsertColumn ENDP
Pozostały kod jest już prosty. Umieszczamy adres etykiety drugiej kolumny w pszText i jej szerokość w lx. Następnie wysyłamy wiadomość LVM_INSERTCOLUMN do kontrolki widoku listy określając numer kolumny i adres struktury.
Gdy kolumny są wstawione, przechodzimy do wypełniania ich elementami.
INVOKE FillFileInfo
Procedura FillFileInfo ma następujący kod:
FillFileInfo PROC USES edi
LOCAL finddata: WIN32_FIND_DATA
LOCAL FHandle: DWORD
INVOKE FindFirstFile, ADDR FileNamePattern, ADDR finddata
Wywołujemy funkcję FindFirstFile w celu otrzymania informacji o pierwszym pliku, który odpowiada kryteriom poszukiwań. Funkcja ta posiada następujący prototyp:
FindFirstFile PROTO pFileName:DWORD, pWin32_Find_Data:DWORD
Funkcja zwraca wartość INVALID_HANDLE_VALUE w rejestrze eax, jeśli nie znaleziono pasującego pliku. W przeciwnym wypadku zostaje zwrócony uchwyt poszukiwawczy, którego można użyć w następnych wywołaniach FindNextFile do wyszukania pozostałych plików.
.IF eax!=INVALID_HANDLE_VALUE
mov FHandle, eax
xor edi, edi
Jeśli zostanie znaleziony jakiś plik, zapamiętujemy uchwyt poszukiwawczy w zmiennej, a następnie zerujemy rejestr edi, który będzie użyty w charakterze indeksu elementów (numeru wiersza).
.WHILE eax!=0
test finddata.dwFileAttributes, FILE_ATTRIBUTE_DIRECTORY
.IF ZERO?
Na tej lekcji nie chcę jeszcze zajmować się katalogami, zatem odfiltrowuje je sprawdzając, czy pole dwFileAttributes ma ustawiony znacznik FILE_ATTRIBUTE_DIRECTORY. Jeśli odczytany element jest katalogiem, przechodzimy do wywołania FindNextFile.
.IF ZERO?
INVOKE ShowFileInfo, edi, ADDR finddata
inc edi
.ENDIF
INVOKE FindNextFile, FHandle, ADDR finddata
.ENDW
Jeśli element jest plikiem, umieszczamy jego nazwę i rozmiar w kontrolce widoku listy wywołując funkcję ShowFileInfo. Następnie zwiększamy bieżący numer wiersza w rejestrze edi. Na koniec przechodzimy do wywołania funkcji FindNextFile do wyszukania następnego pliku w bieżącym katalogu, aż do momentu, gdy funkcja FindNextFile zwróci 0 (oznaczające brak dalszych plików).
INVOKE FindClose, FHandle
.ENDIF
ret
FillFileInfo ENDP
Gdy wszystkie pliki w bieżącym katalogu zostaną przeglądnięte, należy zamknąć uchwyt poszukiwawczy.
Teraz spójrzmy na funkcję ShowFileInfo. Przyjmuje ona dwa parametry, indeks elementu (numer wiersza) i adres struktury 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
Do rejestru edi trafia adres struktury WIN32_FIND_DATA.
mov lvi.imask, LVIF_TEXT + LVIF_PARAM
push row
pop lvi.iItem
mov lvi.iSubItem, 0
Dostarczamy etykietę elementu (nazwa pliku) oraz wartość w lParam, zatem w polu imask umieszczamy znaczniki LVIF_TEXT i LVIF_PARAM. Następnie w polu iItem umieszczamy przekazany numer wiersza, a skoro jest to element główny, wypełniamy pole iSubItem wartością 0 (kolumna 0).
lea eax, [edi].cFileName
mov lvi.pszText, eax
push row
pop lvi.lParam
Teraz przepisujemy adres etykiety, czyli w naszym przypadku nazwę pliku, ze struktury WIN32_FIND_DATA do pola pszText. Ponieważ w naszej kontrolce widoku listy chcemy zaimplementować sortowanie, wypełniamy pole lParam pewną wartością. Wybrałem umieszczanie w tym polu numeru wiersza, aby móc pobierać informacje o elemencie na podstawie jego indeksu.
INVOKE SendMessage, hList, LVM_INSERTITEM, 0, ADDR lvi
Gdy zostaną wypełnione wszystkie niezbędne pola w strukturze LV_ITEM, wysyłamy wiadomość LVM_INSERTITEM do kontrolki widoku listy, aby umieścić w niej ten element.
mov lvi.imask, LVIF_TEXT
inc lvi.iSubItem
INVOKE wsprintf, ADDR buffer, ADDR template, [edi].nFileSizeLow
lea eax, buffer
mov lvi.pszText, eax
W drugiej kolumnie ustawimy podelement skojarzony z dopiero co wstawionym elementem. Podelement może tylko posiadać etykietę. Stąd w polu imask ustawiamy znacznik LVIF_TEXT. W polu iSubItem wprowadzamy numer kolumny, w której powinien przebywać ten podelement. W naszym przypadku będzie to 1 poprzez zwiększenie o 1 zawartości pola iSubItem. Jako etykiety użyjemy długości pliku. Jednakże długość jest liczbą i musimy ją najpierw zamienić na łańcuch znaków przez wywołanie funkcji wsprintf. Po tej operacji wprowadzamy do pola pszText adres łańcucha znakowego.
INVOKE SendMessage, hList, LVM_SETITEM, 0, ADDR lvi
ASSUME edi: NOTHING
ret
ShowFileInfo ENDP
Gdy wypełnimy wszystkie niezbędne pola struktury LV_ITEM, wysyłamy wiadomość LVM_SETITEM do kontrolki widoku listy przekazując jej adres struktury LV_ITEM. Zwróć uwagę, iż używamy LVM_SETITEM zamiast LVM_INSERTITEM, ponieważ podelement jest traktowany jako własność elementu. Stąd "ustawiamy" własność elementu, a nie wstawiamy nowy element listy.
Gdy wszystkie elementy zostaną wstawione do kontrolki widoku listy, ustawiamy w niej kolory tekstu i tła.
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
Stosuję makro RGB do połączenia wartości składowych kolorów w liczbę w rejestrze eax określającą potrzebny nam kolor. Kolor czcionki i tła tekstu ustawiamy za pomocą wiadomości LVM_SETTEXTCOLOR i LVM_SETTEXTBKCOLOR. Kolor tła kontrolki widoku listy ustawiamy przy pomocy wiadomości LVM_SETBKCOLOR.
INVOKE GetMenu, hWnd
mov hMenu, eax
INVOKE CheckMenuRadioItem, hMenu, IDM_ICON, IDM_LIST,
IDM_REPORT, MF_CHECKED
Pozwolimy użytkownikowi wybrać poprzez menu interesujący go widok. Dlatego najpierw pobieramy uchwyt menu. Aby pomóc użytkownikowi w śledzeniu bieżącego widoku, umieścimy w menu system wyboru wyłącznego (radio button). Element menu odzwierciedlający bieżący widok będzie poprzedzony znakiem wyboru (duża kropka). Dlatego wywołujemy funkcję CheckMenuRadioItem. Funkcja ta umieszcza przed podanym elementem menu znak wyboru.
Zwróć uwagę, iż tworzymy kontrolkę widoku listy z szerokością i wysokością równą 0. Jej wymiary zostaną później dostosowane do rozmiarów obszaru roboczego okna nadrzędnego przy zmianie jego wymiarów. W ten sposób możemy zapewnić, iż rozmiary kontrolki zawsze będą w zgodzie z rozmiarami zawierającego je okna.
.ELSEIF uMsg==WM_SIZE
movzx eax, WORD PTR lParam
movzx edx, WORD PTR lParam + 2
INVOKE MoveWindow, hList, 0, 0, eax, edx, TRUE
Gdy okno nadrzędne otrzymuje wiadomość WM_SIZE, młodsze słowo lParam zawiera nową szerokość obszaru roboczego, a starsze słowo zawiera nową wysokość. Wywołujemy MoveWindow do ustawienia rozmiarów kontrolki i jej położenia na obszarze roboczym okna nadrzędnego. Gdy użytkownik wybierze jakiś widok w menu, musimy zgodnie z tym wyborem zmienić widok w kontrolce. Dokonujemy tego wybierając nowy styl w kontrolce widoku listy za pomocą funkcji SetWindowLong.
.ELSEIF uMsg==WM_COMMAND
.IF lParam==0
INVOKE GetWindowLong, hList, GWL_STYLE
and eax, NOT LVS_TYPEMASK
Pierwszą rzeczą do zrobienia jest pobranie bieżących stylów kontrolki widoku listy. W zwróconych znacznikach stylów usuwamy stare style. Stała LVS_TYPEMASK grupuje wszystkie 4 style widoków (LVS_ICON + LVS_SMALLICON + LVS_LIST + LVS_REPORT). Stąd operacja and na bieżących znacznikach stylów z wartością "NOT LVS_TYPEMASK" powoduje wyzerowanie tej grupy bitów, a więc usunięcie wszystkich starych stylów widoku kontrolki.
Projektując menu dokonałem małego oszustwa. Użyłem w charakterze numerów identyfikacyjnych ID elementów menu stałych stylów widoków.
.CONST IDM_MAINMENU EQU 10000 IDM_ICON EQU LVS_ICON IDM_SMALLICON EQU LVS_SMALLICON IDM_LIST EQU LVS_LIST IDM_REPORT EQU LVS_REPORT
Dlatego w momencie otrzymania przez okno główne wiadomości WM_COMMAND pożądany styl widoku znajduje się w młodszym słowie wParam jako numer identyfikacyjny elementu menu.
or ax, WORD PTR wParam
Mamy pożądany styl widoku w młodszym słowie wParam. Łączymy go z odczytanymi wcześniej stylami okna kontrolki widoku listy
INVOKE SetWindowLong, hList, GWL_STYLE, eax
Przy pomocy funkcji SetWindowLong nowe style zostają wcielone w życie.
movzx eax, WORD PTR wParam
INVOKE CheckMenuRadioItem, hMenu, IDM_ICON,
IDM_LIST, eax, MF_CHECKED
.ENDIF
Musimy również umieścić przed wybranym elementem menu znacznik wyboru. Zatem wywołujemy funkcję CheckMenuRadioItem przekazując jej bieżący styl widoku (pełniący również rolę numeru elementu menu).
Gdy użytkownik kliknie nagłówki kolumn na widoku raportu, chcemy posortować elementy w kontrolce widoku listy. Musimy zareagować na wiadomość WM_NOTIFY.
.ELSEIF uMsg==WM_NOTIFY
push edi
mov edi, lParam
ASSUME edi: PTR NMHDR
mov eax, [edi].hwndFrom
.IF eax==hList
Gdy otrzymamy wiadomość WM_NOTIFY, lParam będzie zawierało wskazanie struktury NMHDR. Sprawdzamy, czy wiadomość pochodzi od naszej kontrolki listy porównując zawartość pola hwndFrom struktury NMHDR z uchwytem kontrolki. Jeśli są równe, możemy przyjąć, iż powiadomienie nadeszło od niej.
.IF [edi].code==LVN_COLUMNCLICK ASSUME edi: PTR NM_LISTVIEW
Jeśli powiadomienie pochodzi od kontrolki widoku listy, sprawdzamy, czy jego kod ma wartość LVN_COLUMNCLICK. Jeśli tak, oznacza to kliknięcie na nagłówku kolumny. W takim przypadku przyjmujemy, iż lParam zawiera wskazanie struktury NM_LISTVIEW, która obejmuje strukturę NMHDR. W dalszej kolejności jest nam potrzebna informacja, w której kolumnie nastąpiło to kliknięcie. Informacji tej udzieli nam sprawdzenie pola iSubItem. Wartość w iSubItem można potraktować jako numer kolumny rozpoczynający się od 0.
.IF [edi].iSubItem==1
.IF SizeSortOrder==0 || SizeSortOrder==2
W przypadku gdy iSubItem wynosi 1, oznacza to, iż użytkownik kliknął nagłówek kolumny z rozmiarami plików. Stosujemy odpowiednie zmienne do przechowywania stanu posortowania elementów listy. 0 oznacza listę jeszcze nie posortowaną, 1 oznacza listę posortowaną rosnąco, a 2 oznacza listę posortowaną malejąco. Jeśli elementy / podelementy w danej kolumnie są jeszcze wcześniej nie posortowane lub posortowane malejąco, ustawiamy porządek sortowania na rosnący.
INVOKE SendMessage, hList, LVM_SORTITEMS, 1, ADDR CompareFunc
Wysyłamy wiadomość LVM_SORTITEMS do kontrolki widoku listy przekazując jej wartość 1 w wParam oraz adres naszej funkcji porównującej w lParam. Zwróć uwagę, iż wartość w wParam jest zdefiniowana przez użytkownika, możesz użyć jej w dowolny sposób. W tym przykładzie służy mi ona jako znacznik sposobu sortowania. Teraz spójrzmy na funkcję porównującą.
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
Do funkcji porównującej kontrolka widoku listy będzie przekazywała pola lParam (struktury LV_ITEM) dwóch elementów, które należy ze sobą porównać. Dane te są zawarte w kolejnych dwóch parametrach lParam1 i lParam2. Przypomnij sobie, iż w lParam umieszczaliśmy indeksy elementów. Zatem możemy otrzymać informację o tych elementach odpytując kontrolkę widoku listy przy wykorzystaniu ich indeksów. Potrzebna nam jest informacja o etykietach sortowanych elementów / podelementów. Przygotowujemy zatem strukturę LV_ITEM określając w polu imask znacznik LVIF_TEXT oraz adres bufora w pszText i jego rozmiar w cchTextMax.
.IF (SortType==1) || (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
.IF SortType==1
sub edi, eax
mov eax, edi
.ELSE
sub eax, edi
.ENDIF
Jeśli wartość parametru SortType wynosi 1 lub 2, to wiemy, iż użytkownik kliknął nagłówek drugiej kolumny. 1 oznacza sortowanie w porządku rosnącym, a 2 w malejącym wg rozmiarów plików. Ustawiamy pole iSubItem na 1, co wybierze nam drugą kolumnę, i wysyłamy wiadomość LVM_GETITEMTEXT do kontrolki widoku listy, aby pobrać z niej etykietę podelementu (rozmiar pliku). Etykietę zamieniamy na liczbę przy pomocy funkcji String2Dword i umieszczamy ją w rejestrze edi, który nie jest zmieniany przez funkcje API Win32. Identyczne operacje wykonujemy z drugim elementem. W efekcie rejestr edi zawiera rozmiar pliku pierwszego, a eax rozmiar pliku drugiego.
Gdy mamy rozmiary dwóch plików, możemy je ze sobą porównać.
Reguła funkcji porównującej jest następująca:
Zatem w naszym przypadku dla porządku rosnącego będzie (SortType == 1):
eax = edi - eax
A dla porządku malejącego (SortType == 2)
eax = eax - edi
To właśnie robi nasz kod.
.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
.IF SortType==3
INVOKE lstrcmpi, ADDR buffer1, ADDR buffer
.ELSE
INVOKE lstrcmpi, ADDR buffer, ADDR buffer1
.ENDIF
.ENDIF
ret
CompareFunc ENDP
W przypadku kliknięcia kolumny nazw plików, porównujemy nazwy. Najpierw pobieramy etykiety elementów i umieszczamy je w dwóch buforach. Następnie w zależności od wartości parametru SortType porównujemy ze sobą nazwę drugą z pierwszą (SortType = 3, porządek rosnący) lub nazwę pierwszą z drugą (SortType = 4, porządek malejący). Do porównania łańcuchów znakowych wykorzystujemy funkcję biblioteki Win32 API o nazwie lstrcmpi, ponieważ jej wynik możemy bezpośrednio zwrócić jako wynik naszej funkcji - stosują się one do identycznych reguł porównywania.
Gdy elementy zostaną posortowane, musimy uaktualnić wartości pól lParam wszystkich elementów, aby zgodne były z ich nowymi indeksami. Uzyskamy to poprzez wywołanie funkcji UpdatelParam.
INVOKE UpdatelParam
mov SizeSortOrder, 1
Funkcja ta po prostu kolejno numeruje wszystkie elementy w kontrolce widoku listy i uaktualnia zawartości pól lParam nowymi indeksami elementów. Krok ten jest niezbędny, w przeciwnym razie następne sortowanie nie zadziała wg naszych oczekiwań z uwagi na założenie, iż zawartości tych pól zawierają indeksy elementów na liście.
.ELSEIF [edi].code==NM_DBLCLK
INVOKE ShowCurrentFocus
.ENDIF
Gdy użytkownik kliknie podwójnie na jakimś elemencie listy, wyświetlamy okienko wiadomości z etykietą tego elementu. Musimy sprawdzić, czy pole code w strukturze NMHDR zawiera wartość NM_DBLCLK. Jeśli tak, to możemy przejść do operacji pobrania etykiety elementu i wyświetlenia jej w okienku informacyjnym.
ShowCurrentFocus PROC
LOCAL lvi: LV_ITEM
LOCAL buffer[256]: BYTE
INVOKE SendMessage, hList, LVM_GETNEXTITEM, -1, LVNI_FOCUSED
Skąd się dowiemy, który z elementów listy został podwójnie kliknięty? Gdy jakiś element zostaje kliknięty pojedynczo lub podwójnie, jego stan zostaje ustawiony na "skupiony" (focused). Nawet jeśli zostało zaznaczone wiele elementów, to tylko jeden z nich może otrzymać skupienie. Naszym zadaniem jest zatem odszukanie elementu listy posiadającego skupienie. Osiągniemy wynik wysyłając wiadomość LVM_GETNEXTITEM do kontrolki widoku listy z określeniem żądanego stanu elementu w lParam. Wartość -1 w wParam oznacza przeszukanie wszystkich elementów.. Indeks elementu zostanie zwrócony w rejestrze 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
Ustawiamy pola struktury LV_ITEM, a następnie przechodzimy do pobrania etykiety elementu przy pomocy wiadomości LVM_GETITEM.
INVOKE MessageBox, 0, ADDR buffer, ADDR AppName, MB_OK
ret
ShowCurrentFocus ENDP
Na koniec wyświetlamy tę etykietę w okienku informacyjnym.
Jeśli interesuje cię użycie ikon w kontrolce widoku listy, to przestudiuj lekcję o kontrolce widoku drzewa. Niezbędne kroki są prawie identyczne. Oczywiście, jak zawsze polecam pliki pomocy biblioteki Win32.
|
Autorem
kursu jest
Iczelion. Kurs
programowania Windows znalazł się na serwerze I LO w Tarnowie za pisemną
zgodą autora. |
![]() | I Liceum Ogólnokształcące |
Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl
W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe