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 ENDS
Poniż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