Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: 'Złośliwiec'
Biblioteki C++

ListView

[lekcja]
List-View to jedna z przydatniejszych kontrolek, pozwalająca wyświetlać informacje w postaci tabeli. Lubi też sprawiać problemy (jak niemal wszystko w WinAPI), więc warto przyjrzeć się jej bliżej.

ListView

Zaczynamy oczywiście od utworzenia kontrolki. Na początek wołamy InitCommonControlsEx, jako że List-View należy do grupy ''Common Controls''. Potem tradycyjnie, bez większych niespodzianek:

C/C++
RECT rcl;
GetClientRect( hwnd, & rcl );
hListView = CreateWindowEx( 0, WC_LISTVIEW, NULL, WS_CHILD | WS_VISIBLE | LVS_REPORT |
LVS_EDITLABELS, 0, 0, rcl.right - rcl.left, rcl.bottom - rcl.top,
hwnd,( HMENU ) 1000, hInstance, NULL );

Kontrolka List-View oferuje cztery możliwe widoki: "duże ikony", "małe ikony", "lista" i "szczegóły" (ang. ''report''). Flaga LVS_REPORT powoduje wyświetlenie kontrolki w widoku "szczegóły", notabene chyba najczęściej używanym (pewnie właśnie dlatego nie jest domyślny, jak to w WinAPI). LVS_EDITLABELS powoduje, że mamy możliwość edycji etykietek w czasie wykonania programu – może się przydać np. do zmiany nazwy plików, które wyświetlamy w kontrolce.

Dodajemy zawartość

Pusta kontrolka nie wygląda z pewnością zbyt interesująco, więc dodajmy do niej parę kolumn. Są one widoczne tylko w widoku "szczegóły", jako nagłówki tabeli:

C/C++
LVCOLUMN lvc;
lvc.mask = LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;

lvc.iSubItem = 0;
lvc.cx = 200;
lvc.pszText = "Nazwa pliku";
ListView_InsertColumn( hListView, 0, & lvc );

lvc.iSubItem = 1;
lvc.cx = 50;
lvc.pszText = "Rozmiar";
ListView_InsertColumn( hListView, 1, & lvc );

lvc.iSubItem = 2;
lvc.cx = 100;
lvc.pszText = "Data";
ListView_InsertColumn( hListView, 2, & lvc );

Podobnie jak w przypadku wielu innych rzeczy w WinAPI, dodawanie kolumn polega na wypełnieniu odpowiedniej struktury (LVCOLUMN). Pole mask określa, które z pozostałych pól zawierają prawidłowe wartości. Nas na razie interesuje przede wszystkim ustawienie tekstu (LVCF_TEXT), a także szerokości kolumny (LVCF_WIDTH) oraz jej numeru (LVCF_SUBITEM). Następnie pozostaje nam już tylko wykorzystanie makra ListView_InsertColumn, albo bezpośrednie wysłanie komunikatu LVM_INSERTCOLUMN, podając wskaźnik do struktury.

Skoro mamy już kolumny, można dodać jakieś elementy do listy, co wygląda bardzo podobnie do dodawania kolumn. Tym razem pole iSubItem oznacza, że struktura, z której korzystamy (LVITEM) odnosi się do elementu (''item''), a nie kolumny (''subitem''), dlatego też ustawiamy to pole na 0:

C/C++
LVITEM lvi;
lvi.mask = LVIF_TEXT;

lvi.pszText = "file.dat";
lvi.iItem = 0;
lvi.iSubItem = 0;

ListView_InsertItem( hListView, & lvi );

lvi.pszText = "program.exe";
lvi.iItem = 1;
lvi.iSubItem = 0;

ListView_InsertItem( hListView, & lvi );

lvi.pszText = "archive.zip";
lvi.iItem = 2;
lvi.iSubItem = 0;

ListView_InsertItem( hListView, & lvi );

Efekt naszych starań będzie następujący:

ListView w widoku 'szczegóły' (Windows XP)
ListView w widoku 'szczegóły' (Windows XP)

Jak można dodać tekst do pozostałych kolumn? Można posłużyć się makrem ListView_SetItemText lub wysłać LVM_SETITEMTEXT. Pierwszy sposób jest o tyle wygodniejszy, że nie musimy wypełniać struktury LVITEM, tylko po prostu podajemy tekst:

C/C++
ListView_SetItemText( hListView, 0, 1, "15" );
ListView_SetItemText( hListView, 0, 2, "14 kwietnia 2003" );
ListView_SetItemText( hListView, 1, 1, "3701" );
ListView_SetItemText( hListView, 1, 2, "31 lutego 1999" );
ListView_SetItemText( hListView, 2, 1, "100" );
ListView_SetItemText( hListView, 2, 2, "7 sierpnia 2006" );

Pierwszym parametrem makra (nie licząc uchwytu kontrolki) jest numer elementu (liczony oczywiście od 0), zaś drugim – numer kolumny (liczony też od 0). Ponieważ kolumnę zerową mamy już wypełnioną, pozostaje nam tylko ustawienie tekstu w kolumnach 1 ("Rozmiar") i 2 ("Data"). Jeśli jednak chcemy, możemy oczywiście wykorzystać ListView_SetItemText do zmiany tekstu w zerowej kolumnie. A oto co uzyskaliśmy:

Wypełnione wszystkie kolumny (Windows XP)
Wypełnione wszystkie kolumny (Windows XP)

Ikonki

Rzeczą przydatną jest wyświetlanie ikonek w kontrolce List-View. W przypadku widoku "szczegóły" nie ma to aż takiego znaczenia, jak na przykład w widoku "duże ikony", ale i tak kontrolka z obrazkami wygląda wtedy dużo lepiej, a my możemy użytkownikowi przekazać dodatkowe informacje, np. o typie danego pliku.

Aby dorzucić ikony, musimy sobie najpierw stworzyć kontrolkę o nazwie ImageList, wypełnić ją kolejnymi obrazkami i przypisać do naszej List-View. Dobra wiadomość jest taka, że można narysować wszystkie ikonki na jednej bitmapie i wczytać ją do ImageList-y, a o dzielenie tego na kawałki martwić się już nie musimy. Tak więc do dzieła! Najpierw stworzymy kilka stałych, które będą oznaczały nasze ikonki:

C/C++
#define IDB_EXE 3000
#define IDB_FOLDER 3001
#define IDB_FILE 3002

W naszym przykładzie będziemy mieć 3 ikonki: dla plików, folderów i plików wykonywalnych (exe). Teraz jeszcze trzeba przygotować odpowiedni plik *.rc:

C/C++
IDB_EXE BITMAP "exe.bmp"
IDB_FOLDER BITMAP "folder.bmp"
IDB_FILE BITMAP "file.bmp"

Zakładam, że już masz odpowiednie pliki *.bmp do ikonek. Teraz możemy przejść do utworzenia kontrolki ImageList.

C/C++
HIMAGELIST himl;
HBITMAP hbmp;

himl = ImageList_Create( 16, 16, ILC_COLOR32, 3, 0 );

hbmp = LoadBitmap( hInstance, MAKEINTRESOURCE( IDB_EXE ) );
ImageList_Add( himl, hbmp,( HBITMAP ) NULL );
DeleteObject( hbmp );

hbmp = LoadBitmap( hInstance, MAKEINTRESOURCE( IDB_FOLDER ) );
ImageList_Add( himl, hbmp,( HBITMAP ) NULL );
DeleteObject( hbmp );

hbmp = LoadBitmap( hInstance, MAKEINTRESOURCE( IDB_FILE ) );
ImageList_Add( himl, hbmp,( HBITMAP ) NULL );
DeleteObject( hbmp );

Teraz mamy już gotową listę ikonek. Zwróć uwagę na flagę ILC_COLOR32, której użyliśmy w funkcji ImageList_Create. Dzięki niej będziemy mieć wszystkie kolory w naszych ikonkach. Domyślnie wybrane by zostało tylko znane z konsoli 16 kolorów, co jest efektem raczej niepożądanym ;-) Kontrolka ImageList została dokładniej opisana w artykule o » Biblioteki C++» Kurs WinAPI, C++» KontrolkiDrzewo (TreeView) lekcja więc nie będę tutaj wyjaśniał poszczególnych funkcji.

Aby dołączyć do ListView te ikonki możemy się posłużyć makrem ListView_SetImageList. Jako pierwszy argument podajemy uchwyt do kontrolki, jako drugi podajemy uchwyt do ImageListy a jako trzeci flagi. Ponieważ dodajemy ikonki do widoku szczegóły (czyli te małe) użyjemy flagi LVSIL_SMALL. Jeżeli mamy duże ikony do widoku "Duże ikony" to użyjemy flagi LVSIL_NORMAL.

C/C++
ListView_SetImageList( hListView, himl, LVSIL_SMALL );

Mamy już ikonki, teraz możemy je przypisać konkretnym elementom listy. W tym celu musimy nieco zmodyfikować kod odpowiedzialny za dodawanie elementów. Przede wszystkim do pola mask struktury LVITEM dorzucamy flagę LVIF_IMAGE. Teraz w tej strukturze będzie aktywne pole odpowiedzialne za ikonkę czyli iImage. Do niego należy wstawić index ikonki z kontrolki ImageList. Tak więc kod dodający elementy powinien wyglądać mniej więcej tak:

C/C++
VITEM lvi;
lvi.mask = LVIF_TEXT | LVIF_IMAGE; // to dodaliśmy

lvi.pszText = "file.dat";
lvi.iItem = 0;
lvi.iSubItem = 0;
lvi.iImage = 2; // to dodaliśmy

ListView_InsertItem( hListView, & lvi );

lvi.pszText = "program.exe";
lvi.iItem = 1;
lvi.iSubItem = 0;
lvi.iImage = 0; // to dodaliśmy

ListView_InsertItem( hListView, & lvi );

lvi.pszText = "archive.zip";
lvi.iItem = 2;
lvi.iSubItem = 0;
lvi.iImage = 2; // to dodaliśmy

ListView_InsertItem( hListView, & lvi );

// to też dodaliśmy:
lvi.pszText = "Nowy Folder";
lvi.iItem = 3;
lvi.iSubItem = 0;
lvi.iImage = 1;

ListView_InsertItem( hListView, & lvi );

Do naszej listy dodaliśmy jeszcze element reprezentujący folder - żeby wykorzystać wszystkie ikonki ;-) A oto efekt naszych starań:

ListView z ikonkami (Windows Vista)
ListView z ikonkami (Windows Vista)

Style rozszerzone ListView

Nasza kontrolka ma pewną denerwującą cechę. Jeśli klikniemy jakiś element, zostanie on zaznaczony, ale zaznaczenie będzie widoczne tylko w zerowej kolumnie. Można to na szczęście zmienić, ustawiając styl LVS_EX_FULLROWSELECT. "EX" w nazwie sugeruje, że ustawia się go w jakiś inny sposób, niż "zwykłe" style, no i faktycznie. Używamy tutaj makra ListView_SetExtendedListViewStyle lub ListView_SetExtendedListViewStyleEx. Pierwsze jest prostsze, ale używając tego drugiego można np. łatwo dodać nowy styl, nie usuwając poprzedniego i nie używając żadnych arytmetyki bitowej. Możemy też po prostu wysłać LVM_SETEXTENDEDLISTVIEWSTYLE do kontrolki. Poniżej korzystamy z pierwszego sposobu:

C/C++
ListView_SetExtendedListViewStyle( hListView, LVS_EX_FULLROWSELECT );

Rozszerzonych stylów jest więcej. Niektóre z nich są bardziej przydatne, inne mniej. Warto wymienić
LVS_EX_CHECKBOXES, który dodaje checkbox-y do elementów listy, LVS_EX_DOUBLEBUFFER (Common Controls 6.0, od XP w górę), który włącza podwójne buforowanie w procedurze rysowania kontrolki, co redukuje irytujące migotanie, LVS_EX_FLATSB, "spłaszczający" paski przewijania oraz LVS_EX_GRIDLINES, który powoduje rysowanie siatki na kontrolce, co ułatwia użytkownikowi czytanie informacji. Pozostałe style pozostawiam ci do własnych eksperymentów.

Grupy

Inną możliwością kontrolki ListView jest podział elementów na grupy. Jeżeli otworzymy w Exploratorze Windows folder "Mój Komputer" to nad ikonkami najprawdopodobniej zobaczymy nagłówek np. ''"Dyski Twarde"'' itp. To są właśnie grupy. Żeby móc ich używać w naszej kontrolce musimy się zapoznać z kolejną strukturą (o radości! ;P) o nazwie LVGROUP.
Użytkownicy kompilatora Dev-C++ muszą teraz przeżyć rozczarowanie - mianowicie Dev-C++ nie ma zdefiniowanej tej struktury, a także i wielu innych powiadomień, makr, stałych itd. związanych z grupami w kontrolce ListView. W związku z tym nie jest możliwe tworzenie ListView z grupami na Dev-C++.
Oto jak mniej więcej jest ona zadeklarowana:

C/C++
typedef struct tagLVGROUP {
   
UINT cbSize;
   
UINT mask;
   
LPWSTR pszHeader;
   
int cchHeader;
   
   
LPWSTR pszFooter;
   
int cchFooter;
   
   
int iGroupId;
   
   
UINT stateMask;
   
UINT state;
   
UINT uAlign;
   
#if _WIN32_WINNT >= 0x0600
   
LPWSTR pszSubtitle;
   
UINT cchSubtitle;
   
LPWSTR pszTask;
   
UINT cchTask;
   
LPWSTR pszDescriptionTop;
   
UINT cchDescriptionTop;
   
LPWSTR pszDescriptionBottom;
   
UINT cchDescriptionBottom;
   
int iTitleImage;
   
int iExtendedImage;
   
int iFirstItem;
   
UINT cItems;
   
LPWSTR pszSubsetTitle;
   
UINT cchSubsetTitle;
   
#endif
} LVGROUP, * PLVGROUP;
LPWSTR czyli ciąg znaków Unicode znalazł się w powyższym kodzie nieprzypadkowo. Mianowice struktura LVGROUP ma zdefiniowaną tylko i wyłącznie wersję unikodową i trzeba o tym pamiętać!
Jak widać, znaczna część tej struktury jest dostępna dopiero na systemie Windows Vista i nowszych. Omówimy sobie niektóre z pól tej struktury.

Pole Znaczenie
cbSize rozmiar struktury LVGROUP. Jeżeli chcemy używać tylko pól pod XP powinniśmy podać w tym polu wartość LVGROUP_V5_SIZE. Jeżeli będziemy korzystać z pól pod Vistę, nasz program nie odpali poprawnie na XP.
mask kombinacja flag oznaczających, które pola struktury zawierają poprawne informacje
pszHeader tytuł grupy (Unikodowy!)
cchHeader długość tytułu. Jest ona potrzebna tylko przy pobieraniu informacji o grupie. Przy jej dodawaniu jest ignorowana
iGroupId ID grupy, musi być różne dla każdej z grup.

Pozostałych pól nie będę omawiał, gdyż znajomość powyższych w zupełności wystarczy do prostej pracy z grupami i zrozumieniem przykładu omówionego w tym artykule. Jeżeli bardzo cię to ciekawi to możesz poczytać o tej strukturze w MSDN (LVGROUP Structure).

Przyjrzyjmy się teraz bliżej polu mask struktury LVGROUP. Jeżeli chcemy, by tytuł grupy był poprawny, wstawiamy tam flagę LVGF_HEADER. Natomiast do uaktywnienia pola iGroupId posłuży flaga LVGF_GROUPID. Zanim jednak zabierzemy się za dodawanie grup, musimy włączyć ich obsługę. Służy do tego komunikat LVM_ENABLEGROUPVIEW. W parametrze wParam podajemy TRUE czyli ''włącz'' ;-):

C/C++
SendMessage( hListView, LVM_ENABLEGROUPVIEW, TRUE, 0 );

Teraz możemy już utworzyć kilka grup. Posłuży nam do tego makro ListView_InsertGroup:

C/C++
LVGROUP lvg;
lvg.cbSize = LVGROUP_V5_SIZE; // pod XP
lvg.mask = LVGF_HEADER | LVGF_GROUPID;

lvg.pszHeader = L"Pliki";
lvg.iGroupId = 1;
ListView_InsertGroup( hListView, - 1, & lvg );

lvg.pszHeader = L"Foldery";
lvg.iGroupId = 2;
ListView_InsertGroup( hListView, - 1, & lvg );

Powyższy kod powinien się znaleść przed kodem odpowiedzialnym za dodawanie elementów (tym z LVITEM). Teraz przyjrzyjmy się bliżej temu kodowi. Do rozmiaru wstawiliśmy stałą LVGROUP_V5_SIZE bo używamy tylko XP'kowej części struktury. Literka L przed stringami oznacza, że są one Unikodowe. Powinienieś to już dawno wiedzieć, ale tak dla przypomnienia napisałem ;-). Jako drugi parametr makra ListView_InsertGroup wpisaliśmy -1. Dlaczego? A to dlatego, że ten argument oznacza po którym elemencie listy ma się pojawić grupa. Jak damy -1, to pojawi się na początku. Ponieważ nie mamy jeszcze żadnych elementów to wartość -1 wydaje się najrozsądniejsza.

No właśnie - elementy! Jak sprawić, żeby znalazły się w grupach? W tym celu trzeba nieco zmodyfikować kod, który dodaje nam elementy do kontrolki. W strukturze LVITEM mamy pole iGroupId do którego wpisujemy ID grupy, do której ma należeć element. Żeby uaktywnić to pole, trzeba do pola mask dorzucić flagę LVIF_GROUPID. Czyli teraz nasz kod dodający elementy powinien wyglądać tak:

C/C++
LVITEM lvi;
lvi.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_GROUPID; // to dodaliśmy

lvi.pszText = "file.dat";
lvi.iItem = 0;
lvi.iSubItem = 0;
lvi.iImage = 2;
lvi.iGroupId = 1; // to dodaliśmy

ListView_InsertItem( hListView, & lvi );

lvi.pszText = "program.exe";
lvi.iItem = 1;
lvi.iSubItem = 0;
lvi.iImage = 0;
lvi.iGroupId = 1; // to dodaliśmy

ListView_InsertItem( hListView, & lvi );

lvi.pszText = "archive.zip";
lvi.iItem = 2;
lvi.iSubItem = 0;
lvi.iImage = 2;
lvi.iGroupId = 1; // to dodaliśmy

ListView_InsertItem( hListView, & lvi );

lvi.pszText = "Nowy Folder";
lvi.iItem = 3;
lvi.iSubItem = 0;
lvi.iImage = 1;
lvi.iGroupId = 2; // to dodaliśmy

ListView_InsertItem( hListView, & lvi );

Teraz możemy już podziwiać rezultaty - nasze ListView z grupami:

ListView z grupami (Windows Vista)
ListView z grupami (Windows Vista)

Zaznaczenie

W kontrolce ListView często interesuje nas, która pozycja jest aktualnie zaznaczona. Aby się tego dowiedzieć, można posłużyć się makrem ListView_GetSelectionMark. Przyjmuje ono tylko jeden argument - uchwyt do kontrolki:

C/C++
int index = ListView_GetSelectionMark( hListView );

Makro to zwraca nam index zaznaczonej pozycji. Jeżeli żadna pozycja nie jest zaznaczona, zwracana jest wartość -1.

Wielokrotne zaznaczenie

Gdy użytkownik zaznaczy kilka elementów na raz, będziemy mieć znacznie bardziej skomplikowaną sytuację. Wtedy makro ListView_GetSelectionMark zwraca nam index pierwszej zaznaczonej pozycji, jednak w niektórych układach zaznaczeń makro to "nawala" i podaje niepoprawną pozycję. Dostępny jest za to komunikat LVM_GETSELECTEDCOUNT, który mówi nam ile pozycji jest zaznaczonych. Nie ma żadnego komunikatu, który pozwoliłby na pobranie indexów wszystkich zaznaczonych elementów.

W związku z tym musimy taką funkcję napisać sami. Zastanówmy się najpierw jak. Otóż ListView posiada pewien komunikat o nazwie LVM_GETNEXTITEM, który zwraca nam index następnego elementu. Ważną rzeczą jest to, że może on filtrować rezultaty i znajdować taki następny element, jaki spełnia nasze kryteria. A nasze kryteria to - ''jest zaznaczony''. To kryterium reprezentowane jest przez flagę LVNI_SELECTED umieszczaną w parametrze lParam komunikatu. Parametr wParam powinien zawierać index elementu od którego zaczynamy szukanie (-1 oznacza początek). Jeżeli nie będzie już więcej elementów, to komunikat zwróci -1.

Możemy więc wykorzystać ten komunikat, wywołując go w pętli dopóki nie zwróci -1, a wcześniej zwrócone indexy zapamiętywać w tablicy. Oto przykładowa implementacja takiej funkcji napisane przeze mnie ;-)

C/C++
int * ListView_GetSelectedItemsIndexes( HWND hListView ) {
   
int items = SendMessage( hListView, LVM_GETSELECTEDCOUNT, 0, 0 ); //(1)
   
if( items == 0 ) return NULL;
   
   
int * index = new int[ items ]; // (2)
   
int poprzedni = - 1; // (3)
   
int i = 0; // (4)
   
while( true ) { // (5)
       
int zaznaczony = SendMessage( hListView, LVM_GETNEXTITEM, poprzedni, LVNI_SELECTED ); // (6)
       
if( zaznaczony == - 1 ) break; // (7)
       
       
index[ i ] = zaznaczony; // (8)
       
poprzedni = zaznaczony; // (9)
       
i++; // (10)
   
}
   
return index; // (11)
}

Funkcja ta zwraca tablicę indexów elementów, które są zaznaczone, lub NULL jeśli żaden nie jest zaznaczony. Omówmy ją po kolei. Najpierw sprawdzamy ile elementów jest zaznaczonych (1) i zwracamy NULL jeśli żaden nie jest. Następnie alokujemy tablicę na indexy (2) o rozmiarze poznanym w kroku (1). Później deklarujemy zmienną pomocniczą poprzedni (3), która będzie przechowywać index poprzednio wyszukanego elementu. -1 oznacza początek, dlatego z taką wartością jest ona inicjalizowana. Tworzymy też zmienną i (4), która będzie nam mówić, do którego elementu tablicy zapisać index. Następnie przetwarzamy w pętli (5) kolejne wartości zwrócone przez LVM_GETNEXTITEM (6). Jeżeli zwróci -1 (7) to znaczy, że odczytaliśmy już wszystkie zaznaczone elementy i przerywamy pętlę. Jeśli nie, zapamiętujemy index w tablicy (9) i zwiększamy zmienną i (10). Na koniec zwracamy tablicę z indexami zaznaczonych elementów (11).

Powyższą funkcję można do woli stosować w swoich programach ;-)

Edycja etykiet

O edycji etykiet wspomnieliśmy już na początku artykułu, przy omawianiu stylu LVS_EDITLABELS. Warto przyjrzeć się bliżej temu zagadnieniu.

Gdy użytkownik kliknie na zaznaczonym elemencie kontrolki, edycja etykiet powinna rozpocząć się automatycznie. Można też wymusić edycję etykiet za pomocą komunikatu LVM_EDITLABEL. W parametrze wParam przyjmuje on index elementu, który ma być kliknięty. I tak też zrobimy - wymusimy edycję etykiet, gdy użytkownik podwójnie kliknie na element. W tym celu musimy obsłużyć powiadomienie NM_DBLCLK, które oznacza podwójne kliknięcie. Powiadomienie to przychodzi do nas ze strukturą NMITEMACTIVATE, która zawiera różne informacje o elemencie. Pole iItem to index elementu, który przekażemy do LVM_EDITLABEL. Zanim jednak to zrobimy, musimy się upewnić, że kontrolka ma focus'a. No to do dzieła:

C/C++
case WM_NOTIFY:
switch((( LPNMHDR ) lParam )->code ) {
case NM_DBLCLK:
   
if(((( LPNMHDR ) lParam )->hwndFrom ) == hListView ) {
       
LPNMITEMACTIVATE item =( LPNMITEMACTIVATE ) lParam;
       
if( item->iItem != - 1 ) {
           
SetFocus( hListView );
           
SendMessage( hListView, LVM_EDITLABEL, item->iItem, 0 );
       
}
    }
   
break;
}
break;

Teraz gdy podwójnie klikniemy w którymkolwiek miejscu wiersza, rozpocznie się edycja etykiet - czyli w zerowej kolumnie zniknie text, a zamiast niego pojawi się pole textowe do wprowadzenia nowego textu. Gdy rozpocznie się edycja etykiet, otrzymamy powiadomienie LVN_BEGINLABELEDIT. Przychodzi ono do nas ze strukturą NMLVDISPINFO, której pole item to struktura LVITEM zawierająca dane elementu, który edytujemy (to ta sama struktura, której użyliśmy przy dodawaniu elementów). Jeżeli chcemy, by dla niektórych elementów nie była możliwa edycja etykiet, to zwracamy wartość niezerową (np. 1):

C/C++
case WM_NOTIFY:
switch((( LPNMHDR ) lParam )->code ) {
case NM_DBLCLK:
   
// ...
   
break;
case LVN_BEGINLABELEDIT:
   
if(((( LPNMHDR ) lParam )->hwndFrom ) == hListView ) {
       
if(((( LPNMLVDISPINFO ) lParam )->item.iItem ) == 0 ) {
           
return TRUE;
       
}
    }
   
break;
}
break;

W naszym przykładzie poszliśmy na łatwiznę i zablokowaliśmy edycję pierwszego elementu (index 0) tylko po to aby pokazać, jak to się robi ;-). Inną ciekawą rzeczą jest to, że możemy się dobrać do kontrolki EDIT, która pojawia się w ListView. Służy do tego komunikat LVM_GETEDITCONTROL, który zwraca uchwyt do EDITa lub NULL jeśli EDITa nie ma. Możemy w ten sposób np. ograniczyć ilość znaków wpisywanych w kontrolce używając komunikatu EM_LIMITTEXT dotyczącego ogólnie pól textowych, a nie tylko tych z ListView.

Zmodyfikujemy więc nieco nasz przykład. Ustawimy maxymalną ilość znaków na 20. Oto jak to zrobić:

C/C++
case WM_NOTIFY:
switch((( LPNMHDR ) lParam )->code ) {
case NM_DBLCLK:
   
// ...
   
break;
case LVN_BEGINLABELEDIT:
   
if(((( LPNMHDR ) lParam )->hwndFrom ) == hListView ) {
       
if((((( NMLVDISPINFO * ) lParam )->item ).iItem ) == 0 ) {
           
return TRUE;
       
} else {
           
HWND hEdit =( HWND ) SendMessage( hListView, LVM_GETEDITCONTROL, 0, 0 );
           
SendMessage( hEdit, EM_LIMITTEXT, 20, 0 );
       
}
    }
   
break;
}
break;

Gdy użytkownik skończy już wpisywać nowy text i zatwierdzi enterem bądź anuluje escapem, to wysyłane jest powiadomienie LVN_ENDLABELEDIT. Przychodzi ono do nas ze strukturą NMLVDISPINFO tak samo jak LVN_BEGINLABELEDIT. Jak zapewne pamiętasz w polu item tej struktury siedzi inna struktura - LVITEM. Jeżeli pole pszText tej struktury jest równe NULL, to znaczy, że user anulował zmianę etykiety. Jeśli nie, jest tam nowy text wpisany przez usera. Warto też sprawdzić, czy nie jest on przypadkiem pusty, użytkownicy lubią czasem robić takie kawały. Tak więc do dzieła:

C/C++
case WM_NOTIFY:
switch((( LPNMHDR ) lParam )->code ) {
case NM_DBLCLK:
   
// ...
   
break;
case LVN_BEGINLABELEDIT:
   
// ...
   
break;
case LVN_ENDLABELEDIT:
   
if(((( LPNMHDR ) lParam )->hwndFrom ) == hListView ) {
       
LVITEM lvitem =(( NMLVDISPINFO * ) lParam )->item;
       
if( lvitem.pszText == NULL ) break;
       
       
if( lstrlen( lvitem.pszText ) < 1 ) break;
       
       
ListView_SetItemText( hListView, lvitem.iItem, 0, lvitem.pszText );
   
}
   
break;
}
break;

Zauważ, że użyliśmy makra ListView_SetItemText by ustawić nowy text. Zrobiliśmy to dlatego, że kontrolka automatycznie nie ustawia nowego textu - musimy to zrobić sami. Poza tym żadnych rewelacji nie ma, nie licząc rezultatu:

ListView z edycją etykiet (Windows Vista)
ListView z edycją etykiet (Windows Vista)
Poprzedni dokument Następny dokument
Drzewo (TreeView) Obszar statusu (Tray)