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. |
Ad Blocking Detected Please consider disabling your ad blocker for our website.
We rely on these ads to be able to run our website.
You can of course support us in other ways (see Support Us on the left).
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:
- Create you Object
- Add the Object and the String to your list with AddObject or InsertObject
- Read the values of your Object with the “objects” property
- 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):
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.
Ad Blocking Detected Please consider disabling your ad blocker for our website.
We rely on these ads to be able to run our website.
You can of course support us in other ways (see Support Us on the left).
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; |
Example how things can be done with minimal Code impact
This is just an addition to this article to show a quick and maybe dirty trick to assign for example an int64 as an object:
Defining your Class
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
| unit ...
interface
uses ...
{ TMyLargeNumber }
TMyLargeNumber = class
LargeNumber:int64;
constructor Create(aLargeNumber:int64); overload;
end;
...
implementation
...
constructor TMyLargeNumber.Create(aLargeNumber:int64); overload;
begin
Create;
LargeNumber := aLargeNumber;
end;
...
end. |
Adding As an Object
In this example, adding in one line the large number (123456) to a TListbox:
1
| MyListBox.Items.AddObject('Some text here',TMyLargeNumber.Create(123456)); |
Reading the Value back at a later time
1
| SomeInt64 := TMyLargeNumber(MyListBox.Items.Objects[SomeIndex]).LargeNumber; |
Cleanup After use
We have to clean up once we remove the item from (for example) the said TListBox, and in a simple line this would be:
1
| TMyLargeNumber(MyListBox.Items.Objects[SomeIndex]).Destroy; |
Or we can clear the entire list at once, where you’d once upon a time would have used
We now use:
1 2 3 4 5 6 7 8 9 10 11 12 13
| var
Counter:integer;
begin
...
for Counter:=0 to MyListBox.Count-1 do
if Assigned(MyListBox.Items.Objects[Counter]) then
TMyLargeNumber(MyListBox.Items.Objects[Counter]).Destroy;
MyListBox.Clear;
... |
Comments
There are 3 comments. You can read them below.
You can post your own comments by using the form below, or reply to existing comments by using the "Reply" button.
After a few hours (approximately 3, time flies, or close to that) of searching through and reading information from websites, I finally found your website. Your presentation has proven to be, to a very new student, the clearest, most direct rendition of information regarding this particular feature of Lazarus programming. Other sites have been so tangled up in their experiential depth, that they could not bring themselves to appreciate the “beginning” woods that surround and, seemingly, engulf the birthing process of a newcomer. If it had been an effort regarding how to climb a mountain, they had a tendency to give examples at a preliminary stage of walking, with “walking” terminology spread robustly throughout the discussion, not realizing that the newly arrived baby had not yet even learned how to roll over or lift its head above the blankets, if blankets could be had. Thank you for having taken it upon yourself to approach the topic in your clear, concise manner. If I weren’t so poor, I would immediately donate to your cause. When the pennies again find me to be among their friends, I will definitely send some of that good fortune your way.
Britt Warren
Hi Britt!
I’m very happy to hear that this article has been useful to you, and appreciate your kind words!
I can relate to the issues one may have with the way certain “other” articles have been written, one of the reasons why I wrote this article.
No worries about donations – this thank-you note is more than enough “payment”.
Feel free to share the link to this article with others.
hans
UPDATE:
I’ve added a super simple and quick section for those wanting to add (in this example) a int64 as an Object.
Obviously this doesn’t need to be an int64, you can use whatever you’d like to use.
It does illustrate that the impact on code doesn’t need to be significant.
hans