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]);
}
//--------------------------------

...powrót do menu. 

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;
}
//--------------------------------

...powrót do menu. 

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
e-mail=cyfrowy_baron@orange.pl
wiek=nieznany
wykształcenie=wiedza tajemna
zamieszkały miasto=Płońsk
adres=top secret
telefon kom.:=506 115 671

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ą.

...powrót do menu. 

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>.
Innym sposobem na zmianę kolejności elementów listy, jest wykorzystanie funkcji Move, ta funkcja działa podobnie do funkcji Exchange, również pobiera dwa argumenty, pierwszy to numer elementu listy, który chcemy przesunąć, a drugi to pozycja na którą ten element zostanie przesunięty, jednak w odróżnieniu od Exchange funkcja Move zmienia automatycznie pozycję wszystkich elementów, które znajdują się poniżej pozycji na którą zostaje przesunięty element, czyli jeśli przesuwamy element z pozycji 5 na pozycję pozycję 2 to wszystkie elementy poniżej pozycji 2 przesuną się o 1 w dół. Przesuwać można elementy zarówno z dołu do góry jak i z góry do dołu, w tym drugim przypadku automatycznemu przesunięciu ulegną elementy znajdujące się powyżej pozycji na, którą został przesunięty wybrany element, a oto dwa sposoby na przesunięcie elementów w ListBox, przypominam, że dla wszystkich pozostałych - tego typu obiektów zasada jest ta sama:

// 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.

...powrót do menu. 

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.

...powrót do menu. 

Łą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
Wujek Stefan
Obcy
Ktoś,Coś

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.

...powrót do menu. 

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
17

1
6
3

94

 

Lista zawierająca tylko tekst:

Dariusz
Jan Kowalski
Jan Nowak

Agnieszka

Cecylia

Stefan

Małgorzata

 

Lista zawierająca liczby jak i tekst:

13 Dariusz
17 Jan Kowalski

1 Jan Nowak
6 Agnieszka
3 Cecylia

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:

return -foo(rTemp1, rTemp2);

Teraz pokażę jak sortować listę zawierającą zarówno liczby jak i tekst rosnąco według liczby, a w następnej kolejności sortowanie listy zawierającej zarówno liczby jak i tekst rosnąco, ale według tekstu. W obydwu przypadkach dochodzi jeszcze jeden element, otóż trzeba podać prefiks oddzielający liczbę od tekstu:

// 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

...powrót do menu.