Home

String-Based Lists

 

Lists

 

Introduction

A list is a series of items of the same category and considered as an entity. By default, a list is made of a single type of items easily identified, but an advanced list is made of objects that are of the same type although each object can be made of sub-objects.The first concern of a list is the composition of its members.

 

Types of Lists

The members of a list could be simple text fields, such is the case of the Animation list of the Microsoft Word Font dialog box:

A list could also be structured as a tree made of words or groups of words. An example is the list of fonts in WordPerfect 2002:

Another list could be made of graphics or pictures only. Examples are the Solitaire and the FreeCell games:

A list can also combine graphics and text. The Area dialog box of StarOffice features such a list:

A list does not have to be made of items uniformly aligned. For example, the list of colors of Microsoft Excel’s Format Cells dialog box is made of square boxes representing colors:

A list does not necessarily display its items all at once. For example, the items of a ListView are usually configured to change their display mode. Such is the case for the right pane of Windows Explorer. Another category of list of this kind is implemented for a multiple-choice question exam or text where only one question as a member of the list of questions would display:

 

Using Lists

There are various reasons for using lists. Lists provide a uniform way of displaying a group of similar items to the user. Depending on how it is configured, a certain list could be used to display a simple list of items to the user; this could serve to provide static information to the user. An example is the list of Fonts of MS Windows:

The Fonts window provides a static list that allows the user to view and examine the list of fonts installed on the local computer. Although the user can be provided with limited interaction with the list, the list cannot be modified by, or receive input from, the user.
 
Another type of list also provides a static list but allows the user to select or retrieve an item from the list.

A popular type of list is available on database applications. These lists allow the user to select items from one list to create a new one. An example is the Form Expert from Corel Paradox:

When creating a list, you will decide how you would like users to interact with that part of your application, how they can use the list, what they can do and what they should be prevented from doing. The less interaction users have with a list, the least difficult it is to create. Depending on your intentions, you will need to provide just as much usefulness as possible to the user. Although lists can appear difficult to create and implement, users will not care how much work you put into creating a list.

Many controls use lists as their main components. Such controls are list views, combo boxes, rich texts, tree views, list boxes, color fields, radio button groups, text memos, checked list boxes, etc. There are various classes C++ Builder provides to create such lists.

The primary and the most regularly used class to create a list is the TStrings class.

The TStrings Class

 

Introduction

The TStrings class is used to provide most of the list-based controls with the properties and methods they need for their functionality. Because of this, such controls are equipped to take advantage of all (or most) properties of this class. Such controls need to be filled out with their main items, usually strings.

Since the TStrings class does not lead to a specific Windows control, the control that needs it has to call it. Put it in reverse order, the TStrings class is declared in each class that needs to create a list based on a TStrings class. For this reason, it is free to be named anyway a class wants it. For example, in the TMemo class, the TStrings variable is declared as Lines because a memo is just a list of paragraphs. On the other hand, the TStrings class is called Items in the TListBox and the TComboBox classes. For a TStringGrid object, the TStrings variable is called Cells.

To implement a list based on the TStrings class from any of these objects, call the TStrings property which in turn would give access to its properties and methods.

Working With Individual Strings

The operations performed using the TStrings class consist of creating a list of items, inserting new ones, deleting others, counting the items, changing their positions, or switching the items to another list.

The primary operation you as the programmer will perform on a new list is to fill it with the items the user can use. At design time, this is usually easy to do because most list-based controls provide a dialog box used to create an initial list. If you have to programmatically create the list, depending on the control, you would be interested to know not only whether the item was added to the list but also what position the item is occupying in the list. To create such a list, call the TStrings::Add() method. Its syntax is:

int __fastcall Add(const AnsiString Source);

This function takes one argument as an AnsiString object and adds it to the end of the target list. If the list is empty, the Add() method would initiate the list with the new item. The Source argument could be a locally defined string. For example, the following would add the “Borland C++ Builder is Fun!!!” string to a Memo control when the user clicks Button1:

//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
    Memo1->Lines->Add("Borland C++ Builder is fun!!!");
}
//---------------------------------------------------------------------------

You can also use a string held by another control. In this case you would call the control field that holds the string. For example, the following would add the content of the Edit1 edit box, or the Text property of the Edit control, to a Memo control when the user clicks Button1:

//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
    Memo1->Lines->Add(Edit1->Text);
}
//---------------------------------------------------------------------------

You could also use the name of a string variable to add its value to a TStrings variable:

//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
    String Address = "4812 Lockwood Drive #D12";
    Memo1->Lines->Add(Address);
}
//---------------------------------------------------------------------------

If the addition of the Source string to the TStrings variable was successful, Add() returns the position where the Source string was added. The position is zero-based. If Source was set as the first item of the list, its position would be 0. If it were added as the 2nd item of the list, it would assume position 1, etc. If you need to, you can find out the position the argument is occupying after being added. Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
    String Address = "4812 Lockwood Drive #D12";
    int Success = Memo1->Lines->Add(Address);

    if( Success != -1)
        ShowMessage(Address + " was added at position " +
                    String(Success + 1));
}
//---------------------------------------------------------------------------

If you are not interested in the position that Source occupied once it was added, you can use the TStrings:: Append() method. Its syntax is:

void __fastcall Append(const AnsiString Source);

For a text-based control such as Memo or Rich Edit, the only thing you want to know is whether the item was added to the list. This makes the TStrings:: Append() method useful. This method also takes one argument as the AnsiString object to be added. Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
    Memo1->Lines->Append("The magazine is on the table");
}
//---------------------------------------------------------------------------

Practical Learning: Using Individual Strings

  1. Start Borland C++ Builder if you didn't yet.
    On the main menu, click File -> New -> VCL Forms Application
  2. To save the project, on the Standard toolbar, click the Save All button 
  3. By clicking the Create new Folder button, create a new folder called List Filler and display it in the Save combo box
  4. Save the unit as Main and the project as ListFiller
  5. Change the form’s caption to Travel Plans
  6. Add a label on the top left section of the form. Set the caption to
    Select a Continent:
  7. Add a ComboBox control under the existing label. Change its name to cbxCountries
     
  8. To create a list of continents, double-click in the middle of the form to access its OnCreate event.
  9. Implement the even as follows:
     
    //---------------------------------------------------------------------------
    void __fastcall TForm1::FormCreate(TObject *Sender)
    {
        cbxContinents->Items->Append("Asia");
        cbxContinents->Items->Append("Europe");
        cbxContinents->Items->Append("America");
        cbxContinents->Text = "Asia";
    }
    //---------------------------------------------------------------------------
  10. To test the project, on the main menu, click Run -> Run
  11. Test the combobox by selecting different items. Close the form
  12. If the form is not displaying, press F12. Add a label under the combo box with a caption of Select a Country:
  13. Add a ListBox under the new label. Change its name to lstCountries
     
  14. To fill out the new control, we will do this depending on the item selected on the top ComboBox. Therefore, double-click the Select A Continent combo box to access its OnChange event.
  15. Set the default list for the Countries combo box in the OnCreate event of the form to synchronize with the default selection of the Continent ComboBox. Then implement the OnChange event of the Countries ComboBox as follows:
     
    //---------------------------------------------------------------------------
    void __fastcall TForm1::FormCreate(TObject *Sender)
    {
        cbxContinents->Items->Append("Asia");
        cbxContinents->Items->Append("Europe");
        cbxContinents->Items->Append("America");
        cbxContinents->Text = "Asia";
        
        lstCountries->Items->Append("Sri Lanka");
        lstCountries->Items->Append("Indonesia");
        lstCountries->Items->Append("Yemen");
        lstCountries->Items->Append("Iraq");
    }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::cbxContinentsChange(TObject *Sender)
    {
        if(cbxContinents->Text == "Asia")
        {
            lstCountries->Clear();
            lstCountries->Items->Append("Sri Lanka");
            lstCountries->Items->Append("Indonesia");
            lstCountries->Items->Append("Yemen");
            lstCountries->Items->Append("Iraq");
        }
        else if(cbxContinents->Text == "Europe")
        {
            lstCountries->Clear();
            lstCountries->Items->Clear();
            lstCountries->Items->Append("Sweden");
            lstCountries->Items->Append("Greece");
            lstCountries->Items->Append("Portugal");
            lstCountries->Items->Append("Spain");
        }
        else
            lstCountries->Clear();
    }
    //---------------------------------------------------------------------------
  16. To test the project, on the Debug toolbar, click the Run button
  17. On the Select A Continent ComboBox, select different continents and make sure the list of countries changes accordingly. After testing the ComboBox, close the form.

Strings and Their Positions in a List

While the Add() and the Append() methods are used to add a string to the end of a list, the Insert() method allows you to add a string to a position of your choice in the target list. The syntax of the Insert() method is:

void __fastcall Insert(int Index, const AnsiString Source)

This function needs two pieces of information. The Index argument specifies the intended position to insert the item. The positions are zero-based. If you want to add the new item on top of the list, set the Index to 0, for the second place, set the Index to 1, etc. The string to be added is the Source argument. In the following example, the “Easy Listening” string is added to the 3rd position on a list:

//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
    ListBox1->Items->Insert(2, "Easy Listening");
}
//---------------------------------------------------------------------------

The Index value applies whether the list is sorted or not. If you would like to add a new string to a sorted list and insert it to the right alphabetical sequence, use the Add() or the Append() methods.

Besides adding, appending, or inserting, you can delete an item from a list. This is performed using the Delete() method whose syntax is:

void __fastcall Delete(int Index)
To delete an item, the Delete() method needs to know its position. Once again, the list is zero-based: the 1st item is at position 0, the 2nd at 1, etc. If the Index argument points to an item that does not exist, for example either you set it to a value greater than the number of items or all items have been deleted, nothing would happen (no error would be thrown). In the following example, the 3rd item of a combo box is deleted:

 

//---------------------------------------------------------------------------
void __fastcall TForm1::btnDeleteClick(TObject *Sender)
{
    cbxNetwork->Items->Delete(2);
}
//---------------------------------------------------------------------------

To (effectively) use the Delete() method, you should supply the position of the item you want to delete. Sometimes such a position would be invalid. The best thing to do is to first inquire whether the item you want to delete exists in the list. For this and many other reasons, you can ask the compiler to check the existence of a certain item in a list. This operation is performed using the IndexOf() method. Its syntax is:

int __fastcall IndexOf(const AnsiString Source);

To use this function, provide the string you are looking for. This string is an AnsiString object. If the string exists in the list, the IndexOf() method returns its position in the list. The following event is used to examine the items of a combo box looking for a Printer string:

//---------------------------------------------------------------------------
void __fastcall TForm1::btnFindClick(TObject *Sender)
{
    cbxNetwork->Items->IndexOf("Printer");
}
//---------------------------------------------------------------------------

If the IndexOf() method finds that the Source string exists in the target list, it returns the position of Source. You can then use this information as you see fit. For example you can insert a new string on top of the found string. Here is example:

//---------------------------------------------------------------------------
void __fastcall TForm1::btnInsertClick(TObject *Sender)
{
    int Found = cbxNetwork->Items->IndexOf("Printer");

    if( Found )
        cbxNetwork->Items->Insert(Found, "Digital Camera");
}
//---------------------------------------------------------------------------

You could also look for Source in a target list and delete it if it exists. Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::btnDeleteClick(TObject *Sender)
{
    int Found = cbxNetwork->Items->IndexOf("Printer");

    if( Found )
        cbxNetwork->Items->Delete(Found);
}
//---------------------------------------------------------------------------

Another type of operation you will perform in a list is to retrieve the text of a particular string in the group; this usually happens when you want to transfer an item to another control or display a message about the item. The Strings property allows you to get an item based on its position in the list. This property is an array that represents the items of the list. To locate a string, use the square brackets to specify its position. Because the list is zero-based, the first item is 0 and would be represented by Strings[0], the second is 1 and would be called with Strings[1], etc.

For the following example, when the user clicks the list box, regardless of the item selected, a label on the form would retrieve the third item of the list, provided the list has at least 3 items:

//---------------------------------------------------------------------------
void __fastcall TForm1::ListBox1Click(TObject *Sender)
{
    Label1->Caption = ListBox1->Items->Strings[2];
}
//---------------------------------------------------------------------------

One of the most effective ways to use the Strings property is to find out the item that the user would have selected in a list and act accordingly. In the following example, when the user selects a radio button from a RadioGroup control, the caption of the button selected displays on a label:

//---------------------------------------------------------------------------
void __fastcall TForm1::RadioGroup1Click(TObject *Sender)
{
    // Find the position of the item selected
    int ItemPos = RadioGroup1->ItemIndex;
    // Get the text of the item selected
    String strSelected = RadioGroup1->Items->Strings[ItemPos];
    // Using a label, display a message associated with the item selected
    Label1->Caption = "You selected " + strSelected;
}
//---------------------------------------------------------------------------

One of the most regular reasons for this operation is to make sure that a string is not duplicated and added to a list that already has the Source argument. In the following example, when the user types a string in an edit box and clicks somewhere else (that is, when the Edit control looses focus), the compiler checks to see if the string in the edit box already exists in the list. If the list of the combo box does not have the string in the edit box, then this string is added to the list:

//---------------------------------------------------------------------------
void __fastcall TForm1::edtLaptopExit(TObject *Sender)
{
    String Laptop = edtLaptop->Text;
    int Exists = cbxNetwork->Items->IndexOf(Laptop);

    if( Exists == -1 )
        cbxNetwork->Items->Append(Laptop);
}
//---------------------------------------------------------------------------

Some operating system configuration files contain lines with the = symbol as part of a string. There is no strict rule on what those files are or what they do. The company or the person who creates such a file also decides what the file is used for and when. For example, a music program would use such a file to configure the keyboard keys as related to the associated software. A communication program could use another type of those files as a list or table of ports. Programmers have to deal with those files for various reasons. Some of those files have strings made of three parts: Left=Right. The value called Right has to be assigned to the value on the left. Sometimes the Left value is called a Key; sometimes it is called a Name. The value on the right of equal is also called a Value. Therefore, a string on this file would have the form Name=Value. Some of these files have the .INI extension but can perfectly have any extension the programmer wanted.

Depending on how the file is structured, programmers have to find a certain key or name in the list. The TStrings class is equipped with a function that helps with this operation. The method is the IndexOfName() and its syntax is:

int __fastcall IndexOfName(const AnsiString Name);

This method takes an AnsiString object as argument. The compiler scans the list looking for a string that has the form Name=Value. If the left part of a string matches the Name argument, the IndexOfName() method returns the first position where such a string was found. The following dialog box is equipped with an edit box and a memo. To add a new key to the list of keys in the memo, the user types the key in the edit box and clicks the Add Key button (the Edit control is named edtNewKey, the Memo control is named mmoConfigure, the button is named btnAddKey, the bottom Edit control is named edtFound):

Here is an example of implementing the IndexOfName() method from the OnClick event of the Add Key button:

//---------------------------------------------------------------------------
void __fastcall TForm1::btnAddKeyClick(TObject *Sender)
{
    AnsiString NewKey = edtNewKey->Text;
    int LookFor = mmoConfigure->Lines->IndexOfName("HelpDir");

    if(LookFor == -1)
        mmoConfigure->Lines->Append(NewKey);
    else
        edtFound->Text = LookFor;
}
//---------------------------------------------------------------------------

When the user clicks the Add Key button, the Name part, which is the string on the left of the = symbol of the edit box, is checked for each string in the memo. If no name matches the Name part of the edit box, the new key is added to the memo. If that Name part is already in the list, the bottom edit box displays the position of the first string that contains Name.

Probably the best way to do this, “Live”, is to retrieve the Name part from the string that the user has typed in the top edit box. The following commented code would accomplish that:

//---------------------------------------------------------------------------
void __fastcall TForm1::btnAddKeyClick(TObject *Sender)
{
    // Get the content of the edit box
    AnsiString NewKey = edtNewKey->Text;
    // Find the position of the first occurrence in the edit box int
    EqualPos = NewKey.AnsiPos("=");
    // Create a string from the beginning to the first occurrence of =
    AnsiString NamePart = NewKey.Delete(EqualPos, NewKey.Length());
    // Find out if the Name part of the key is already in the list
    int LookFor = mmoConfigure->Lines->IndexOfName(NamePart);
    // if it is not, add the new key to the file
    if(LookFor == -1)
        mmoConfigure->Lines->Append(edtNewKey->Text);
}
//---------------------------------------------------------------------------

Again, depending on how the list is structured, you can ask the compiler to retrieve the name part of a string provided its position. This operation is performed using the Names property. This property is an array of the strings of this type of file. When needed, you can ask the compiler to give you the name part of a certain line. For example, to retrieve the name part of the 5th line, you could write Names[4]. If the item at that position does not have = as part of the string, the compiler would consider that the line is not a valid IndexOfName string and would avoid it. In the following example, the 2nd line of mmoConfigure is examined. If that line is a valid IndexOfName, its Name part displays in the Found At edit box:

//---------------------------------------------------------------------------
void __fastcall TForm1::btnGetNameClick(TObject *Sender)
{
    edtFound->Text = mmoConfigure->Lines->Names[1];
}
//---------------------------------------------------------------------------

By contrast, to retrieve the Value part of an IndexOfName string, use the Values property. This also is an array of the valid = strings of the list. It uses the following syntax:

AnsiString Values[AnsiString NamePart];

This time, you must (or should) provide the string you are looking for as if it were the position of the string. This string, which is an AnsiString object, should be the Name part of a string you are looking for. The compiler would scan the list of strings looking for valid IndexOfName strings. If it finds a string that encloses an = symbol, then the compiler would check its Name part. If this Name matches the NamePart of the Values property, then it would return the Value. This is useful if you want to know whether a certain key has already been defined in the file.

When providing the NamePart as the string to look for, make sure you do not include = as part of the string

Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::btnGetValueClick(TObject *Sender)
{
    edtFound->Text = mmoConfigure->Lines->Values["Directory"];
}
//---------------------------------------------------------------------------

Another usefulness of items of a list is to switch their positions as related to each other. The primary method used to swap items is the Move() method. Its syntax is:

void __fastcall Move(int CurrentPos, int NewPos);

This function takes two strings. The first string is considered for its position. When executing, the function moves the first item from the CurrentPos position to the position specified by NewPos. The following event would move the 2nd item of a CheckListBox control to the 5th position:

//---------------------------------------------------------------------------
void __fastcall TForm1::btnMoveClick(TObject *Sender)
{
    CheckListBox1->Items->Move(1, 4);
}
//---------------------------------------------------------------------------

After the move, the item at CurrentPos is moved to NewPos. If the item is moved just one position, all of the items whose positions are between CurrentPos and NewPos are affected. If the item moved up, the items that were above it would be moved down. The opposite occurs if the item has moved down.

While the Move() method is used to move an item from one position to another, the Exchange() method is used to switch two items. Its syntax is:

void __fastcall Exchange(int Index1, int Index2);

The compiler takes the item at Index1, moves it to Index2, takes the item that was at Index2 and moves it to Index1. In the following example, the items at the 4th and the 1st positions are switched:

//---------------------------------------------------------------------------
void __fastcall TForm1::btnExchangeClick(TObject *Sender)
{
    CheckListBox1->Items->Exchange(3, 0);
}
//---------------------------------------------------------------------------
 

Practical Learning: Using Strings Positions

  1. To continue with our list project, add a label to the right side of the Select a Continent label. Set its caption to Countries Selected
  2. Add a ListBox control under the Countries Selected label. Change its name to lstCities
     
    //---------------------------------------------------------------------------
    void __fastcall TForm1::lstCountriesClick(TObject *Sender)
    {
        // When a new country is selected, empty the list of cities
        lstCities->Clear();
    
        // If a country is selected, get its name, instead of its position
        int i = lstCountries->ItemIndex;
        AnsiString Selected = lstCountries->Items->Strings[i].c_str();
    
        if(Selected == "Portugal")
        {
            lstCities->Clear();
            lstCities->Items->Append("Braga");
            lstCities->Items->Append("Coimbra");
            lstCities->Items->Append("Viana do Castelo");
            lstCities->Items->Append("Aveiro");
        }
        else if(Selected == "Indonesia")
        {
            lstCities->Clear();
            lstCities->Items->Append("Makassar");
            lstCities->Items->Append("Ambon");
            lstCities->Items->Append("Kupang");
            lstCities->Items->Append("Jakarta");
            lstCities->Items->Append("Surabaya");
        }
        else if(Selected == "Sweden")
        {
            lstCities->Clear();
            lstCities->Items->Append("Upsala");
            lstCities->Items->Append("Kalmar");
            lstCities->Items->Append("Sundsvall");
            lstCities->Items->Append("Tärnaby");
            lstCities->Items->Append("Kiruna");
            lstCities->Items->Append("Göteborg");
        }
        else
            lstCities->Clear();
    }
    //---------------------------------------------------------------------------
  3. To test the project, on the Debug button, click the Run button.
  4. Save your project

Working With a Group of Strings

Instead of adding one item at a time to a string, there are various techniques available for filling out a list with all of the needed items. If you want to fill out a Memo or a RichEdit controls with a file, which is just a list of strings, use the TStrings::LoadFromFile() method. Its syntax is:

void __fastcall LoadFromFile(const AnsiString FileName);

This function takes an AnsiString object as argument, which is the FileName. If you want to open a file whose path you know, you can provide this path as the argument. Here is an example that fills out a Memo control of a form with the contents of a text file:

//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
    Memo1->Lines->LoadFromFile("C:\\Windows\\WINHELP.INI");
}
//---------------------------------------------------------------------------

If you provide the path of a file but the file does not exist, when the user clicks the button, the application would throw an uncomfortable error. The best alternative is to let the user select a file using an appropriate object such the Open dialog box. In this case, the FileName argument would be matched to the content of the dialog box. Following this code, a Memo control named Memo1 would be filled with the content of a file opened from an OpenDialog1 and a Button1 controls placed on the form:

//---------------------------------------------------------------------------
void __fastcall TForm1::btnOpenFileClick(TObject *Sender)
{
    if(OpenDialog1->Execute())
        Memo1->Lines->LoadFromFile(OpenDialog1->FileName);
}
//---------------------------------------------------------------------------

On the other hand, the TStrings class is also equipped with a special method that can be used to save a file from a Memo or a RichEdit controls. Its syntax is:

void __fastcall SaveToFile(const AnsiString FileName);

This method takes an AnsiString object as the argument, called FileName. This argument specifies the default name of the file being saved by the user. Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::btnSaveClick(TObject *Sender)
{
    if( SaveDialog1->Execute() )
        Memo1->Lines->SaveToFile(SaveDialog1->FileName);
}
//---------------------------------------------------------------------------

Besides the Memo and RichEdit controls, you can also fill out a list of a control from the strings of another list. This is mainly performed using the AddStrings() method whose syntax is:

void __fastcall AddStrings(TStrings* Str);

The argument provided to this string must be a valid string object. Therefore, you should make sure that the list originates from another list-based control or from another valid source. The Str argument could come from a known control. Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::btnTransferFileClick(TObject *Sender)
{
    Memo2->Lines->AddStrings(Memo1->Lines);
}
//---------------------------------------------------------------------------

Since the TStrings class is a descendent of the TPersistent class, you can also use the Assign() method. Its syntax is:

void __fastcall Assign(TPersistent* Source);

The above event could also be written as:

//---------------------------------------------------------------------------
void __fastcall TForm1::btnTransferFileClick(TObject *Sender)
{
    Memo2->Lines->Assign(Memo1->Lines);
}
//---------------------------------------------------------------------------

At any time you can find out how many items compose a list using the Count property. Not only does this property provide an integral count of the members of the list but also you can use it when scanning the list using a for loop. The fundamental way of using the Count property is to get the number of items of a list. In the following example, when the user clicks the Count button, the number of strings in the RadioGroup1 control displays in the Count edit box:

//---------------------------------------------------------------------------
void __fastcall TForm1::btnCountClick(TObject *Sender)
{
    edtCount->Text = RadioGroup1->Items->Count;
}
//---------------------------------------------------------------------------

An alternative is to use the Capacity property which fundamentally also provides the number of items of a list. Its main role is to deal with memory allocation especially when writing a component that uses a list of items.

If you have two lists and want to compare them, use the Equals() method whose syntax is:

bool __fastcall Equals(TStrings* Strings);
This function requires a TStrings list of strings. The conversion is performed on two fronts. First, both lists should have the same number of strings. If both lists have different number of items, the function returns false. If they have the same number of strings, then the compiler examines the strings one line at a time, for each list. If two strings of the same position are not the same, the function returns false. This means that even if both lists have the same strings, the comparison can still render negative.

Here is an example of implementing this method:

//---------------------------------------------------------------------------
void __fastcall TForm1::btnCompareListsClick(TObject *Sender)
{
    if( !ListBox1->Items->Equals(ListBox2->Items) )
        ShowMessage("Both lists are not the same\n"
                    "You should synchronize them before exiting");
}
//---------------------------------------------------------------------------

The items of a TStrings list are usually text-based although they can also be of different kinds of objects. When the items are made of strings, sometimes you will need to convert them to a single text. Such is the case when transferring the items of a ListBox or a ComboBox to a text document or a rich text file. To convert a TStrings list of strings to text, you should translate the list to a C-based string of objects. This can be done using the GetText() method whose syntax is:

char * __fastcall GetText(void);

When this method is called by a TStrings variable, it returns a null-terminated string that represents all of the items of the text.

To assign a C-based string to a TStrings list of strings, use the SetText() method. Its syntax is:

void __fastcall SetText(char * Text);

This method takes one argument as a C string and converts it to TStrings.

You can use these two methods to perform the same transfer or transaction we used to pass a list of strings from one list to another. Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::btnGetTextClick(TObject *Sender)
{
    char *WholeList = Memo1->Lines->GetText();
    Memo2->Lines->SetText(WholeList);
}
//---------------------------------------------------------------------------

Even if the controls are of different kinds, you can use the same transaction. For example, the following event transfers the contents of a ListBox to a memo:

//---------------------------------------------------------------------------
void __fastcall TForm1::btnTransToLBXClick(TObject *Sender)
{
    char *WholeList = ListBox1->Items->GetText();
    Memo3->Lines->SetText(WholeList);
}
//---------------------------------------------------------------------------

This transfer of a list of strings from a ListBox, a ComboBox, a CheckListBox or a RadioGroup controls to a text-based control such as a memo, a RichEdit or a label can also effectively be accomplished using the Text property. When you ask it to use the Text property to perform a transfer, the compiler scans the list of items. At the end of each item, the compiler adds a carriage return (this is equivalent to the Enter key) and add the next string to the next line. This allows the compiler to create an AnsiString object made of all of the strings. Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm2::btnTransferClick(TObject *Sender)
{
    Memo1->Lines->Add(ListBox1->Items->Text);
    Edit1->Text = ListBox1->Items->Text;
    Label1->Caption = ListBox1->Items->Text;
}
//---------------------------------------------------------------------------

As you can see, this transfer is not properly interpreted by the Edit control because this control does not have a multiple line capability while the WordWrap property helps to manage the Label control.

Another technique used to most effectively transfer a list from a strictly list-based control, such as a ListBox, a ComboBox or a RadioGroup control to a text-based control such as a memo or a RichText control, is to use the CommaText property. When called to use this property, the compiler would scan the list of items. If an item is a one-word string, the compiler would write a comma “,” on its right and start adding the next item. If the item is a string made of more than one word, to delimit it, the compiler would enclose it with double-quotes. This is done until the last string

In the following example, when the user clicks the Transfer button, the list of items from the ListBox is transferred to both a memo and an edit box using the CommaText property:

//---------------------------------------------------------------------------
void __fastcall TForm1::btnTransferClick(TObject *Sender)
{
    Memo1->Lines->Add(ListBox1->Items->CommaText);
    Edit1->Text = ListBox1->Items->CommaText;
}
//---------------------------------------------------------------------------

If you decide to dismiss a whole list, use the Clear() method. Its syntax is:

void __fastcall Clear();

Unlike the Delete() method, the Clear() function completely empties the list of items. Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::btnEmptyTheListClick(TObject *Sender)
{
    Memo1->Lines->Clear();
}
//---------------------------------------------------------------------------

The TStringList Class

 

Introduction

The TStrings class appears to provide enough methods to perform any operation on any list of strings. Unfortunately, because TStrings is an abstract class, you cannot declare an instance of it, which means that you cannot create a dynamic list of strings using the TStrings class. That is one of the reasons you will use alternate classes to accomplish such a task.

The TStringList is derived from the TStrings class and adds new properties and methods for operations not possible on its parent. This is because the TStrings class can only fill out a list. It cannot independently manage the items of its own list; it relies on the control that uses it.

One of the strengths of the TStringList class is that, unlike the TStrings class, it is not associated with any control, not even a control that creates a list. Therefore, you are in charge of using it as you see fit. This also allows a TStringList object to accommodate almost any control that uses a list. It also provides the primary means of exchanging items among controls of various kinds.

Creating a TStringList Object

Because the TStringList class is not a property of any control, whenever you need it, you must create it dynamically. And because it is not a control, you only need the new operator to declare an instance of a TStringList class. The compiler does not need to know the parent or owner of the list. For the same reason, you have the responsibility of deleting the list when you do not need it anymore. Although lists are usually difficult to create and maintain, Borland did a lot of work behind the scenes so that the compiler can tremendously help you with your list.

To dynamically create a list, you must declare an instance of a TStringList class using the new operator. If you are planning to use the list inside of only one function or event, you can initiate the object as follows:

//---------------------------------------------------------------------------
void __fastcall TForm1::CreateAlist()
{
    TStringList *Categories = new TStringList;
}
//---------------------------------------------------------------------------

Such a list cannot be accessed outside of the event or function in which you created it. If you want the list to be available to more than one event or function, create it globally. This could be done on top of the source file. Here is an example:

//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
TStringList *Categories = new TStringList;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//---------------------------------------------------------------------------

This time, the list would be available to any event or function of the same source file. Other objects, events, or functions that are part of other units (for example if you call this form from another form) other than this one cannot access the list. The alternative, sometimes the best one, is to declare the list variable in the header file of the primary unit that would manipulate or use it. This is done in the private, public or protected sections of the unit. If only one unit will use this list, declare the variable in the private section. By contrast, if more than one unit will need it, then declare it in the public section. This time, since you cannot initialize a variable in a class, only declare a pointer to a TStringList class. Here is an example:

//---------------------------------------------------------------------------
#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
private:
    TStringList * Categories; // User declarations
public: // User declarations
    __fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif

After declaring such a variable, you should initialize it in a function or event that would be called prior to any other event or function using the list. Although this can be done in the OnCreate() event of a form, probably the safest place to initialize the list is the constructor of the form. You will use the new operator to initialize the variable. Here is an example:

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
    Categories = new TStringList;
}
//---------------------------------------------------------------------------

Once you have created the list locally or globally, you can call any of its properties or methods to manipulate it.

Using a TStringList Object

The fundamental difference between the TStrings and the TStringList classes is that, besides the first being the parent of the second, you cannot create an instance of a TStrings class. Using the properties of inheritance, you can perform any operation on a TStringList variable that you would perform on a TStrings object.

Like a TStrings list, the primary means of filling out a TStringList object is by using either the Add() or the Append() methods. The Append() method is only inherited. The syntax of the TStringList::Add() method is:

int __fastcall Add(const AnsiString Source);

Once again, each item is added to the list as an AnsiString object. If the addition is successful, the method returns the position occupied by the Source argument in the list. If the list was empty, Source would occupy the first position, which is 0 because the list is zero-based. If the list already had at least one item, the Source argument would be added to the end of the existing items.

Practical Learning: Creating a Dynamic List

  1. Start a new project with the default form
  2. Change the caption of the form to Date and Time and its name to Main
  3. To save the project, on the Standard toolbar, click the Save All button 
  4. Create a new folder called DateAndTime
  5. Save the unit as Main
  6. Save the project as DateTimeDlg
  7. Change the caption of the form to Date and Time and set its BorderStyle property to bsDialog
  8. Change the name of the form to DateAndTime
  9. Set the Height property of the form to approximately 266 and its Width to 306
  10. Add a Bevel control to the form. Set its Height to approximately 217 and set its Width to approximately 201
  11. Add another Bevel control inside the first one. Set its Style to bsRaised
  12. Add a label inside the interior bevel and set its caption to Available Formats:
  13. Add a ListBox control under the label but completely inside the interior bevel. Change the name of the listbox to lstFormats
  14. Add a button to the top right section of the form. Set its name to btnOK and its Default property to true. Change its Caption to OK
  15. Add another button under the OK buton. Change its name to btnCancel and set its Cancel property to true. Change its Caption to Cancel
     
  16. Because the date and time values must be set by the computer at the exact time the user needs a date or time value for a document, the list of dates and times of our dialog must be created dynamically.
    Double-click an unoccupied area on the form to access its OnCreate event.
  17. Implement it as follows:
     
    //---------------------------------------------------------------------------
    void __fastcall TDateAndTime::FormCreate(TObject *Sender)
    {
        TDateTime TodayDate = Date();
        TDateTime RightNow = Time();
        TStringList* DateList = new TStringList;
    
        try {
            DateList->Add(TodayDate);
            ShortDateFormat = "m/d/yy";
            DateList->Add(TodayDate);
            ShortDateFormat = "m/dd/yy";
            DateList->Add(TodayDate);
            ShortDateFormat = "m/dd/yyyy";
            DateList->Add(TodayDate);
            ShortDateFormat = "yy/m/dd";
            DateList->Add(TodayDate);
            DateSeparator = '-';
            ShortDateFormat = "yyyy/m/dd";
            DateList->Add(TodayDate);
            ShortDateFormat = "dd/mmm/yy";
            DateList->Add(TodayDate);
            LongDateFormat = "dddd";
            DateSeparator = ',';
            ShortDateFormat = "dddd/ mmmm dd/ yyyy";
            DateList->Add(TodayDate);
            ShortDateFormat = "mmmm dd/ yyyy";
            DateList->Add(TodayDate);
            ShortDateFormat = "dddd/ dd mmmm/ yyyy";
            DateList->Add(TodayDate);
            ShortDateFormat = "dd mmmm/ yyyy";
            DateList->Add(TodayDate);
            ShortTimeFormat = "h:n:s";
            DateList->Add(RightNow);
            ShortTimeFormat = "hh:nn:ss";
            DateList->Add(RightNow);
            LongTimeFormat = "HH:nn:ss";
            DateList->Add(RightNow);
            lstFormats->Items->AddStrings(DateList);
        }
        __finally
        {
            delete DateList;
            DateList = NULL;
        }
    }
    //---------------------------------------------------------------------------
  18. Save your project.
  19. To test the dialog box, on the Debug toolbar, click the Run button 
    Save your project.
  20. To test the dialog box, on the Debug toolbar, click the Run button 

Creating a List

 

Introduction

When it comes to creating a list, everything depends on what the list would be used for, what you want the list to accomplish, how much, and what kind of interaction the users would have with the list. As we saw already, there are three main categories of lists. The easiest lists tend to be those that only display items; the only options to the user could be to change how the list displays. The 2nd categories in difficulty are those the user would use to perform selections; these types are common in (desktop) database applications. Probably the most difficult of the lists are those that, either the users would create or the user would be allowed to edit the items or the list itself. The same advice would be given here: only provide the necessary tools to the user, not more not less.

TStrings and TStringList Based Lists

As you know already, the VCL provides various classes for creating lists. It is very likely that one class would not be enough to handle all of the functionality you expect from your application. Therefore, you should know how and when to combine classes to assign the needed actions the user would need to perform.

The TStrings and the TStringList classes exchange information fairly easily. It is likely that you will have to use the TStringList class whenever you need a dynamic list. Then use the TStrings class to actually fill a list on a control.

Practical Learning Practical Learning: String Lists Handling

  1. Start a new project with the default form
  2. To save the project, on the main menu, click File -> Save All
  3. Create a new folder named Travel Planning
  4. Save the unit as Main and save the project as TravelPlaning
  5. Change the caption of the form to Travel Planning and its name to frmMain
  6. To create a list of continents, add a GroupBox control to the top left section of the form. Change its caption to Continents
  7. Add six RadioButton controls named rdoAfrica, rdoAmerica, rdoPacific, rdoAsia, rdoEurope, and rdoNowhere as follows:
     
  8. Set each radio button’s caption by removing rdo from its name
  9. Save the project and test the functionality of the radio buttons by running the project. Return to Borland C++ Builder
  10. To add our first list control, add a ListBox control under the GroupBox control.
  11. Change its name to lstCountries
     
  12. To create five lists variables, on the Class Explorer, expand the Classes folder. Double-click TfrmMain to access its header file.
  13. Declare the following TStringList variables:
     
    //---------------------------------------------------------------------------
    #ifndef MainH
    #define MainH
    //---------------------------------------------------------------------------
    #include <Classes.hpp>
    #include <Controls.hpp>
    #include <StdCtrls.hpp>
    #include <Forms.hpp>
    //---------------------------------------------------------------------------
    class TfrmMain : public TForm
    {
    __published: // IDE-managed Components
        TGroupBox *GroupBox1;
        TRadioButton *rdoAfrica;
        TRadioButton *rdoAmerica;
        TRadioButton *rdoAsia;
        TRadioButton *rdoEurope;
        TRadioButton *rdoPacific;
        TRadioButton *rdoNowhere;
        TListBox *lstCountries;
    private: // User declarations
    public:
        TStringList* Africa;
        TStringList* America;
        TStringList* Asia;
        TStringList* Europe;
        TStringList* Pacific; // User declarations
        __fastcall TfrmMain(TComponent* Owner);
    };
    //---------------------------------------------------------------------------
    extern PACKAGE TfrmMain *frmMain;
    //---------------------------------------------------------------------------
    #endif
  14. On the Code Editor, click the Main.cpp tab. In the form’s constructor, initialize the variables as follows:
     
    //---------------------------------------------------------------------------
    #include <vcl.h>
    #pragma hdrstop
    #include "Main.h"
    //---------------------------------------------------------------------------
    #pragma package(smart_init)
    #pragma resource "*.dfm"
    TfrmMain *frmMain;
    //---------------------------------------------------------------------------
    __fastcall TfrmMain::TfrmMain(TComponent* Owner)
        : TForm(Owner)
    {
        Africa = new TStringList;
        America = new TStringList;
        Asia = new TStringList;
        Europe = new TStringList;
        Pacific = new TStringList;
    }
    //---------------------------------------------------------------------------
  15. Press F12 to access the form. Double-click an empty area on the form to access its OnCreate event. Implement it as follows:
     
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::FormCreate(TObject *Sender
    {
        // Create a list of countries for each continent
        Africa->Add("Cameroon");
        Africa->Add("Lybia");
        Africa->Add("Botswana");
        Africa->Add("Morocco");
    
        America->Add("Argentina");
        America->Add("Haiti");
        America->Add("USA");
        America->Add("Uruguay");
    
        Asia->Add("Thailand");
        Asia->Add("Sri Lanka");
        Asia->Add("Iraq");
        Asia->Add("Japan");
        Asia->Add("Bangla Desh");
    
        Europe->Add("Denmark");
        Europe->Add("Italy");
        Europe->Add("Greece");
        Pacific->Add("New Zealand");
        Pacific->Add("Australia");
    
        // Select or at least make sure that one radio button is selected
        rdoAfrica->Checked = True;
    }
    //---------------------------------------------------------------------------
  16. With each continent filled with a few countries, we need to fill out the list of countries depending on the continent that is selected in the GroupBox.
    Press F12 to display the form. On the form, double-click the Africa radio button to access its OnClick event.
  17. Access the form again. Double-click each one of the other radio buttons and implement them as follows:
     
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::rdoAfricaClick(TObject *Sender)
    {
        lstCountries->Items->AddStrings(Africa);
        lstCountries->ItemIndex = 0;
    }
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::rdoAmericaClick(TObject *Sender)
    {
        lstCountries->Items->AddStrings(America);
        lstCountries->ItemIndex = 0;
    }
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::rdoPacificClick(TObject *Sender)
    {
        lstCountries->Items->AddStrings(Pacific);
        lstCountries->ItemIndex = 0;
    }
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::rdoAsiaClick(TObject *Sender)
    {
        lstCountries->Items->AddStrings(Asia);
        lstCountries->ItemIndex = 0;
    }
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::rdoEuropeClick(TObject *Sender)
    {
        lstCountries->Items->AddStrings(Europe);
        lstCountries->ItemIndex = 0;
    }
    //---------------------------------------------------------------------------
  18. Save your project.
  19. To test the project, on the main menu, click Run ª Run.
  20. Click various radio buttons and verify that the list of countries changes everytime a new continent is selected. Actually, we have a small problem. When a continent is selected, we need to display only the countries that are part of that continent. Close the form to return to Borland C++ Builder
  21. In the Code Editor, add the highlighted line on top of the OnClick event for each radio button:
     
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::rdoAfricaClick(TObject *Sender)
    {
        // When a new continent is selected, empty the list of countries
        // to make sure that countries are not mixed
        lstCountries->Clear();
        lstCountries->Items->AddStrings(Africa);
        lstCountries->ItemIndex = 0;
    }
    //---------------------------------------------------------------------------
  22. On the form, double-click the Nowhere radio button to access its OnClick event and implement it as follows:
     
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::rdoNowhereClick(TObject *Sender)
    {
        lstCountries->Clear();
    }
    //---------------------------------------------------------------------------
  23. Save your project.
  24. Test the project. This time, each radio button controls its list of countries. Close the form.
  25. Now we need to display the countries associated with each continent.
    Add a label on the right side of the GroupBox. Set its caption to Cities
  26. Add ListBox control to the form. Change its name to lstCities
     
  27. To fill out the list of cities, we will rely on the country that was selected in the Countries listbox.
    On the form, double-click the Countries listbox to access its OnClick event. Implement it as follows:
     
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::lstCountriesClick(TObject *Sender)
    {
        // Since a new country has been selected, empty the list of cities
        lstCities->Clear();
        // If a country is selected, get its name, instead of its position
        AnsiString Selected =
            lstCountries->Items->Strings[lstCountries->ItemIndex].c_str();
        // Create a list of cities for each country
        // and fill out the Cities list with this list
        if(Selected == "Cameroon")
        {
            lstCities->Items->Add("Ebolowa");
            lstCities->Items->Add("Ngaoundere");
            lstCities->Items->Add("Batouri");
            lstCities->Items->Add("Bamenda");
        }
        else if(Selected == "Senegal")
        {
            lstCities->Items->Add("Thiès");
            lstCities->Items->Add("Kaolack");
            lstCities->Items->Add("Dakar");
        }
        else if(Selected == "USA")
        {
            lstCities->Items->Add("Atlanta");
            lstCities->Items->Add("Boston");
            lstCities->Items->Add("Miami");
            lstCities->Items->Add("San Diego");
            lstCities->Items->Add("Chicago");
        }
        else if(Selected == "Haiti")
        {
            lstCities->Items->Add("Port-de-Paix");
            lstCities->Items->Add("Les Cayes");
            lstCities->Items->Add("Jérémie");
            lstCities->Items->Add("Port-Au-Prince");
            lstCities->Items->Add("Jacmel");
        }
    
        // Select the first city in the list of cities
        // This reduces the possibility of an error due to no selected city
        lstCities->ItemIndex = 0;
    }
    //---------------------------------------------------------------------------
  28. Test the project. Select different radio buttons and different countries. Make sure that the lists change correcty. The problem we have at this time is that, although we have configured the list of Cities to display Cameroonian cities when that country is selected, When the form opens, even though Cameroon is selected, its cities are not displaying in the Cities list.
  29. Close the form.
  30. If the form is not displaying, press F12. Click an unoccupied area of the form to select the form. On the Object inspector, click the Events tab. Double-click the empty area on the right side of OnActivate and implement it as follows:
     
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::FormActivate(TObject *Sender)
    {
        // Behave as if the Countries list had been clicked
        lstCountriesClick(Sender);
    }
    //---------------------------------------------------------------------------
  31. Test the program again. This time, the cities of the country selected always display in the Cities listbox. Click different countries.
  32. Close the form and return to Borland C++ Builder.
  33. Now, we will allow the user to select cities from the Cities listbox and create a list of cities to visit.
    Add a new label to the right side of the Cities label. Set its caption to Selected
  34. Add a new ListBox control under the Selected: label. Change its name to lstSelected
     
  35. Fundamentally, the user can use a button to select a city and put it in a new list. If the list is empty, the item will be the first in the list. If the list already contains a city, the new city will be added to the end of the existing one(s). To make our application more useful, we will let the user decide whether to add the new item on top or under a selected item already in the list.
    Add a new button between the right list boxes. Change its name to btnAddAbove and set its Caption to >
  36. Double-click the new button and implement its OnClick event as follows:
     
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::btnAddAboveClick(TObject *Sender)
    {
        // Since a city was selected, get its string (its name)
        AnsiString NewCity = lstCities->Items->Strings[lstCities->ItemIndex];
        // add the new item to the end of the list
        lstSelected->Items->Add(NewCity);
    }
    //---------------------------------------------------------------------------
  37. To make our program more user friendly, besides clicking an item from the Cities list, we will also allow the user to double-click an item to add it to the Selected list. Instead of writing code to implement this, we can just use code that has already been written.
    Press F12 to access the form. On the form, click the Cities listbox to select it. On the Object Inspector, click the Events tab.
  38. Click the OnDblClick field to reveal its combo box. Click the arrow of the combo box and select btnAddAboveClick:
     
  39. Test the program. Select different continents, different countries, and different cities. Also, click the button just added. Observe that, although the button can create a list of cities effectively, the problem is that it duplicates them. The application we are creating allows the user to select cities to travel. There is no reason a city should be added twice: we need to prevent a city from being added if it exists in the Selected list already.
  40. Close the form and return to Borland C++ Builder.
  41. To check whether a city exists already in the Selected list, we will call the TStrings::IndexOf() by providing the name of the city we are looking for. If IndexOf() does not find the string, then we will add it.
    Change the OnClick event of the AddAbove button as follows:
     
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::btnAddAboveClick(TObject *Sender)
    {
        // Since a city was selected, get its string (its name)
        AnsiString NewCity =
            lstCities->Items->Strings[lstCities->ItemIndex].c_str();
    
        // find out whether the new string already exists in the Selected list
        // If if does, we will not add it
        if(lstSelected->Items->IndexOf(NewCity) == -1)
        {
            // add the new item to the end of the list
            lstSelected->Items->Add(NewCity);
        }
    }
    //---------------------------------------------------------------------------
  42. Test the project again. After making sure that no city gets duplicated in the Selected list, close the form and return to Borland C++ Builder.
  43. By the way, we had decided to let the user add a city above another city in the Selected list. This allows the user to prioritize the places to travel. In order to control this order, the user should first select a city. Using the Add Above button, we will insert the new city above the selected one, of course provided the new city is not already in the Selected list.
    Change the OnClick event of the AddAbove button as follows:
     
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::btnAddAboveClick(TObject *Sender)
    {
        // Find out if a city is selected in the Cities list
        if(lstCities->ItemIndex != -1)
        {
            // Since a city was selected, get its string (its name)
            AnsiString NewCity =
                lstCities->Items->Strings[lstCities->ItemIndex].c_str();
            // Find out whether the new string already exists in the Selected list
            // If if does, we will not add it
            if(lstSelected->Items->IndexOf(NewCity) == -1)
            {
                // Now find out if a city was chosen in the Selected list,
                if(lstSelected->ItemIndex != -1)
                {
                    // then, allow the user to insert the new item
                    // on top of the selected one
                    lstSelected->Items->Insert(lstSelected->ItemIndex, NewCity);
                    // Select the newly added city
                    lstSelected->ItemIndex = lstSelected->ItemIndex - 1;
                }
                else // Since no city was selected, add the new one at the end
                {
                    // add the new item to the end of the list
                    lstSelected->Items->Add(NewCity);
                }
            }
        }
    }
    //---------------------------------------------------------------------------
  44. Test the project to make sure it works fine. Test that the user can select an already added city in the Selected list and then insert a city above it. Close the form to Borland C++ Builder.
  45. Save your project.
  46. In the same way, we will allow the user to insert a city under a selected one in the Selected listbox.
    Add a new button under the AddAbove button. Change its name to btnAddBelow and set its caption to _
     
  47. Double-click the new button and implement it as follows:
     
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::btnAddBelowClick(TObject *Sender)
    {
        if(lstCities->ItemIndex != -1) // find out if a city is selected
        {
            // Since a city was selected, get its string (its name)
            AnsiString NewCity =
                lstCities->Items->Strings[lstCities->ItemIndex].c_str();
            // find out whether the new string already exists in the Selected list
            // If if does, we will not add it
            if(lstSelected->Items->IndexOf(NewCity) == -1)
            {
                if(lstSelected->ItemIndex != -1)
                {
                    // If a city was chosen in the Selected list,
                    // allow the user to insert the new item on top of
                    // the selected one
                    lstSelected->Items->Insert(lstSelected->ItemIndex + 1,
                                               NewCity);
    
                    // Select the newly added city
                    lstSelected->ItemIndex = lstSelected->ItemIndex + 1;
                }
                else
                {
                    // add the new item to the end of the list
                    lstSelected->Items->Add(NewCity);
                }
            }
        }
    }
    //---------------------------------------------------------------------------
  48. Since we have allowed the user to add one item at a time, if the user wants to visit all cities of a certain country, we will let the user select all cities and add them to the Selected list.
    Add a new button under the AddBelow button. Change its name to btnSelectAll and its caption to >>
  49. When the user decides to add all cities to the Selected listbox, although we can just call the TStrings::AddStrings() method to perform this job, it is possible that the Selected listbox already contains a certain city the user wants to add. For example, if a user selects a city X from a country A, then selects another city from country B, and then comes back to country A, since country A would display all of its cities, the user would have selected city A already. Once again, we need to prevent a certain city from being added twice to the Selected list.
    Double-click the new button and implement its OnClick event as follows:
     
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::btnSelectAllClick(TObject *Sender)
    {
        // We need to prevent the same city from being added twice
        // to the Selected list.
        // Initiate a scanning of all items of the Cities list
        for(int i = 0; i < lstCities->Items->Count; i++)
        {
            // Give the name City to each item of the Cities list
            AnsiString City = lstCities->Items->Strings[i].c_str();
    
            // Find out if the Selected list is empty
            if(lstSelected->Items->Count == 0) // then fill it with the cities
                lstSelected->Items->AddStrings(lstCities->Items);
            else // Since there is at least one item in the Selected list,
            {
                // Initiate the process of scanning each item of the Selected list
                for(int j = 0; j < lstSelected->Items->Count; j++)
                {
                    // If an item of the Cities list is not in the Selected list,
                    if(lstSelected->Items->IndexOf(City) == -1 )
                    {
                        // then add it to the Selected list, ignoring any match
                        lstSelected->Items->Add(City);
                    }
                }
            }
        }
    
        // Select the last item of the Selected list
        lstSelected->ItemIndex = lstSelected->Items->Count - 1;
    }
    //---------------------------------------------------------------------------
  50. The buttons we just added are used to add items to the Selected listbox. For example, the AddBelow button is used to insert a city under a selected one in the Selected listbox. If no item exists or no city is selected in the Selected listbox, we do not need this button. Therefore, we are going to disable it whenever we do not need this button. While we are at it, we will also disable the AddAbove and SelectAll buttons whenever we do not need them. This makes our program more professional and lets the user know what actions are available at a certain time.
    On the form, click the AddBelow button to select it. In the Object Inspector, set its Enabled property to false.
  51. Here is the current situation with our buttons. We our list system to display no countries when the Nowhere button is selected. When a new continent is selected using its radio button, if we do not have cities for the selected country in the Countries listbox, we empty the Cities listbox; otherwise, we fill it out with the cities of the selected country. To avoid exception errors from the Cities listbox, we need to disable the unneeded buttons. Finally, whenver a new country is selected, before displaying its cities, we should empty the Cities listbox. Since every radio button needs to check these values, we will just create a function that the needed controls can call to check what they want.
    On the Class Explorer, right-click the TfrmMain class and click New Method…
  52. Set the name to ControlTheLists and make sure the Class Name is TfrmMain. Set the Function Result to void and click the __fastcall check box. Click OK.
  53. Implement the function as follows:
     
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::ControlTheLists()
    {
        // When a new continent is selected, empty the list of countries
        // to make sure that countries are not mixed
        lstCountries->Clear();
        // Since a new item country has been selected, empty the list of cities
        lstCities->Clear();
    
        // If a city is selected, whose prerequisite is that
        // the Cities list not be empty in the first place
        if(lstCities->ItemIndex != -1)
        {
            // enable the selection buttons
            btnAddAbove->Enabled = True;
            btnSelectAll->Enabled = True;
        }
        else // Otherwise, either no city is selected or the list is empty
        {
            // disable the selection buttons
            btnAddAbove->Enabled = False;
            btnSelectAll->Enabled = False;
        }
    }
    //---------------------------------------------------------------------------
  54. To make this function available to needing events, call it on top of the OnClick event of each radio as follows:
     
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::rdoAfricaClick(TObject *Sender)
    {
        ControlTheLists();
        lstCountries->Items->AddStrings(Africa);
        lstCountries->ItemIndex = 0;
    }
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::rdoAmericaClick(TObject *Sender)
    {
        ControlTheLists();
        lstCountries->Items->AddStrings(America);
        lstCountries->ItemIndex = 0;
    }
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::rdoPacificClick(TObject *Sender)
    {
        ControlTheLists();
        lstCountries->Items->AddStrings(Pacific);
        lstCountries->ItemIndex = 0;
    }
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::rdoAsiaClick(TObject *Sender)
    {
        ControlTheLists();
    
        lstCountries->Items->AddStrings(Asia);
        lstCountries->ItemIndex = 0;
    }
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::rdoEuropeClick(TObject *Sender)
    {
        ControlTheLists();
    
        lstCountries->Items->AddStrings(Europe);
        lstCountries->ItemIndex = 0;
    }
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::rdoNowhereClick(TObject *Sender)
    {
        ControlTheLists();
        lstCountries->Clear();
    
        // When the user clicks this button, both the Countries and the Cities
        // lists are emptied. Therefore, we don't need the AddBelow button
        btnAddBelow->Enabled = False;
    }
    //---------------------------------------------------------------------------
  55. At this time, the buttons have been disabled, we need to find out when and where they should be enabled and disabled.
    At the bottom of the OnCreate event of the form, add the following commented code:
     
    // Select or at least make sure that one radio button is selected
        rdoAfrica->Checked = True;
        // Since the list of cities is empty at this time,
        // disable the selection buttons
        btnAddAbove->Enabled = False;
        btnSelectAll->Enabled = False;
    }
    //---------------------------------------------------------------------------
  56. At the bottom of the OnClick event of the Countries list box, add the following self-explanatory code:
     
    // Select the first city in the list of cities
        // This reduces the possibility of an error due to no selected city
        lstCities->ItemIndex = 0;
    
        // If there is something in the list of cities,
        // which is equivalent to the list not being empty
        if(lstCities->Items->Count > 0)
        {
            // enable the selection buttons
            btnAddAbove->Enabled = True;
            btnSelectAll->Enabled = True;
        }
        else // When the list of cities is empty
        {
            // disable the add and selection buttons
            btnAddAbove->Enabled = False;
            btnAddBelow->Enabled = False;
            btnSelectAll->Enabled = False;
        }
    }
    //---------------------------------------------------------------------------
  57. On the form, double-click the Selected list to access its OnClick event. Implement it as follows (the comments have been added to explain why):
     
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::lstSelectedClick(TObject *Sender)
    {
        // Apparently the user has made a selection in the Selected list
        // Maybe the user did not select anything. Well, find out
        if(lstSelected->ItemIndex != -1)
        {
            // Since the user made a selection,
            // change the caption of the AddAbove button
            btnAddAbove->Caption = "^";
            // Enable the AddBelow button btnAddBelow->Enabled = True;
        }
    }
    //---------------------------------------------------------------------------
  58. Save your project and test it. Make sure you can add cities. Also make sure that the selection button are enabled and disabled when necessary. Close the form

    just as we had allowed the user to add cities to the Selected list, we need to let the user removed the unneeded cities to create a better list.

  59. Add a new button under the SelectAll button. Change its name to btnRemove and its caption to R
     
  60. In order to remove a city, the user must have selected one. Therefore, this would be the first condition to check. Then, since a selected item will have been removed, we need to select another, just in case the user would like to remove more than one.
    Double-click the R button to access its OnClick event and implement it as follows:
     
    //---------------------------------------------------------------------------
    void __fastcall TForm1::btnRemoveClick(TObject *Sender)
    {
        // Get the position of the currently selected item
        int Selected = lstSelected->ItemIndex;
        // Since an item is selected already due to previous code
        // remove it from the list
        lstSelected->Items->Delete(lstSelected->ItemIndex);
        // After an item has been removed, get the position of
        // the item that was under it int Sel = Selected - 1;
        // Whenever an item has been removed from the Selected list,
        // select the item that was under it
        // Before making this selection, make sure that there is at least
        // one item in the Selected list
        if(Selected == lstSelected->Items->Count)
        {
            lstSelected->ItemIndex = Sel;
        }
        else
        {
            // Since the top item was selected, select the first item now
            lstSelected->ItemIndex = 0;
        }
        // If there are no more items in the Selected list,
        // then disable the Remove button
        if(lstSelected->Items->Count == 0)
        {
            btnRemove->Enabled = False;
        }
    }
    //---------------------------------------------------------------------------
  61. To allow the user to remove all selected cities, add another button under the R button. Change its name to btnRemoveAll and its caption to R-A
  62. Double-click the R-A button to access its OnClick event and implement it as follows:
     
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::btnRemoveAllClick(TObject *Sender)
    {
        // Remove everything from the Selected list
        lstSelected->Clear();
        // Since the Selected listbox has become empty,
        // we don't need the removing buttons anymore
        btnRemove->Enabled = False;
        btnRemoveAll->Enabled = False;
    }
    //---------------------------------------------------------------------------
  63. Once again, we need to know when the new buttons should be available or not. To start, when the form loads, the Selected list is empty. Since there is nothing to remove, we do not need these buttons. Therefore, change the bottom section of the form’s OnCreate event as follows:
     
        // Select or at least make sure that one radio button is selected
        rdoAfrica->Checked = True;
        // Since the list of cities is empty at this time,
        // disable the selection buttons
        btnAddAbove->Enabled = False;
        btnSelectAll->Enabled = False;
        btnRemove->Enabled = False;
        btnRemoveAll->Enabled = False;
    }
    //---------------------------------------------------------------------------
  64. When using the AddAbove button, whenever an item is added to the Selected list, we can enable the remove buttons in case the user wants to remove the selected city.
     
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::btnAddAboveClick(TObject *Sender)
    {
        // Find out if a city is selected in the Cities list
        if(lstCities->ItemIndex != -1)
        {
            // Since a city was selected, get its string (its name)
            AnsiString NewCity =
                lstCities->Items->Strings[lstCities->ItemIndex].c_str();
    
            // find out whether the new string already exists in the Selected list
            // If if does, we will not add it
            if(lstSelected->Items->IndexOf(NewCity) == -1)
            {
                // Now find out if a city was chosen in the Selected list,
                if(lstSelected->ItemIndex != -1)
                {
                    // then, allow the user to insert the new item
                    // on top of the selected one
                    lstSelected->Items->Insert(lstSelected->ItemIndex, NewCity);
                    // Select the newly added city
                    lstSelected->ItemIndex = lstSelected->ItemIndex - 1;
                }
                else // Since no city was selected, add the new one at the end
                {
                    // add the new item to the end of the list
                    lstSelected->Items->Add(NewCity);
                }
    
                // Since at least one item has been added to the Selected list,
                // enable the buttons used to remove items from the Selected list
                btnRemove->Enabled = True; btnRemoveAll->Enabled = True;
            }
        }
    }
    //---------------------------------------------------------------------------
  65. In the same way, when the SelectAll button has been used to add items to the Selected clist, we can enable the remove button. At the bottom section of the OnClick event of the SelectAll button, add the following:
     
        // Select the last item of the Selected list
        lstSelected->ItemIndex = lstSelected->Items->Count - 1;
        // Since some items have been added to the Selected list,
        // enable the buttons used to remove items from the Selected list
        btnRemove->Enabled = True;
        btnRemoveAll->Enabled = True;
    }
    //---------------------------------------------------------------------------
  66. We know that the user keeps removing items one by one using the R button, after all items have been removed, we do not need the R-A button. Therefore, we can disable it as follows:
     
        // If there are no more items in the Selected list,
        // then disable the Remove buttons
        if(lstSelected->Items->Count == 0)
        {
            btnRemove->Enabled = False;
            btnRemoveAll->Enabled = False;
        }
    }
    //---------------------------------------------------------------------------
  67. Test your project. Perform a few selections and removals. Close the form.
  68. Save your project.
  69. This time, we will allow the user to move cities and down in the Selected listbox, event the user could do it indirectly by playing with the add and remove buttons.
    Add a new button on the right side of the Selected button. Change its name to btnMoveUp and its caption to /\
  70. Add one more button to the form, this time position it below the /\ button. Change its name to btnMoveDown and its caption to \/
  71. Set the Enabled property of both buttons to false (when the form opens, we cannot move items; which means we do not need them).
  72. Double-click the MoveUp button and implement its OnClick event as follows (the comments give necessary explanations):
     
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::btnMoveUpClick(TObject *Sender)
    {
        // Get the position of the currently selected item int
        CurItem = lstSelected->ItemIndex;
    
        // To make sure the application does not throw a nasty error,
        // alsways make sure that, either the moveUp button is disabled
        // Or an item under the first one is selected
        if(CurItem == 1)
        {
            btnMoveUp->Enabled = False;
            lstSelected->ItemIndex = 0;
        }
        else
        {
            // Swap the selected item to tye one above it
            lstSelected->Items->Move(CurItem, CurItem - 1);
    
            // Follow the item that was selected
            lstSelected->ItemIndex = CurItem - 1;
        }
    
        // Just in case the MoveDown button was disabled, if the selected item
        // is not at the bottom of the list, then enable the MoveDown button
        if(CurItem < lstSelected->Items->Count)
            btnMoveDown->Enabled = True;
    }
    //---------------------------------------------------------------------------
  73. Access the form and double-click the MoveDown button. Implement its OnClick event as follows:
     
    //---------------------------------------------------------------------------
    void __fastcall TfrmMain::btnMoveDownClick(TObject *Sender)
    {
        // Get the position of the currently selected item
        int CurItem = lstSelected->ItemIndex;
        // If the user successfully clicked this button, this means the
        // MoveUp button can work. Still, if the selected item is not
        // the most top item, then enable the MoveUp button
        if(CurItem >= 0)
            btnMoveUp->Enabled = True;
    
        int HowMany = lstSelected->Items->Count - 1;
        if(CurItem == HowMany - 1)
        {
            btnMoveDown->Enabled = False;
            lstSelected->ItemIndex = HowMany;
        }
        else
        {
            // Swap the selected item to tye one above it
            lstSelected->Items->Move(CurItem, CurItem + 1);
            // Follow the item that was selected
            lstSelected->ItemIndex = CurItem + 1;
        }
    }
    //---------------------------------------------------------------------------
  74. Again, we need to decide when and where these buttons should be enabled or not. Remember that when the user has selected all cities and added them to the Selected list, a city should be selected. Therefore, it becomes time to enable the move buttons.
    At the bottom of the OnClick event of the SelectAll button, add the following:
     
    // Since some items have been added to the Selected list,
        // enable the buttons used to remove items from the Selected list
        btnRemove->Enabled = True;
        btnRemoveAll->Enabled = True;
    
        // Also enable the buttons used to move items of the Selected list
        btnMoveUp->Enabled = True;
        btnMoveDown->Enabled = True;
    }
    //---------------------------------------------------------------------------
  75. Save your project and test it

 

Previous Copyright © 2004-2007 Yevol Next