ListVIEW
Menu |
ListView informacje podstawowe.
Obiekt
ListView jest jednym spośród najmniej
opisanych i najrzadziej używanych obiektów, wynika to pewnie stąd, że nie bardzo
wiadomo jak go używać, a pomoc środowiska BCB na temat tego obiektu jest bardzo
uboga i mało przejrzysta. W tej poradzie przedstawię możliwości tego obiektu
tylko ogólnie, kolejne porady będą prezentowały w sposób szczegółowy
zastosowanie tego obiektu.
ListView
może być stosowany podobnie jak ListBox,
jednak może dodatkowo zawierać oprócz elementów Items,
również elementy SubItems,
dodatkowo komponent posiada możliwość tworzenia kolumn. Przed utworzeniem listy
w obiekcie należy najpierw utworzyć minimum jedną kolumnę poprzez wybranie w
Inspektorze Obiektów właściwości Columns.
Utworzenie jednej kolumny umożliwia utworzenie tylko listy przypominającej
ListBox,
jednak w odróżnieniu od tego obiektu możliwe jest włączenie nagłówka poprzez
ustawienie właściwości ShowColumnHeader
na true.
ListView posiada dwa
zasadnicze elementy umieszczające wartości na liście, pierwszy element to
właściwość Items,
kliknięcie na tej właściwości w Inspektorze Obiektów wywoła okno
ListView Items Editor, w oknie
tym można klikając na przycisku New Item
utworzyć nowy element listy (Items),
jest ona zawsze umieszczany w pierwszej kolumnie, przy czym
kolumny numerowane są od 0,
więc element ten jest umieszczany w kolumnie 0. Nazwę temu elementowi nadaje się
poprzez wypełnienie jego właściwości
Caption.
Do każdej właściwości Items
można przypisać właściwość SubItems
klikając na przycisku New SubItem.
Właściwość SubItems
jest zawsze przypisywana do zaznaczonej właściwości Items
i można tworzyć wiele tych właściwości przypisanych do różnych właściwości
Items. Właściwości
SubItems są
umieszczane w kolejnych kolumnach obiektu ListView,
więc jeżeli np. utworzymy 4 kolumny to możemy utworzyć tylko dla każdej
właściwości Items
tylko 3 właściwości SubItems,
jeśli utworzymy ich więcej, to wszystkie ponad 3 nie będą widoczne. Można
tworzyć dowolną liczbę Items
i jak napisałem wyżej ta właściwość zawsze jest umieszczana w kolumnie 1
(kolumna 0), natomiast właściwości SubItems
są umieszczane w kolejnych kolumnach, w kolejności w jakiej są dodawane do
listy.

Właściwości
Items w
połączeniu z SubItems
są dostępne dla ListView
tylko wtedy gdy jego właściwość ViewStyle
jest ustawiona na vsReport,
w pozostałych przypadkach widoczna jest tylko właściwość Items,
a SubItems jest
ignorowana. Istnieje możliwość przypisywania właściwościom
Items i SubItems
ikon poprzez dodanie do projektu obiektu ImageList
i umieszczenia w nim ikon i bitmap. Żeby móc dodawać do właściwości
Items ikony, należy we
właściwości StateImages
włączyć obiekt ImageList.
Ikony do właściwości Items
przypisuje się poprzez zmianę wartości parametru State Index,
wartośc -1 oznacza brak ikony, wszystkie wartości powyżej przypisują kolejne
ikony do tej właściwości z obiektu ImageList.
Przypisanie obiektu ImageList
do właściwości SmallImages
i LargeImages
umożliwi przypisywanie ikon do właściwości SubItems
poprzez właściwość Image Index.
Jak widać właściwość Items
posiada dwie właściwości umożliwiające przypisanie ikon, pierwsza to
Image Index przypisująca ikony
do tej właściwości przy ustawionym ViewStyle
na vsIcon, pod uwagę
brane są ikony umieszczone w obiekcie ImageList
przypisanym do LargeImage
w trybie vsIcon,
natomiast we wszystkich pozostałych trybach ViewStyle
przy ustawionym parametrze Image Index
brane są pod uwagę ikony umieszczone w ImageList
przypisanym do właściwości SmallImages.
Druga właściwość State Index
bierze pod uwagę ikony umieszczone w ImageList
przypisanym do właściwości StateImages
obiektu ListView.
Parametr State Index
jest brany pod uwagę we wszystkich trybach właściwości
ViewStyle. Do właściwości
Items można przypisać dwie
ikony, pierwsza pochodzi z ImageList
przypisanego do StateImages,
a druga z ImageList
przypisanego do SmallImages,
a w trybie ViewStyle = vsIcon
z ImageList
przypisanego do LargeImages.
Właściwość SubItems
pobiera ikony tylko z ImageList
przypisanego do SmallImages
i tylko w trybie ViewStyle = vsReport.
Kolumnom również można przypisywać ikony, ale tylko w trybie
ViewStyle = vsReport i tylko z
ImageList
przypisanego do właściwości SmallIcon.
W trybie ViewStyle
ustawionym na vsIcon
wyświetlanę są duże ikony na ListView
(o ile zostały przypisane), a pod ikonami przypisywany jest tekst. o tym czy
tekst ma być zawijany decyduje właściwość IconOptions ->
WrapText. Przy
ViewStyle ustawionym na
vsSmallIcon wyświetlane są
ikony z tekstem po prawej stronie, o tym czy w tych trybach ikony są
rozmieszczane w poziomie czy w pionie, decyduje właściwość
IconOptions -> Arangement.
Tworzenie listy raportu w trakcie działania programu.
Lista
raportu, zawiera elementy typu Items z przypisanymi do nich elementami SubItems
znajdujące się w jednym wierszu, sposób tworzenia takiej listy na etapie
programowania opisałem w poradzie ListView
informacje podstawowe, dlatego w tej poradzie pokażę jak tworzyć taką
listę w trakcie działania programu. Dodawanie nowych elementów Items do listy
odbywa się za pomocą funkcji Add(), funkcja nie pobiera żadnych elementów, a
tworzy jedynie pusty element typu Items nie zawierający nazwy, nazwę temu
elementowi przypisuje się za pomocą właściwości Caption. Właściwość ViewStyle
obiektu ListView1 należy ustawić na vsReport:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { ListView1->Items->Add(); ListView1->Items->Item[0]->Caption = "Item 0"; } //-------------------------------- |
Na żółto zaznaczyłem
wartość liczbową określającą, któremu elementowi listy przypisujemy właściwość
Caption. W tak skonstruowanym kodzie, klikanie w przycisk będzie powodowało
tworzenie nowych elementów listy (Items) , ale wartość Caption zawsze będzie
zmieniana tylko dla pierwszego elementu listy, numerowanie odbywa się od 0.
Jeżeli chcemy, żeby przy każdym kliknięciu przycisku przypisywać Caption nowo
utworzonemu elementowi, należy dodać zmienną static int:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { static int i = 0; ListView1->Items->Add(); ListView1->Items->Item[i]->Caption = "Item " + IntToStr(i); i++; } //-------------------------------- |
Zmienna statyczna ma to do siebie, że raz zainicjowana jakąś wartością, nie
jest inicjowana ponownie przy każdym wywołaniu, lecz przechowuje zawsze ostatnią
zapamiętaną wartość, dlatego jeżeli chcemy jej zmienić tą wartość, przywracając
np początkową to należ ją jej przypisać. Ten sam efekt można osiągnąć posługując
się zamiast zmiennej static klasą TListItem, klasa ta jest wykorzystywana przez
klasę TListView:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { TListItem *Li; Li = ListView1->Items->Add(); Li->Caption = "Items"; Li = ListView1->Items->Add(); delete Li; } //-------------------------------- |
Należy zwrócić uwagę na dwukrotne użycie funkcji Add() przed dodaniem
elementu listy i po jego dodaniu, w poprzednim przykładzie byłoby to
niedopuszczalne, a przy tej metodzie jest niezbędne.
Przedstawione przykłady dodawały jak dotychczas do listy tylko elementy Items,
ale można oczywiście dodawać również elementy SubItems, przy czym należy można
je dodawać razem z elementami Items, lub później podają numer elementu Item do
którego ma być przypisany SubItem, oto przykład dodawania elementów SubItems
razem z Items - pierwszy z wykorzystaniem operatora static, drugi z
wykorzystaniem klasy TListItem:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { static int i = 0; ListView1->Items->Add(); ListView1->Items->Item[i]->Caption = "Item " + IntToStr(i); ListView1->Items->Item[i]->SubItems->Add("SubItem " + IntToStr(i) + "A"); ListView1->Items->Item[i]->SubItems->Add("SubItem " + IntToStr(i) + "B"); i++; } //-------------------------------- |
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { TListItem *Li; Li = ListView1->Items->Add(); Li->Caption = "Items"; Li->SubItems->Add("SubItem 1"); Li->SubItems->Add("SubItem 2"); Li = ListView1->Items->Add(); delete Li; } //-------------------------------- |
Jak napisałem wyżej elementy SubItems można przypisywać do elementów Items,
później:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { ListView1->Items->Item[2]->SubItems->Add("SubItem 2A"); ListView1->Items->Item[2]->SubItems->Add("SubItem 2B"); } //-------------------------------- |
Spowoduje to dodanie nowych elementów SubItems do trzeciego w kolejności
elementu Item. Możliwe jest również zmienianie nazwy elementów Item i SubItem. W
przypadku Item sprawa jest oczywista ponieważ wystarczy posłużyć się
właściwością Caption, natomiast w przypadku SubItems, trzeba skorzystać z
właściwości Strings przypisując jej numer elementu SubItems:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { ListView1->Items->Item[2]->Caption = "Zmiana Items"; ListView1->Items->Item[2]->SubItems->Strings[0] = "Zmiana 1 SubItem"); ListView1->Items->Item[2]->SubItems->Strings[1] = "Zmiana 2 SubItem"); } //-------------------------------- |
Elementy Items i SubItems można również tworzyć wewnątrz pętli:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { for(int i = 0; i < 10; i++) { ListView1->Items->Add(); ListView1->Items->Item[i]->Caption = "Item " + IntToStr(i); for(int j = 0; j < 5; j++) { ListView1->Items->Item[i]->SubItems->Add("SubItem " + IntToStr(i) + "-" + IntToStr(j)); } } } //-------------------------------- |
Można również przypisywać ikony do elementów Items i SubItems:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { static int i = 0; ListView1->Items->Add(); ListView1->Items->Item[i]->Caption = "Item " + IntToStr(i); ListView1->Items->Item[i]->ImageIndex = i; ListView1->Index->Item[i]->StateIndex = i; ListView1->Items->Item[i]->SubItems->Add("SubItem " + IntToStr(i) + "A"); ListView1->Items->Item[i]->SubItemImages[0] = i; ListView1->Items->Item[i]->SubItems->Add("SubItem " + IntToStr(i) + "B"); ListView1->Items->Item[i]->SubItemImages[1] = i + 1; i++; } //-------------------------------- |
Zapisywanie i odczytywanie zawartości obiektu ListView do/z pliku.
ListView posiada bardzo
ubogą funkcję zapisywania i odczytywania własnej zawartości:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { ListView1->Items->Item[0]->SubItems->SaveToFile("Nazwa pliku.roz"); } //-------------------------------- void __fastcall TForm1::Button2Click(TObject *Sender) { ListView1->Items->Item[0]->SubItems->LoadFromFile("Nazwa pliku.roz"); } //-------------------------------- |
I to jest w zasadzie wszystko, jak widać można w ten sposób zapisać tylko
zawartość wybranego elementu Item i to zapiszą się tylko elementy SubItem, sam
Item nie zostanie zapisany, dlatego jeżeli chcemy zapisać wszystko co znajduje
się w obiekcie ListView lepiej jest posłużyć się klasą TFileStream i funkcjami
WriteComponent i ReadComponent:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { TFileStream *File = new TFileStream("nazwa pliku.roz", fmCreate); File->WriteComponent(ListView1); delete File; } //-------------------------------- void __fastcall TForm1::Button2Click(TObject *Sender) { TFileStream *File = new TFileStream("nazwa pliku.roz", fmOpenRead | fmShareExclusive); File->ReadComponent(ListView1); delete File; } //-------------------------------- |
Taki sposób zapisania komponentu z zawartością jest szybki i bardzo wygodny, zapisana zostanie nie tylko zawartość obiektu ListView, ale również zawartość obiektów ImageList przypisanych do ListView. Występuje jednak pewna wada, jeżeli ustawimy szerokość kolumn na auto (Columns->AutoSize = true), to ten parametr nie zostanie zapisany do pliku, dlatego przed zapisaniem lepiej jest ustawić AutoSize na false.
Edytowanie właściwości Item bezpośrednio w obiekcie ListView.
Edytowanie właściwości
Item, czyli zmiana wartości może być dokonana za pomocą kodu poprzez zmianę
właściwości Caption, o czym było w poradzie
tworzenie listy raportu w trakcie działania programu. Ta porada dotyczy
jednak innego sposobu, takiego w którym klikając np. dwukrotnie na wybranej
właściwości Item poddajemy ją do edycji tak jak to ma np. miejsce w Edit1:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::ListView1DblClick(TObject *Sender) { ListView1->Items->Item[ListView1->ItemIndex]->EditCaption(); } //-------------------------------- |
|
Ten sposób pozwala na poddanie edycji tylko właściwości Item[x]->Caption, nie
istnieje natomiast żadna funkcja umożliwiająca poddanie takiej edycji
właściwości SubItem, można jednak coś takiego stworzyć używając obiektu Edit1.
Nie można jednak określić w której kolumnie wybrano właściwość Items, inaczej
niż poprzez kliknięcie na nagłówku kolumny, więc trzeba kod dostosować do tego,
że kliknięciem w nagłówek wybieramy z której kolumny ma być edytowana właściwość
SubItems, lub podajemy numer kolumny jawnie wewnątrz kodu. Obiekt Edit należy
przypisać do ListView, a następnie ustawiać jego pozycję w zależności od
wybranej kolumny:
| // Plik nagłówkowy np. Unit1.h //-------------------------------- private: int col; |
| // Plik źródłowy np. Unit1.cpp //-------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { Edit1->Parent = ListView1; } //-------------------------------- void __fastcall TForm1::ListView1ColumnClick(TObject *Sender, TListColumn *Column) { col = Column->Index; } //-------------------------------- void __fastcall TForm1::ListView1DblClick(TObject *Sender) { if(col > 0) { int left = 0; for(int i = 0; i < col; i++) { left = left + ListView1->Column[i]->Width; } Edit1->Visible = true; Edit1->Top = ListView1->Items->Item[ListView1->ItemIndex]->Top; Edit1->Left = ListView1->Items->Item[0]->Left + left; Edit1->Width = ListView1->Column[col]->Width; } else Edit1->Visible = false; } //-------------------------------- void __fastcall TForm1::ListView1Click(TObject *Sender) { Edit1->Visible = false; } //-------------------------------- |
Sortowanie zawartości obiektu ListView
Zawartość kolumn obiektu ListView można posortować klikając na te kolumny z wykorzystaniem funkcji AlphaSort() wywoływanej w zdarzeniu OnColumnClick tegoż obiektu. Właściwość SortType obiektu ListView należy ustawić na stNone.
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::ListView1ColumnClick(TObject *Sender, TListColumn *Column) { ((TListView *)Sender)->AlphaSort(); } //-------------------------------- |
To proste wywołanie funkcji pozwala posortować
tylko pierwszą kolumnę i tylko rosnąco i tylko jako tekst. W celu posortowanie
dowolnej kolumny i rosnąco lub malejąco jako tekst należy skorzystać z funkcji
CustomSort przekazując jej jako pierwszy argument wartość NULL (0), a
jako drugi argument zmienną sterującą przyjmującą raz wartość 0, drugi raz 1.
Dzięki takiemu sterowaniu zmienną jedno kliknięcie na kolumnę będzie sortowało
kolumnę rosnąco, a drugie kliknięcie zmieni kierunek sortowania i posortuje ją
malejąco. Wartość tej zmiennej jest przekazywana do zdarzenia OnCompare poprzez
argument Data tegoż zdarzenia, dlatego trzeba jeszcze stworzyć dodatkowe
zdarzenie OnCompare dla obiektu ListView. W tym zdarzeniu będzie
poddawana sortowaniu wybrana kolumna. O tym która kolumna ma być sortowana
zadecyduje zmienna Tag wchodząc w skład obiektu ListView i to właśnie
przez tą zmienną będzie przekazywany numer wybranej kolumny do zdarzenia
OnCompare:
| //-------------------------------- void __fastcall TForm1::ListView1ColumnClick(TObject *Sender, TListColumn *Column) { static bool test = false; ((TListView *)Sender)->Tag = Column->Index; ((TListView *)Sender)->CustomSort(NULL, (long)test); test = !test; } //--------------------------------------------------------------------------- void __fastcall TForm1::ListView1Compare(TObject *Sender, TListItem *Item1, TListItem *Item2, int Data, int &Compare) { if(((TListView *)Sender)->Tag == 0) { Compare = CompareText(Item1->Caption, Item2->Caption); } else { int c = ((TListView *)Sender)->Tag - 1; Compare = CompareText(Item1->SubItems->Strings[c], Item2->SubItems->Strings[c]); } if(Data == 1) Compare = -Compare; } //-------------------------------- |
To oczywiście działa, ale zawartości kolumn są sortowane jako tekst, w przypadku gdy będą się w którejś kolumnie znajdowały liczby, to również zostaną potraktowane jako tekst i tak posortowane.
W celu posortowania kolumn zgodnie z ich zawartości, czyli tekst jest sortowany jako tekst, a liczby jako liczby, można posłużyć się dwiema metodami, które różnią się od siebie tym, że pierwsza metoda zawiera cały mechanizm sortujący w zdarzeniu OnCompare, druga zaś korzysta z funkcji pomocniczej. W obydwu przypadkach trzeba jednak stworzyć funkcję porównującą liczby, taką jak CompareText porównującą tekst, funkcji tej nadam nazwę foo. Zawartość zdarzenia OnCompare również bardzo się zmienia. Do sprawdzania czy mamy do czynienia z tekstem czy z liczbą posłużyłem się prostą metodą try catch, co podczas testowania aplikacji w środowisku BCB może zaowocować zgłaszaniem ostrzeżenia przez kompilator, ale w trakcie normalnej pracy programu tego komunikatu nie będzie, ale to tak na marginesie, żebyś się nie przestraszył, że coś tutaj nie działa.
| // Plik źródłowy np. Unit1.cpp //-------------------------------- #pragma warn +csu double foo(double u, double i) { double z = u - i; return z; } //-------------------------------- void __fastcall TForm1::ListView1ColumnClick(TObject *Sender, TListColumn *Column) { static bool trend = false; ((TListView *)Sender)->Tag = Column->Index; ((TListView *)Sender)->CustomSort(NULL, (long)trend); trend = !trend; } //--------------------------------------------------------------------------- void __fastcall TForm1::ListView1Compare(TObject *Sender, TListItem *Item1, TListItem *Item2, int Data, int &Compare) { bool test = true; String cText1, cText2; if(((TListView *)Sender)->Tag == 0) { cText1 = Item1->Caption; cText2 = Item2->Caption; } else { int c = ((TListView *)Sender)->Tag - 1; cText1 = Item1->SubItems->Strings[c]; cText2 = Item2->SubItems->Strings[c]; } double rTemp1, rTemp2; try{ rTemp1 = cText1.Trim().ToDouble(); } catch(...){ test = false;} try{ rTemp2 = cText2.Trim().ToDouble(); } catch(...){ test = false;} if(!test) { Compare = CompareText(cText1.Trim(), cText2.Trim()); } else { Compare = foo(rTemp1, rTemp2); } if(Data == 1) Compare = -Compare; } //-------------------------------- |
Druga z prezentowanych metod nie korzysta ze zdarzenia OnCompare, lecz z funkcji CustomAutoSort przekazywanej jako argument funkcji CustomSort, w tym przypadku potrzebna jest również zmienna sterująca kierunkiem sortowania, ale jest to już zmienna globalna sDirection, cała reszta jest bardzo podobna:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- #pragma warn +csu double foo(double u, double i) { double z = u - i; return z; } //-------------------------------- BOOL sDirection = true; int __stdcall CustomAutoSort(long Item1, long Item2, long lParam) { bool test = true; String cTemp1, cTemp2; int c = ((TListView *)lParam)->Tag - 1; if(c == -1) { cTemp1 = ((TListItem *)Item1)->Caption; cTemp2 = ((TListItem *)Item2)->Caption; } else { cTemp1 = ((TListItem *)Item1)->SubItems->Strings[c]; cTemp2 = ((TListItem *)Item2)->SubItems->Strings[c]; } double rTemp1, rTemp2; try{ rTemp1 = cTemp1.Trim().ToDouble(); } catch(...){ test = false;} try{ rTemp2 = cTemp2.Trim().ToDouble(); } catch(...){ test = false;} if(!test) { if(!sDirection) return CompareText(cTemp1.Trim(), cTemp2.Trim()); return -CompareText(cTemp1.Trim(), cTemp2.Trim()); } if(!sDirection) return foo(rTemp1, rTemp2); return -foo(rTemp1, rTemp2); } //--------------------------------------------------------------------------- void __fastcall TForm1::ListView1ColumnClick(TObject *Sender, TListColumn *Column) { sDirection = !sDirection; ((TListView *)Sender)->Tag = Column->Index; ((TListView *)Sender)->CustomSort(CustomAutoSort, (long)((TListView *)Sender)); } //-------------------------------- |