Menu |
Zmiana koloru zaznaczenia wybranej pozycji w ListBox.
Nie będę wyjaśniał czym jest obiekt ListBox i do czego służy, ponieważ każdy powinien już to wiedzieć. Zawsze gdy wybieramy jakąś pozycję w ListBox to kolor zaznaczenia jest z góry określony poprzez właściwości ekranu. Dla tych którzy nie wiedzą właściwości ekranu ustawia się poprzez wybranie z menu kontekstowego pulpitu opcji Właściwości a następnie zakładki Wygląd i tam można znaleźć opcję Elementy wybrane dla której można ustawić między innymi kolor zaznaczenia, który będzie kolorem domyślnym dla wszelkich zaznaczeń, we wszystkich programach uruchamianych pod Windows.
Jednak dla obiektu ListBox można zdefiniować własny kolor zaznaczenia. W tym celu należy zmienić właściwość Style obiektu ListBox1 na
lbOwnerDrawVariable lub lbOwnerDrawFixed, a następnie trzeba dodać do zdarzenia OnDrawItem tegoż obiektu następujący kod:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::ListBox1DrawItem(TWinControl *Control, int Index, TRect &Rect, TOwnerDrawState State) { TListBox *pListbox = dynamic_cast<TListBox*>(Control); TCanvas *pCanvas = pListbox->Canvas; if(State.Contains(odSelected)) { pCanvas->Brush->Color = clYellow; pCanvas->Font->Color = clHighlightText; } pCanvas->FillRect(Rect); pCanvas->TextRect(Rect, Rect.Left,Rect.Top, pListbox->Items->Strings[Index]); } //-------------------------------- |
W przykładzie ustawiłem żółty kolor zaznaczenia.
Wykorzystując tą metodę można zrobić znacznie więcej, a mianowicie zmienić kolor co drugiego wiersza na liście:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::ListBox1DrawItem(TWinControl *Control, int Index, TRect &Rect, TOwnerDrawState State) { TListBox *pListbox = dynamic_cast<TListBox*>(Control); TCanvas *pCanvas = pListbox->Canvas; if(Index%2) { pCanvas->Brush->Color = clBlack; pCanvas->Font->Color = clWhite; } pCanvas->FillRect(Rect); pCanvas->TextRect(Rect, Rect.Left,Rect.Top, pListbox->Items->Strings[Index]); } //-------------------------------- |
Proszę zwrócić uwagę w jaki sposób zostały zdefiniowane co drugie elementy listy - kod na żółtym tle.
W ten sposób uzyskaliśmy bardzo ciekawy efekt:
element 1 |
element 2 |
element 3 |
element 4 |
element 5 |
element 6 |
element 7 |
element 8 |
Można również połączyć obydwie metody:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::ListBox1DrawItem(TWinControl *Control, int Index, TRect &Rect, TOwnerDrawState State) { TListBox *pListbox = dynamic_cast<TListBox*>(Control); TCanvas *pCanvas = pListbox->Canvas; if(State.Contains(odSelected)) { pCanvas->Brush->Color = clYellow; pCanvas->Font->Color = clHighlightText; } else if(Index%2) { pCanvas->Brush->Color = clBlack; pCanvas->Font->Color = clWhite; } pCanvas->FillRect(Rect); pCanvas->TextRect(Rect, Rect.Left,Rect.Top, pListbox->Items->Strings[Index]); } //-------------------------------- |
Wyszukiwanie elementu listy poprzez podanie jego nazwy.
W celu odnalezienia wewnątrz listy obiektu ListBox określonej wartości należy posłużyć się funkcją Perform należącą do tegoż obiektu. Odnaleziony element, jeżeli istnieje zostanie zaznaczony na liście:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { ListBox1->Perform(LB_SELECTSTRING, -1,(LPARAM)"treść poszukiwanego elementu"); } //-------------------------------- |
...i to w zasadzie już prawie wszystko, pokażę jeszcze tylko jak znaleźć element gdy poszukiwana wartość wpisana jest do obiektu Edit1:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { ListBox1->Perform(LB_SELECTSTRING, -1,(LPARAM)Edit1->Text.c_str()); } //-------------------------------- |
i jeszcze inny sposób rozwiązania tego samego problemu:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { int index = ListBox1->Perform(LB_FINDSTRING, -1,(LPARAM)Edit1->Text.c_str()); if(index != -1) ListBox1->ItemIndex = index; |
Tworzenie listy zawierającej nazwy i wartości przypisane do
nazw.
dotyczy: TListBox, TComboBox,
TStringList, THashedStringList itp.
tworzenie
aplikacji w wielu wersjach językowych.
O tym jak się tworzy listy w takich obiektach jak ListBox, ComboBox, TStringList, THashedStringList i wszystkich podobnych korzystających z wirtualnej klasy TStrings wiedzą wszyscy, wystarczy wstawiać do takiej listy wartości poprzez Add, a odwoływać się do nich poprzez wartość String[int index], np:
ListBox1->Items->Add("tekst przykładowy");
ComboBox1->Items->Add("tekst przykładowy");
TStringList *Lista = new TStringsList;
Lista->Add("tekst przykładowy");
THashedStringList *HLista = new THashedStringList;
HLista->Add("tekst przykładowy");
Wspomniałem tutaj o klasie
THashedStringList, nie wszyscy pewnie wiedzą co to jest, a jest to klasa podobna
do TStringList i używa jej się tak samo. Klasa ta jest zdefiniowana w bibliotece
inifiles.hpp i żeby jej używać należy włączyć tą bibliotekę do sekcji include
pliku nagłówkowego. Znajduje się ona wewnątrz klasy TMemIniFile i jest znacznie
szybsza w użyciu od innych tablic dynamicznych.
Przedstawiony tutaj sposób można zastosować do wszystkich
obiektów wykorzystujących klasę TStrings, czyli np. właściwość Items obiektu
ListBox jest typu TStrings, ale bezpośrednio jest to właściwość typu TStringList,
czyli hierarcha dla obiektu ListBox wygląda następująco:
TObject ® TPersistent ® TComponent ® TControl ® TWidgetControl ® TCustomListBox ® TListBox
i dalej dla właściwości Items i Add:
TListBox ® TStrings ® TStringList ® TStrings
W przypadku obiektu TStringList nie występuje właściwość Items ponieważ jak widać jest to właściwość typu TStringList, czyli jest dokładnie tym obiektem, ale obiekt ten posiada już właściwość Add, która jest typu TStrings i wywodzi się z właściwości TStringList, czyli dla TStringList hierarchia wygląda następująco:
TObject ® TPersistent ® TStrings ® TStringList
i dla właściwości Add:
TStringList ® TStrings
To tyle gwoli wyjaśnienia, mam nadzieję, że to rozumiesz bo nie wiem jak inaczej mógłbym to wyjaśnić. Przechodzimy teraz do istoty tej porady, na początek wyjaśnię o co w tym chodzi, a mianowicie lista zawierająca nazwy i przypisane do nich wartości będzie miała następującą postać:
Cyfrowy Baron=http://cyfbar.republika.pl |
Jak widać lista składa się
z dwóch kolumn w pierwszej kolumnie mamy nazwy, a w drugiej są wartości
przypisane do nazw. Nazwy od wartości zawsze są oddzielone znakiem równości (=) i
nie można tutaj zastosować innego separatora. Jeśli jesteś bystry to na pewno
zauważyłeś, że konstrukcją taka lista przypomina plik ini, brakuje tutaj tylko
kluczy, dostęp do właściwości wybranego elementu uzyskuje się poprzez podanie
jego nazwy, można w ten sposób zaindeksować np. same nazwy lub same wartości na
podstawie nazw. Posłużmy się przykładem, można utworzyć plik z taką listą i
wczytać go do obiektu za pomocą funkcji LoadFromFile, ale można również dodać je
do listy za pomocą funkcji Add, żeby to lepiej zobrazować proponuję stworzyć
obiekt typu THashedStringList (można też TStringList) i umieścić w nim listę,
następnie umieszczamy na formularzu 3 obiekty typu TListBox w każdym z tych
obiektów umieścimy inny rodzaj wartości po naciśnięciu przycisku Button1:
// Plik nagłówkowy np. Unit1.h #include <inifiles.hpp> //-------------------------------- private: THashedStringList *Lista; //-------------------------------- |
// Plik źródłowy np. Unit1.cpp //-------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { Lista = new THashedStringList; Lista->Add("Cyfrowy Baron=http://cyfbar.republika.pl"); Lista->Add("e-mail=cyfrowy_baron@orange.pl"); Lista->Add("wiek=nieznany"); Lista->Add("wykształcenie=wiedza tajemna"); Lista->Add("zamieszkały miasto=Płońsk"); Lista->Add("adres=top secret"); Lista->Add("telefon kom.:=506 115 671"); } //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { ListBox1->Items = Lista; // przepisuje zawartość obiektu Lista do obiektu ListBox1 for(int i = 0; i < Lista->Count; i++) { ListBox2->Items->Add(Lista->Names[i]); // pobiera nazwę elementu listy ListBox3->Items->Add(Lista->Values[Lista->Names[i]]); // pobiera wartość na podstawie nazwy } } //-------------------------------- |
Myślę, że nie trzeba tutaj nic więcej opisywać ponieważ wynik działania
programu mówi sam za siebie. Należy zwrócić uwagę na sposób w jaki pobierana
jest wartość na podstawie nazwy, wartość jest określana przez właściwość
Values, a nazwa przez właściwość Names (nie mylić z właściwością Name).
Właściwość Values jest właściwością typu TStrings, ale w odróżnieniu od
takich właściwości jak Strings[numer], Names[numer]
ta właściwość nie odwołuje się do tablicy za pomocą numeru jej elementu lecz
poprze nazwę, czyli wartość typu AnsiString - Values[nazwa].
W przedstawionym wyżej przykładzie nazwy i wartości zostały w
całości zaindeksowane w obiektach ListBox, ale można te wartości wyszukiwać i
przepisywać do dowolnego innego obiektu pobierającego wartość AnsiString:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button2Click(TObject *Sender) { Edit1->Text = Lista->Values["telefon kom.:"]; Edit2->Text = "Adres internetowy Cyfrowego Barona " + Lista->Values["Cyfrowy Baron"]; } //-------------------------------- |
Obowiązują pewne zasady, otóż żeby pobrać wartość na podstawie nazwy należy
podać pełną nazwę. Jeżeli listę odczytujemy z pliku modyfikujemy i ponownie
zapisujemy do pliku, to inaczej niż ma to miejsce w przypadku klasy TIniFiles
należy przetwarzać całą listę.
Ktoś może zapytać: - No dobrze, ale po cholerę mi to?, no cóż
to może okazać się bardzo przydatne przy tworzeniu programu w kilku wersjach
językowych, ponieważ jako nazwę elementu listy można podawać nazwę obiektu,
któremu chcemy zmienić właściwość Caption, np. taki prosty kod:
// Plik źródłowy np. Unit1.cpp //-------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { Lista = new THashedStringList; Lista->Add("Button1=Zapisz"); Lista->Add("Button2=Otwórz"); Lista->Add("Button3=Zamknij"); Lista->Add("Button4=OK"); Lista->Add("zamieszkały miasto=Płońsk"); Lista->Add("adres=top secret"); Lista->Add("telefon kom.:=506 115 671"); } //-------------------------------- void __fastcall TForm1::FormShow(TObject *Sender) { for(int i = 1; i <= 4; i++) { String name = "Button" + IntToStr(i); dynamic_cast<TButton *>(FindComponent(name))->Caption = Lista->Values[name]; } } //-------------------------------- |
W ten oto sposób w prostej pętli zostały zmienione właściwości Caption
czterech obiektów Button, na podstawie zawartości listy. Kluczem do taśmowej
zmiany wszystkich właściwości jest tutaj nazwa obiektu, która dla wszystkich
przycisków jest podobna, czyli zawiera człon główny Button i numer tego
przycisku. Jeżeli byśmy chcieli zmienić właściwość Caption taśmowo dla
wszystkich obiektów umieszczonych na formularzu i posiadających taką właściwość,
należy im przypisać takie same nazwy, ale z różnymi numerami, np. Obiekt + numer.
W takiej sytuacji zamiast operatora dynamic_cast należy zastosować operator
reinterpret_cast, a rzutowanie powinno się odbywać na dowolną klasę posiadającą
właściwość Caption, czyli w tym przypadku najlepiej na TForm1:
// Plik źródłowy np. Unit1.cpp //-------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { Lista = new THashedStringList; Lista->Add("Obiekt5=Zapisz"); // dla obiektu Button5 Lista->Add("Obiekt2=Otwórz"); // dla obiektu Button2 Lista->Add("Obiekt3=Zamknij");// dla obiektu Button3 Lista->Add("Obiekt4=OK"); // dla obiektu Button4 Lista->Add("Obiekt1=Nazwa mojego programu"); // dla właściwości Caption formularza np. Form1 Lista->Add("Obiekt6=nazwa etykiety"); // dla właściwości Caption obiektu Label6 Lista->Add("Obiekt7=wartość Text obiektu Edit1"); // dla właściwości Caption formularza np. Edit7 } //-------------------------------- void __fastcall TForm1::FormShow(TObject *Sender) { for(int i = 1; i <= 7; i++) { String name = "Obiekt" + IntToStr(i); reinterpret_cast<TForm1 *>(FindComponent(name))->Caption = Lista->Values[name]; } } //-------------------------------- |
Opis operatorów reinterpret_cast i dynamic_cast w dziale: rzutowanie obiektów, przypisanie polimorficzne. No i to na tyle, mamy szybką i prostą metodę na zmianę wersji językowej programu wystarczy utworzyć tylko pliki w kilku wersjach i odpowiednio je wczytywać. Konfigurację programu też można w ten sposób zapisywać, ale w tym pliki *.ini lepiej się sprawdzają.
Zmiana kolejności elementów na liście.
dotyczy:
TListBox, TComboBox, TStringList, THashedStringList itp.
Poniższa porada nie jest skomplikowana, ale jest bardzo przydatna, może mieć ona
zastosowanie we wszystkich typach list korzystających z wirtualnej klasy
TStrings, a więc w takich typach obiektów jak: TListBox, TComboBox, TStringList,
THashedStringList itp. Na jednym przykładzie pokaże jak odwoływać do wszystkich
wymienionych typów, a w dalszej części będę posługiwał się tylko obiektem
ListBox, lecz zasada stosowania dla pozostałych obiektów się nie zmieni i będzie
przebiegała w taki sam sposób jak dla ListBox.
Najprostszym sposobem zmiany kolejności elementów na liście jest wykorzystanie
funkcji Exchange, którą to funkcję posiadają wszystkie z wymienionych
wyżej obiektów. Exchange zmienia kolejność dwóch dowolnych elementów listy, jako
argumenty przekazuje się funkcji numery elementów listy, które chcemy zamienić,
czyli możemy np. zamienić miejscami element o numerze 2 z elementem o numerze 5
(2
Û
5):
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1ClickTObject *Sender) { ListBox1->Items->Exchange(2, 5); ComboBox1->Items->Exchange(2, 5); TStringList *Lista = new TStringList; Lista->Exchange(2, 5); THashedStringList *HLista = new THashedStringList; HLista->Exchange(2, 5); delete Lista, HLista; } //-------------------------------- |
Należy pamiętać, że używając klasy THashedStringList, trzeba włączyć do pliku nagłówkowego bibliotekę
#include <inifiles.hpp>.// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1ClickTObject *Sender) { ListBox1->Items->Move(5, 2); TStringList *Lista = new TStringList; Lista->Move(5, 2); delete Lista; } //-------------------------------- void __fastcall TForm1::Button2ClickTObject *Sender) { ListBox1->Items->Move(ListBox1->ItemIndex, 2); // przesunięcie zaznaczonego elementu. } //-------------------------------- void __fastcall TForm1::Button3ClickTObject *Sender) { if(ListBox1->ItemsIndex > 0) ListBox1->Items->Move(ListBox1->ItemIndex, ListBox1->ItemIndex - 1); // przesunięcie zaznaczonego elementu o jeden w górę. } //-------------------------------- void __fastcall TForm1::Button4ClickTObject *Sender) { if(ListBox1->ItemsIndex < ListBox1->Items->Count - 2) ListBox1->Items->Move(ListBox1->ItemIndex, ListBox1->ItemIndex + 1); // przesunięcie zaznaczonego elementu o jeden w dół. } //-------------------------------- |
Kolejnym sposobem na przesunięcie elementów listy jest użycie funkcji
MoveSelection, funkcja ta ma zastosowanie tylko w komponentach, ponieważ
jest związana z zaznaczeniem elementów listy, czyli można ją stosować w
obiektach typu TListBox i TComboBox, ale już w
obiektach typu TStringList i THashedStringList nie
da się jej zastosować. Funkcja MoveSelection służy do przenoszenia
zaznaczonych elementów z jednej listy do drugiej, czyli np. z ListBox1 do
ListBox2, przy czym nie jest to kopiowanie, elementy przenoszone z ListBox1
zostaną usunięte z tej listy i pojawią się w ListBox2. Standardowo na liście
można zaznaczyć tylko jeden element, ale wystarczy ustawić właściwość
MultiSelect obiektu ListBox1 na true i już staje się
możliwe zaznaczanie wielu elementów listy, obiekt ComboBox nie posiada
właściwości MultiSelect:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1ClickTObject *Sender) { ListBox1->MoveSelection(ListBox2); // przesunięcie elementów z ListBox1 do ListBox2. } //-------------------------------- |
Skoro wspomniałem o funkcji MoveSelection, powinienem wspomnieć o funkcji
CopySelection, jak wskazuje nazwa funkcja ta kopiuje zaznaczone elementy z
jednej listy do drugiej bez ich usuwania z listy źródłowej:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1ClickTObject *Sender) { ListBox1->CopySelection(ListBox2); // skopiowanie elementów z ListBox1 do ListBox2. } //-------------------------------- |
Jedna uwaga na zakończenie, elementy listy są numerowane od 0, a właściwość Count od 1.
Usuwanie elementów listy.
dotyczy: TListBox, TComboBox, TStringList, THashedStringList itp.
Do usuwanie
elementów listy służą w zasadzie dwie funkcje, pierwsza to Delete kasująca
wybrany element listy, funkcja pobiera jeden argument będący numerem elementu,
który ma być usunięty, można ją stosować we wszystkich obiektach korzystających
z wirtualnej klasy TStrings:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1ClickTObject *Sender) { ListBox1->Items->Delete(2); ComboBox1->Items->Delete(5); TStringList *Lista = new TStringList; Lista->Delete(3); THashedStringList *HLista = new THashedStringList; HLista->Delete(1); delete Lista, HLista; } //-------------------------------- |
Druga funkcja to DeleteSelected usuwająca wszystkie zaznaczone
elementy na liście, ponieważ ta funkcja ma związek z zaznaczaniem jest stosowana
tylko w komponentach takich jak ListBox i ComboBox, nie ma natomiast
zastosowania w TStringList i THashedStringList, żeby zaznaczyć wiele elementów w
obiekcie ListBox należy ustawić jego właściwość MultiSelect na
true, obiekt ComboBox nie posiada tej właściwości. Funkcja nie
pobiera żadnych argumentów:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1ClickTObject *Sender) { ListBox1->DeleteSelected(); ComboBox1->DeleteSelected(); } //-------------------------------- |
Jedna uwaga na zakończenie, elementy listy są numerowane od 0, a właściwość Count od 1.
Łączenie elementów listy w jeden ciąg znaków.
dotyczy: TListBox, TComboBox,
TStringList, THashedStringList itp.
Mam tu na myśli dokładnie to co wynika z tytułu tej porady, czyli mając listę składającą się z wielu elementów można ją połączyć w jeden ciąg (łańcuch) znaków, a wynik umieścić w zmiennej lub właściwości obiektu typu AnsiString. Domyślnie łączone elementy listy będą oddzielane od siebie przecinkami. Jeśli mamy np. taką listę:
Ciotka Kryśka |
Po połączenie elementów listy otrzymamy taki wynnik:
"Ciotka Kryśka","Wujek Stefan",Obcy,"Ktoś,Coś" |
Wspomniałem, że elementy
listy oddzielane są przecinkami, ale jeżeli element listy zawiera spację lub
przecinek, wtedy jest on automatycznie umieszczany w cudzysłowie:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1ClickTObject *Sender) { Edit1->Text = ListBox1->Items->CommaText; Edit2->Text = ComboBox1->Items->CommaText; TStringList *Lista = new TStringList; Edit3->Text = Lista->CommaText; THashedStringList *HLista = new THashedStringList; Edit4->Text = HLista->CommaText; delete Lista, HLista; } //-------------------------------- |
Istnieje możliwość zmiany przecinka na inny separator np. na @, w takiej
sytuacji przecinek traci status separatora i jeśli element listy zawiera
przecinek to nie jest brany w cudzysłowie, jednak jeżeli zawiera nowy separator
wtedy zostanie umieszczony w cudzysłowie, jednym słowem nowy separator przejmuje
funkcję przecinka, który jest domyślnym cudzysłowem:
// Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1ClickTObject *Sender) { ListBox1->Items->Delimiter = '@'; Edit1->Text = ListBox1->Items->CommaText; ComboBox1->Items->Delimiter = '#'; Edit2->Text = ComboBox1->Items->CommaText; TStringList *Lista = new TStringList; Lista->Delimiter = '$'; Edit3->Text = Lista->CommaText; THashedStringList *HLista = new THashedStringList; HLista->Delimiter = 'A'; Edit4->Text = HLista->CommaText; delete Lista, HLista; } //-------------------------------- |
Jako separator można zdefiniować tylko jeden znak, kompilator zaakceptuje więcej niż jeden znak, ale jako separator ustawi ten, który będzie występował jako ostatni, czyli jeśli ustawimy taki separator '@#' to separatorem będzie tylko znak #. Zwracam uwagę na to, że przypisując do funkcji Delimiter separator nie umieszczamy go w cudzysłowie, ponieważ ta funkcja pobiera zmienną typu char, a nie AnsiString.
Sortowanie listy według różnych
kryteriów (liczby lub tekstu).
dotyczy: TListBox, TComboBox,
TStringList, THashedStringList itp.
Obiekty takie jak ListBox, ComboBox czy podobne posiadają domyślnie sortowanie
listy przyrostowo według tekstu. Oznacza to, że niezależnie od tego czy lista
zawiera tylko tekst czy tylko liczby zawsze będzie sortowana według alfabetu, a
więc liczba jest traktowana jako tekst. W tej poradzie chcę jak posortować listę
według liczby lub według alfabetu, rosnąco lub malejąco. Dodatkowo pokaże sposób
na sortowanie listy która zwiera zarówno liczby jak i tekst i jak posortować
taką listę według liczby lub według tekstu.
Poniżej przykład takich list:
Lista zawierająca tylko liczby:
13
1 94 |
Lista zawierająca tylko tekst:
Dariusz Agnieszka Cecylia Stefan Małgorzata |
Lista zawierająca liczby jak i tekst:
13 Dariusz
1 Jan Nowak 94 Stefan 567 Małgorzata |
Obiekt
ListBox nie zawiera standardowo żadnej funkcji umożliwiającej takie sortowanie,
dlatego wykorzystam do tego celu klasę TStringList, która posiada funkcję
CustomSort. Ta funkcja jest daleka od doskonałości i nie traktuje tekstu
zawierającego liczby jako liczby, lecz jako tekst, ale można ją udoskonalić i
zmusić do tego by działała jak tego oczekujemy. Działanie funkcji CustomSort
polega na porównywaniu dwóch łańcuchów znaków i przesuwaniu ich na liście w górę
lub w dół, w zależności od wyniku porównania. Listę można by porównywać poprzez
pętlę for lub while, ale przy bardzo długiej liście byłoby to nieekonomiczne i
niepraktyczne, dlatego wykorzystamy do tego celu funkcję działającą w sposób
dynamiczny CALLBACK typu TStringListSortCompare. Na początek pokażę sposób
sortowania listy zawierającej tylko liczby:
// Plik źródłowy np. Unit1.cpp //-------------------------------- #pragma warn +csu int foo(int u, int i) { int z = u - i; return z; } //-------------------------------- int __fastcall CustomSortInt(TStringList *Lista, int idx1, int idx2) { String cTemp1 = Lista->Strings[idx1]; String cTemp2 = Lista->Strings[idx2]; int rTemp1 = cTemp1.Trim().ToIntDef(0); int rTemp2 = cTemp2.Trim().ToIntDef(0); return foo(rTemp1, rTemp2); } //-------------------------------- void __fastcall TForm1::Button1ClickTObject *Sender) { ListBox1->Sorted = false; TStringList *sortList = new TStringList; sortList->Assign(ListBox1->Items); sortList->CustomSort(CustomSortInt); ListBox1->Items->Assign(sortList); delete sortList; } //-------------------------------- |
Jak to widać na tym prostym przykładzie, musiałem posłużyć się dodatkową
funkcją sprawdzającą która z dwóch podanych liczb jest większa, jest to funkcja
wspomagająca działanie funkcji CustomSortInt. Przedstawiona funkcja
sortuje listę rosnąco, gdybyśmy chcieli, żeby lista była sortowana malejącą
wystarczy postawić znak minus przed zwracaną funkcją foo:
// Plik źródłowy np. Unit1.cpp //-------------------------------- #pragma warn +csu int foo(int u, int i) { int z = u - i; return z; } //-------------------------------- String sortPrefix; int __fastcall CustomSortDbInt(TStringList *Lista, int idx1, int idx2) { String cTemp1 = Lista->Strings[idx1]; String cTemp2 = Lista->Strings[idx2]; int x1 = cTemp1.Pos(sortPrefix); int x2 = cTemp2.Pos(sortPrefix); int rTemp1 = cTemp1.SubString(1, x1 - 1).Trim().ToIntDef(0); int rTemp2 = cTemp2.SubString(1, x2 - 1).Trim().ToIntDef(0); return foo(rTemp1, rTemp2); } //-------------------------------- void __fastcall TForm1::Button1ClickTObject *Sender) { sortPrefix = " "; ListBox1->Sorted = false; TStringList *sortList = new TStringList; sortList->Assign(ListBox1->Items); sortList->CustomSort(CustomSortDbInt); ListBox1->Items->Assign(sortList); delete sortList; } //-------------------------------- |
// Plik źródłowy np. Unit1.cpp //-------------------------------- String sortPrefix; int __fastcall CustomSortDbString(TStringList *Lista, int idx1, int idx2) { String cTemp1 = Lista->Strings[idx1]; String cTemp2 = Lista->Strings[idx2]; int x1 = cTemp1.Pos(sortPrefix); int x2 = cTemp2.Pos(sortPrefix); String rTemp1 = cTemp1.SubString(x1 + 1, cTemp1.Length()).Trim(); String rTemp2 = cTemp2.SubString(x2 + 1, cTemp2.Length()).Trim(); return CompareText(rTemp1, rTemp2); } //-------------------------------- void __fastcall TForm1::Button1ClickTObject *Sender) { sortPrefix = " "; ListBox1->Sorted = false; TStringList *sortList = new TStringList; sortList->Assign(ListBox1->Items); sortList->CustomSort(CustomSortDbString); ListBox1->Items->Assign(sortList); delete sortList; } //-------------------------------- |
Na zakończenie stworzę funkcję realizującą zadania sortowania według czterech
różnych kluczy, stworzę tutaj dodatkowo własny typ wyliczeniowy, który będzie
decydował o rodzaju sortowania:
// Plik źródłowy np. Unit1.cpp //-------------------------------- enum TTypeSort {tsInt, tsString, tsDbInt, tsDbString}; // typ wyliczeniowy #pragma warn +csu int foo(int u, int i) { int z = u - i; return z; } //-------------------------------- int __fastcall CustomSortInt(TStringList *Lista, int idx1, int idx2) { String cTemp1 = Lista->Strings[idx1]; String cTemp2 = Lista->Strings[idx2]; int rTemp1 = cTemp1.Trim().ToIntDef(0); int rTemp2 = cTemp2.Trim().ToIntDef(0); return foo(rTemp1, rTemp2); } //-------------------------------- int __fastcall CustomSortString(TStringList *Lista, int idx1, int idx2) { String cTemp1 = Lista->Strings[idx1]; String cTemp2 = Lista->Strings[idx2]; String rTemp1 = cTemp1.Trim(); String rTemp2 = cTemp2.Trim(); return CompareText(rTemp1, rTemp2); } //-------------------------------- String sortPrefix; int __fastcall CustomSortDbInt(TStringList *Lista, int idx1, int idx2) { String cTemp1 = Lista->Strings[idx1]; String cTemp2 = Lista->Strings[idx2]; int x1 = cTemp1.Pos(sortPrefix); int x2 = cTemp2.Pos(sortPrefix); int rTemp1 = cTemp1.SubString(1, x1 - 1).Trim().ToIntDef(0); int rTemp2 = cTemp2.SubString(1, x2 - 1).Trim().ToIntDef(0); return foo(rTemp1, rTemp2); } //-------------------------------- int __fastcall CustomSortDbString(TStringList *Lista, int idx1, int idx2) { String cTemp1 = Lista->Strings[idx1]; String cTemp2 = Lista->Strings[idx2]; int x1 = cTemp1.Pos(sortPrefix); int x2 = cTemp2.Pos(sortPrefix); String rTemp1 = cTemp1.SubString(x1 + 1, cTemp1.Length()).Trim(); String rTemp2 = cTemp2.SubString(x2 + 1, cTemp2.Length()).Trim(); return CompareText(rTemp1, rTemp2); } //-------------------------------- void __fastcall SortList(TListBox *ListBox, TTypeSort ts, String prefix) { if(prefix.Length() > 1) { Application->MessageBox("Za długi prefiks! Podaj pojedynczy znak.", "Błąd prefiksu", MB_OK | MB_ICONSTOP); return; } ListBox->Sorted = false; TStringList *sortList = new TStringList; sortList->Assign(ListBox->Items); sortPrefix = prefix; switch(ts) { case tsInt: sortList->CustomSort(CustomSortInt); break; case tsString: sortList->CustomSort(CustomSortString); break; case tsDbInt: sortList->CustomSort(CustomSortDbInt); break; case tsDbString: sortList->CustomSort(CustomSortDbString); break; } ListBox->Items->Assign(sortList); delete sortList; } //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { SortList(ListBox1, tsInt, ""); } //-------------------------------- void __fastcall TForm1::Button2Click(TObject *Sender) { SortList(ListBox1, tsString, ""); } //-------------------------------- void __fastcall TForm1::Button3Click(TObject *Sender) { SortList(ListBox1, tsDbInt, " "); // jako prefiks można określić dowolny znak } //-------------------------------- void __fastcall TForm1::Button4Click(TObject *Sender) { SortList(ListBox1, tsDbString, " "); // jako prefiks można określić dowolny znak } //-------------------------------- |
Jeszcze kilka drobnych uwag na zakończenie. Funkcje CustomSortInt, CustomSortDbInt, CustomSortString i CustomSortDbString mogą mieć dowolne nazwy, ale muszą być typu int __fastcall i mogą pobierać tylko trzy argumenty dokładnie takie jak widać w przykładach, czyli pierwszy argument jest typu TStringList a pozostałe dwa typu int i nie można tego zmieniać, funkcje muszą również zwracać wartość typu int. Dzieje się tak dlatego, że są to funkcje CALLBACK wywodzące się z predefiniowanego typu TStringListSortCompare, który ma taką konstrukcję:
typedef int __fastcall (CALLBACK *TStringListSortCompare)(TStringList* List, int Index1, int Index2);
Funkcję SortList sam wymyśliłem i może mieć ona dowolną konstrukcję.
Co się tyczy prefiksu to może on być dowolny, czyli może zawierać jeden dowolny
znak typu AnsiString, zresztą funkcja SortList został skonstruowana w
taki sposób, że przerwie swoje działanie, jeśli prefiks będzie zawierał więcej
niż jeden znak.
Przedstawione przykłady nie wyczerpują wszystkich możliwości i można tworzyć
własne typy sortowania według zamieszczonych przykładów