1. Computing

Store a String (or an Object) Along with a String in a ListBox or ComboBox

Understanding TStrings.AddObject method

By

Delphi's TListBox and TComboBox display a list of items - strings in a "selectable" list. TListBox displays a scrollable list, the TComboBox displays a drop down list.

A common property to all the above controls is the Items property. Items defines a list of strings that will appear in the control to the user. At design-time, when you double click the Items property, the "String List Editor" let's you specify string items. The Items property is actually a TStrings type descendant.

Two Strings Per Item in a ListBox?

There are situations when you want to display a list of strings to the user, for example in the list box control, but also have a way to store one more additional string along the one displayed to the user.

What's more, you might want to store / attach more than just a "plain" string to the string, you might want to attach an object to the item (string).

ListBox.Items - TStrings "knows" Objects!

Give the TStrings object one more look in the Help system. There's the Objects property which represents a set of objects that are associated with each of the strings in the Strings property - where the Strings property references the actual strings in the list.

If you want to assign a second string (or an object) to every string in the list box, you need to populate the Items property at run-time.

While you can use the ListBox.Items.Add method to add strings to the list, to associate an object to each string, you will need to use another approach.

The ListBox.Items.AddObject method accepts two parameters. The first parameter, "Item" is the text of the item. The second parameter, "AObject" is the object associated with the item.

Note that list box exposes the AddItem method which does the same as Items.AddObject.

Two Strings for One String, please...

Since both Items.AddObject and AddItem accept a variable of type TObject for their second parameter, a line like:
 //compile error!
 ListBox1.Items.AddObject('zarko', 'gajic');
 
will result in a compile error: E2010 Incompatible types: 'TObject' and 'string'.

You cannot simply supply a string for the object, since in Delphi for Win32 string values are not objects.

To assign a second string to the list box item, you need to "transform" a string variable into an object - you need a custom TString object.

An Integer for a String, please...

If the second value you need to store along with the string item is an integer value, you actually do not need a custom TInteger class.
 ListBox1.AddItem('Zarko Gajic', TObject(1973)) ;
 
The line above stores the integer number "1973" along the added "Zarko Gajic" string.

Now this is tricky :)
A direct type cast from an integer to an object is made above. The "AObject" parameter is actually the 4 byte pointer (address) of the object added. Since in Win32 an integer occupies 4 bytes - such a hard cast is possible.

To get back the integer associated with the string, you need to cast the "object" back to the integer value:

 //year == 1973
 year := Integer(ListBox1.Items.Objects[ListBox1.Items.IndexOf('Zarko Gajic')]) ;
 

A Delphi Control for a String, please...

Why stop here? Assigning strings and integers to a string in a list box is, as you just experienced, a piece of cake.

Since Delphi controls are actually objects, you can attach a control to every string displayed in the list box.

The following code adds to the ListBox1 (list box) captions of all the TButton controls on a form (place this in the form's OnCreate event handler) along with the reference to each button.

 var
   idx : integer;
 begin
   for idx := 0 to -1 + ComponentCount do
   begin
     if Components[idx] is TButton then ListBox1.AddObject(TButton(Components[idx]).Caption, Components[idx]) ;
   end;
 end;
 
To programmatically *click* the "second" button, you can use the next statement:
 TButton(ListBox1.Items.Objects[1]).Click;
 

I want to Assign my Custom Objects to the String Item!

In a more generic situation you would add instances (objects) of your own custom classes:
 type
   TStudent = class
   private
     fName: string;
     fYear: integer;
   public
     property Name : string read fName;
     property Year : integer read fYear;
     constructor Create(const name : string; const year : integer) ;
   end;
 
 ........
 
 constructor TStudent.Create(const name : string; const year : integer) ;
 begin
   fName := name;
   fYear := year;
 end;
 
 --------
 begin
   //add two string/objects -> students to the list
   ListBox1.AddItem('John', TStudent.Create('John', 1970)) ;
   ListBox1.AddItem('Jack', TStudent.Create('Jack', 1982)) ;

   //grab the first student - John
   student := ListBox1.Items.Objects[0] as TStudent;
 
   //display John's year
   ShowMessage(IntToStr(student.Year)) ;
 end;
 

What you Create You MUST FREE!

Here's what the Help has to say about objects in TStrings descendants: the TStrings object does not own the objects you add this way. Objects added to the TStrings object still exist even if the TStrings instance is destroyed. They must be explicitly destroyed by the application.

When you add objects to strings - objects that you create - you must make sure you free the memory occupied, or you'll have a memory leak

A generic custom procedure FreeObjects accepts a variable of type TStrings as its only parameter. FreeObjects will free any objects associated with an item in the string list In the above example, "students" (TStudent class) are attached to a string in a list box, when the application is about to be closed (main form OnDestroy event, for example), you need to free the memory occupied:

 FreeObjects(ListBox1.Items) ;
 
Note: you ONLY call this procedure when objects assigned to string items were created by you.

©2014 About.com. All rights reserved.