Rozdział XXXI - Kontrolka widoku listy


Na tej lekcji nauczymy się tworzenia i wykorzystywania kontrolki widoku listy (listview control).

 

 

Załaduj {ten przykład}

 

Teoria

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:

  1. Utwórz kontrolkę widoku listy za pomocą CreateWindowEx określają nazwę klasy jako SysListView32 . Możesz tutaj określić rodzaj początkowego widoku.
  2. Utwórz i zainicjuj listy obrazków, które mają być użyte z elementami widoku listy.
  3. Wprowadź pożądaną ilość kolumn do kontrolki widoku listy. Ten krok jest konieczny, jeśli kontrolka ma używać widok raportu.
  4. Wprowadź elementy i podelementy do kontrolki widoku listy.

Kolumny

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 i podelementy

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.

  1. Utwórz zmienną o typie struktury LV_ITEM.
  2. Wypełnij ją niezbędną informacją.
  3. Jeśli chcesz wstawić element, wyślij wiadomość LVM_INSERTITEM do kontrolki widoku listy. Jeśli chcesz wstawić podelement, wyślij wiadomość LVM_SETITEM. Jest to nieco mylące, o ile nie rozumiesz związków pomiędzy elementem a jego podelementami. Podelementy są traktowane jako własności elementu. Stąd możesz wstawiać elementy a nie podelementy, także nie może istnieć podelement bez powiązanego z nim elementu głównego. Dlatego należy wysłać wiadomość LVM_SETITEM w celu dodania podelementu zamiast wiadomości LVM_INSERTITEM.

Wiadomości i powiadomienia 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.

 

Sortowanie elementów i podelementów

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.

 

Przykład

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

Analiza

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.
Tłumaczenie z języka angielskiego, opracowanie HTML i konwersję przykładów programów wykonał mgr Jerzy Wałaszek.



List do administratora Serwisu Edukacyjnego Nauczycieli I LO

Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:
Uwaga: ← tutaj wpisz wyraz  ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048

 

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień szeroko opisywanych w podręcznikach.



   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2017 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.