Page 1 of 1

Lazarus – How to add data to lists with AddObject

Lazarus – How to add data to lists with AddObject
   1

Some components in Lazarus Pascal (and Delphi), especially the ones that have a list of strings, allow you to add additional data by linking an object to a string. The function “AddObject” is often used for this, but it’s also the most overlooked option.

With this option you can link (add) any kind of data to rows in your string list, allowing you to add additional (non visual) data.

In this short article, I’ll show you how you can work with these objects, which allow you to extend the data stored with your lists.




Content Overview

The short Version

For those who have read this before, or for those that are impatient, a quick and short example which shows it all.

First create your own class (TMyClass);


  TMyClass = class
    SomeNumber: integer;
  end;

For each line in the list initialize a variabel of this class:


  newItem := TMyClass.Create;
  newItem.SomeNumber := 123;

Add it as an object to your list:


newItemIndex := ListBox1.Items.AddObject('Test text',newItem);

Access the information with typecasting:


TMyClass(ListBox1.Items.Objects[newItemIndex]).SomeNumber

Destroy the object when done with it, done before deleting a list item:


  ListBox1.Items.Objects[newItemIndex].Destroy;
  ListBox1.Items.Delete(newItemIndex);

Now all this combined in an example:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
  Grids, ExtCtrls;

type

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    ListBox1: TListBox;
    procedure Button1Click(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
  end;

  { TMyClass }

  TMyClass = class
    SomeNumber: integer;
  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject);
var newItem:TMyClass;
    newItemIndex:integer;
    Counter:integer;
begin
  // Create object
  newItem := TMyClass.Create;
  newItem.SomeNumber := 123;

  // Add object and string item to the list
  newItemIndex := ListBox1.Items.AddObject('Test text',newItem);

  // Access string and object
  ShowMessage('Text: '+ListBox1.Items[newItemIndex]);
  ShowMessage('SomeNumber: '+IntToStr( TMyClass(ListBox1.Items.Objects[newItemIndex]).SomeNumber ) );

  // Delete one line from the list
  ListBox1.Items.Objects[newItemIndex].Destroy;
  ListBox1.Items.Delete(newItemIndex);

  // Clear entire list
  for Counter:=0 to ListBox1.Items.Count-1 do
    ListBox1.Items.Objects[Counter].Destroy;
  ListBox1.Clear;
end;

end.

 

Adding Additional Information (data)

It might not happen often, but you could find yourself in a situation where you want to display, for example, a list of names. However, you’d like to “link” additional data to these individual “names”, but you do not wish the additional information to be in your “list”.

You could of course maintain a separate list or array with your additional data in it, but you’d have to keep it in sync, which can be a pain at times, causing extra work and problems.

This is exactly where the function “AddObject” and “InsertObject” and the property “Objects” come in handy … but how do we use them?
They all expect a TObject, and that’s the part that can be a little confusing at times.

Since I too have struggled with this in the past, I figured: why not write a small article about that ..?

Components that allow Adding Objects

Not every component allows you to add objects to their “elements”.

Typically, this option can be found with components that have “lists” in it, for example lists of strings.

You can think of the visual components TMemo, TStringGrid, TListBox, TComboBox en TRadioGroup.
But this works for the non-visual “components” TStrings and TStringlist as well.
In fact they all inherit this option from the “TString” component.

Save and Load String Lists … not handling Objects! 

I’m a big fan of the “SaveToFile()” and “LoadFromFile()” functions offered by these string based lists, and I use it often.

However … when saving or loading a string list, with object attached to it, the object data will not be saved or loaded with it! So you will have to create your own code to save and load the data for objects.

 

The process of adding Objects

The process, in simple steps:

  1. Create you Object
  2. Add the Object and the String to your list with AddObject or InsertObject
  3. Read the values of your Object with the “objects” property
  4. Destroy you object before you remove a string from your list

 

It’s really a simple process once you know what to pay attention to.

What Objects?

So now that we know a few components that allow adding objects, and what the process of adding and removing objects looks like, then what are those darn “objects”?

In the definition of these functions (AddObject, InsertObject) and properties (Objects), this object is defined as a TObject.
But a TObject on itself isn’t really handy to work with … it’s a “barebones” object, the basis for all other objects.

An Object is actually a simple class you can define yourself. I’ll show you examples of these.

One thing to keep in mind: The “Objects[]” property is an array of pointers to objects, where the index number matches up with the index number of the string, item or line you’ve linked it to.

 

Objects You Create, are Objects You Destroy ! 

When destroying a component to which you have attached your own objects, you will be responsible for handling the destruction of your own objects. Destroying a component will not destroy your attached objects!
So to avoid memory leaks: you will have to add code that destroys such a self-attached object!

TypeCasting an Integer as a TObject pointer – not recommended … 

I have seen some developers work with type casting, casting an integer for example as a TObject pointer, but this is not a method I can recommend. I’ve done this in the past as well, and it’s probably a bad habit …

Keep in mind that different compiler versions on different Operating Systems, might or might not use a 32 bit pointer.
Not to mention possible error messages you compiler might give.

I’m not against using code that works, but I learned it the hard way myself as well; just use a simple class that takes only a few more lines to create and do it right. I guarantee you that it will save headaches in the future.

 

How to create your own Object

To create an object, we’re going to define our own “Class” …

Now what is a “Class”?
Simply said: A class is a highly structured data type which is able to contain variables, and functions. Class functions and properties can inherit, or be inherited from, by other classes. Which can save us a lot of work …

Also note that each class needs at least one “constructor“, and just one “destructor“, but … those can be inherited.

Note: When our class inherits properties and functions from a given class, then that given class is called the “parent class“.

Note: When defining a class, without specifying a parent class, the compiler will automatically inherit from the base class “TObject“.

It sounds complicated to work with classes, but it really isn’t once you get the hang of it.

Simple Class (or: Object)

All-righty then … let’s create a simple class:


1
2
3
TMyClass = class
    SomeNumber   : integer;
  end;

This is the shortest way I could find to define a class. Since we omitted the parent class,  the compiler will use TObject as the parent class. Everything else, like the constructor and destructor, is all handled by the compiler as it’s inherited from TObject.

Our newly defined class only holds an integer (SomeNumber) and … this looks a lot like a record, but it’s far from the same!

A record created in a procedure for example, will no longer live outside of the scope of that procedure.

The created object on the other hand, will be accessible outside of the procedure (scope) it was created in.
Granted, the variable name is nowhere to be found outside of it’s scope, but the allocated memory for the object remains in tact and if you have the pointer to that location, then you can access it from anywhere in your program, until you explicitly destroy the object.
Hence the “What you create, You must destroy” comment …

Creating your Object

To use this class we have define a variable (newItem) of this class (TMyClass):


var newItem : TMyClass;

After that we need to “construct” (Create) the object, before we can use it:


newItem := TMyClass.Create;

Working with your Object

And from this point forward we can work with this object – yes, you’ve just created an object! For example:


...
newItem.SomeNumber := 123;
...
ShowMessage('SomeNumber = '+IntToStr(newItem.SomeNumber));
...
newItem.SomeNumber := newItem.SomeNumber + 10;
...

Linking our Object to our “list”

Since we are reading this article because we want to link additional data to our lists, we’d like to do just that.
For this we can use the function “AddObject” (or “InsertObject), which is really simple.

Say we have a Form and we placed a TListBox on it. The default name of this listbox would be “ListBox1“.

Normally when adding just a string to our list, we would use the “Add” function like so:


newItemIndex := ListBox1.Items.Add('Test text');

As you will probably already know, this will add the string “Test text” at the end of the list of strings stored in the “Items” property of “ListBox1”.

We can do the same thing in a single line, and additionally add our new object to it right away, with the “AddObject” function, as such:


newItemIndex := ListBox1.Items.AddObject('Test text',newItem);

Without objects, you’re used to retrieving the string as such, and even with objects, this is still valid and functional:


1
ShowMessage('Text: '+ListBox1.Items[newItemIndex]);

Now, the trick to retrieve our object works in a similar way. However, since we stored the pointer to our object as a TObject pointer, do we will need to use typecasting to make the value “SomeNumber” accessible. After all, a TObject does not have our selfmade property “SomeNumber” so we need to cast the TObject as a TMyClass.

Type casting is done as such: TMyClass(ListBox1.Items.Objects[newItemIndex])

To access the property “SomeNumber” we need:  TMyClass(ListBox1.Items.Objects[newItemIndex]).SomeNumber

Tip: You will see that code completion also works with your selfmade class!

Now let’s glue all this together, and put it under a button (TButton, named “Button1”), on the onClick event:


1
2
3
4
5
6
7
8
9
10
11
procedure TForm1.Button1Click(Sender: TObject);
var newItem:TMyClass;
    newItemIndex:integer;
begin
  newItem := TMyClass.Create;
  newItem.SomeNumber := 123;
  newItemIndex := ListBox1.Items.AddObject('Test text',newItem);

  ShowMessage('Text: '+ListBox1.Items[newItemIndex]);
  ShowMessage('SomeNumber: '+IntToStr( TMyClass(ListBox1.Items.Objects[newItemIndex]).SomeNumber ) );
end;

Destroying your Object

Keep in mind though: What YOU create, is What YOU must Destroy! So to destroy our object, before we delete a line:


1
2
  ListBox1.Items.Objects[newItemIndex].Destroy; // destroy object first
  ListBox1.Items.Delete(newItemIndex);          // remove line after that

Or before we want to clear the entire list:


1
2
3
  for Counter:=0 to ListBox1.Items.Count-1 do
    ListBox1.Items.Objects[Counter].Destroy;
  ListBox1.Clear;

Making things more complex

Now, you do not have to limit the number of properties to one single variable. You can add as many as you’d like, of any datatype you’d like.

Just an example:


1
2
3
4
5
6
7
  TPerson = class
    DataOfBirth: TDateTime;
    Age: integer;
    Gender: string;
    Address: string;
    Phone: string;
  end;

You can even add functions to this, for example for calculations, and expand with more than one constructor, for example to enter some default values under certain conditions. Working with functions in this setup is a little outside of the scope of what I’d like to describe here, so I’ll leave that for you to play with.

Examples with several Components

We’ve looked at an example using a TListBox, but as mentioned before, there are other components that allow adding objects.

I’ll list a few examples below, but there are more components that support adding objects.
Most of them work the same (ListBox, ComboBox, RadioGroup), some have a slightly different name (TMemo uses “lines” whereas TListBox uses “items”), and a StringGrid requires you to provide a Column and Row number and cannot set the text and add an object at the same time.

Non visual classes like TStrings and TStringList work in the same way.

Adding an Object

Base example: ListBox1.Items.AddObject('Test text',newItem);

Examples of Adding Objects
 Component  Code to add an Object
 TMemo  newItemIndex := Memo1.Lines.AddObject('Test text',newItem);
 TComboBox  newItemIndex := ComboBox1.Items.AddObject('Test text',newItem);
 TStringGrid  StringGrid1.Cells[1, 2]:='Test text';
StringGrid1.Objects[1, 2]:=newItem;
 TRadioGroup  RadioGroup1.Items.AddObject('Test text',newItem);

Accessing an Object

Base example: TMyClass(ListBox1.Items.Objects[newItemIndex]).SomeNumber

Tip: Check if an Object exists 

If you want to make sure an object exists, then you can verify that as follows.

if Memo1.Lines.Objects[newItemIndex]<>nil then

Examples of Accessing Objects
 Component  Code to access an Object
 TMemo  TMyClass(Memo1.Lines.Objects[newItemIndex]).SomeNumber
 TComboBox  TMyClass(ComboBox1.Items.Objects[newItemIndex]).SomeNumber
 TStringGrid  TMyClass(StringGrid1.Objects[1, 2]).SomeNumber
 TRadioGroup  TMyClass(RadioGroup1.Items.Objects[newItemIndex]).SomeNumber

Destroying an Object

Base example: ListBox1.Items.Objects[newItemIndex].Destroy;

Examples of Destroying Objects
 Component  Code to destroy an Object
 TMemo  Memo1.Lines.Objects[newItemIndex].Destroy;
 TComboBox  ComboBox1.Items.Objects[newItemIndex].Destroy;
 TStringGrid  StringGrid1.Objects[1, 2].Destroy;
 TRadioGroup  RadioGroup1.Items.Objects[newItemIndex].Destroy;

Donation options


Donations are very much appreciated, but not required. Donations will be used for web-hosting expenses, project hardware or a motivational boost (a drink or snack). Thank you very much for those have donated already! It's truly AwEsOmE to see that folks like our articles and small applications.

Comments


There is only one comment, you can read it below.
You can post your own comments by using the form below, or reply to existing comments by using the "Reply" button.



Your Comment …

Friendly request to not post large files here (like source codes, log files or config files). Please use the Forum for that purpose.

Please share:
*
*
Notify me about new comments (email).
       You can also use your RSS reader to track comments.


Tweaking4All uses the free Gravatar service for Avatar display.
Tweaking4All will never share your email address with others.