Overzicht
De Korte Versie
Voor wie dit artikel al eens gelezen heeft, of voor wie gewoon ongeduldig is, hier een voorbeeld die alle stappen laat zien.
Eerst maken we onze eigen class (TMyClass);
TMyClass = class
SomeNumber: integer;
end;
Voor elke regel in de lijst maken we een object en initialiseren we deze:
newItem := TMyClass.Create;
newItem.SomeNumber := 123;
En vervolgens hangen we dit object aan betreffende regel in de lijst:
newItemIndex := ListBox1.Items.AddObject('Test text',newItem);
Toegang tot het object doen we met typecasting:
TMyClass(ListBox1.Items.Objects[newItemIndex]).SomeNumber
En als we klaar zijn met het gebruik van het object, voor we dus een regel uit de lijst verwijderen, verwijderen we het object:
ListBox1.Items.Objects[newItemIndex].Destroy;
ListBox1.Items.Delete(newItemIndex);
En dit alles bij elkaar in een voorbeeld:
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 Gedetecteerd Vriendelijk verzoek om te overwegen Ad Blocking uit te zetten voor onze website.
We zijn afhankelijk van inkomen uit Advertenties om de website te kunnen draaien.
Je kunt ons ook op andere manieren ondersteunen (zie Ondersteun ons links bovenin).
Het kan zijn dat je dit nog nooit nodig had, of gebruikt hebt, maar ik ben er al vaker tegen aan gelopen. Je hebt een lijst, bijvoorbeeld een lijst met namen, en je wilt graag extra informatie opslaan bij de namen. Bijvoorbeeld een ID of geboorte datum, elke specifiek voor een item in de lijst, maar je wilt deze informatie niet zichtbaar hebben maar wel toegnakelijk hebben.
Je zou natuurlijk een aparte lijst bij kunnen gaan houden, maar ja, die moet je dan wel continue in-sync houden met de lijst met namen, wat vaak extra werk levert en de nodige problemen met zich mee brengt.
Dit is precies waarvoor de functies “AddObject” en “InsertObject” en de property “Objects” bestaan, maar hoe gebruik je ze?
Ze verwachten allen een TObject, en dat is precies waar het in het begin nog weleens verwarrend kan zijn.
Omdat ik er ook af-en-toe mee loop te klooien, leek het een goed idee om het e.e.a. eens uit te schrijven, zodat ook anderen hier lol van kunnen hebben.
Componenten die het Toevoegen van Objecten toestaan
Niet elke component staat het toe om objecten to te voegen aan de elementen van een lijst.
Maar zo’n beetje elk component dat met “lijsten” werkt, vooral als dit lijsten van strings zijn, staan dit wel toe.
Er zijn een aantal visuele componenten te bedenken, zoals TMemo, TStringGrid, TListBox, TComboBox en TRadioGroup.
Maar er zijn ook een aantal niet-visuele componenten te bedenken zoals TStrings en TStringlist.
Ze erven deze mogelijkheid namelijk van het “TString” component.
Save en Load String Lists … maar dan zonder de Objecten!
Ik gebruik zelf best vaak de functies “SaveToFile()” en “LoadFromFile()” voor lijsten die op strings gebaseerd zijn – rete handig!
Echter … wanneer je een string lijst saved of load, dan worden de gekoppelde object niet mee gesaved of geladen! Dat zul je dus zelf in code moeten oplossen als je het nodig hebt.
Het proces van het Toevoegen van Objecten
Het proces van werken met deze objecten is eenvoudig:
- Maak een Object aan en initialiseer het
- Voeg het Object en de String toe aan de lijst met AddObject of InsertObject
- Lees de waarden van jouw objecten met de “objects” property
- Destroy een object voor je een item uit de lijst verwijderd
Het is een simpel proces, als je eenmaal weet hoe en wat.
Wat voor Objecten?
We weten nu dat een aantal componenten, objecten kan koppelen aan strings, en welke stappen we ruw-weg moeten doorlopen om er mee te werken. Maar wat zijn nou die maffe “Objecten”?
In de definitie van deze functies (AddObject, InsertObject) en properties (Objects), zie je steeds: TObject.
Maar een TObject opzichzelf is niet erg zinvol, het is maar een skelet voor alle objecten die in Lazarus gebruikt worden.
Een Object is eigenlijk een class, die je geheel naar smaak, zelf kunt aan maken. In de volgende voorbeelden laat ik je zien hoe simpel het is om er mee aan de slag te gaan.
Even net vergeten: De “Objects[]” property is een array van pointers naar objecten, waarbij het index nummer overeenkomt met het index nummer van de string waaraan deze gekoppeld is.
Objecten die JIJ Aanmaakt, zijn Objecten die JIJ moet Opruimen (Destroy) !
Waaneer je het component vernietigt (destroy) waaraan jouw objecten hangen, dan worden jouw objecten niet automatisch ook verwijderd.
Je bent zelf verantwoordelijk voor het vernietigen van jouw objecten en als je dat niet correct doet, dan gaat jouw programma geheugen verliezen (memory leak)!
TypeCasting een Integer als een TObject pointer – niet aan te raden …
Ik heb het zelf ook in het verleden gedaan en ik zie een groot aantal ontwikkelaars het nog steeds doen: Een integer typecasten naar een TObject. Het werkt misschien wel, maar het is niet helemaal de juiste methode …
Je moet bedenken dat verschillende compiler versie of verschillend platformen misschien wel, of misschien niet 32 bits gebruiken voor de opslag van een pointer. Om maar te zwijgen van eventuele foutmeldingen die wel of niet verschijnen.
Ik ben niet tegen code gebruiken die werkt, ook al is het niet 100% volgens de regels. Maar omdat de juiste methode zo eenvoudig is, kun je het beter gewoon correct doen. Het bespaard gegarandeerd ellende in de toekomst.
Hoe maak je jouw eigen Object
Om ons eigen object te maken, moeten we een zogenaamde “Class” definiëren …
En wat is nou een “Class”?
Simpel gezegd: Een class is een zeer gestructureerd data type, welke zowel variabelen (properties) als functies kan bevatten. Een class kan daarnaast functies en properties erven van andere classen, en andere classen kunnen die weer van jouw class erven – wat uiteindelijk veel werk bespaard en het leven net even makkelijk maakt …
Let wel op, dat iedere class minstens èèn “constructor” (om te initialiseren), en maar een enkele “destructor” (vernietigen) nodig heeft, maar … die kunnen we erven van de basis class.
N.b.: Als onze class funties of properties overerft, dan noemt men de class waarvan met erft de “parent class“.
N.b.: Als je geen “parent class” opgeeft bij de definitie, dan gebruikt de compiler automatisch “TObject“.
Het klinkt ingewikkeld, maar als je eenmaal ziet hoe het werkt, dan is het dat zeker niet.
Simpele Class (of: Object)
OK, laten we eens een erg simpele class maken:
1 2 3
| TMyClass = class
SomeNumber : integer;
end; |
Dit si de kortste methode die ik kon vinden om een simpele class te definiëren. Omdat we geen parent class hebben opgegeven, zal de compiler dus TObject gaan gebruiken als de parent class. Everything else, like the constructor and destructor, is all handled by the compiler, omdat het ge-erft wordt van TObject.
Onze nieuwe class kan maar weinig natuurlijk, en in dit geval kan het alleen maar een integer (SomeNumber) bevatten …
Je zult zien dat dit veel lijkt op een record, maar dat is verre van hetzelfde!
Een record, b.v. aangemaakt in een procedure, bestaat niet [meer] buiten de scope van de procedure.
Een object echter, blijft bestaan buiten de procedure (scope) waarin het aangemaakt was.
OK, de variabele naam is wieberen, maar het geheugen blok voor het object blijft in takt, en kan (als je de pointer hebt) gewoon benaderd worden van waar dan ook in jouw programma. Dit blijft zo tot je het object expliciet vernietigt, vandaar dus de opmerking “Objecten die JIJ maakt, moet JIJ ook vernietigen” …
Create (initialiseren)
Om onze class te kunnen gebruiken moeten we een variabele definiëren (newItem) van deze class (TMyClass):
Vervolgens moeten we dit nieuwe object (class) initialiseren:
newItem := TMyClass.Create;
Werken met jouw Object
Na deze eerste 2 stappen, kunnen we aan de slag met dit object – yep, we hebben zo-even een object gemaakt! Als voorbeeld:
...
newItem.SomeNumber := 123;
...
ShowMessage('SomeNumber = '+IntToStr(newItem.SomeNumber));
...
newItem.SomeNumber := newItem.SomeNumber + 10;
...
Object koppelen aan onze “lijst”
We lezen dit artikel natuurlijk omdat we extra informatie aan onze lijst elementen willen koppelen, dus dat gaan we nu doen.
We kunnen hiervoor de functie “AddObject” (of “InsertObject), gebruiken en dat is gelukkig envoudig.
Stel we hebben een Form en we hebben daarop een TListBox geplaatst. De standaard naam voor de listbox is “ListBox1“.
Als we normaal een string gaan toevoegen, dan gebruiken we de “Add” functie:
newItemIndex := ListBox1.Items.Add('Test text');
Zoals je waarschijnlijk weet, voegt dit de string “Test text” aan het einde van de lijst toe, en staat dan in de “Items” property van “ListBox1”.
We kunnen hetzelfde doen met “AddObject“, waarbij we zowel de tekst als het object kunnen toevoegen in een enkel statement:
newItemIndex := ListBox1.Items.AddObject('Test text',newItem);
Zonder objecten te gebruiken, kun je de betreffende tekst benaderen met de volgende code, en ook met objecten werkt dit nog steeds:
1
| ShowMessage('Text: '+ListBox1.Items[newItemIndex]); |
Het ophalen van ons object, en de informatie in ons object, werkt op een vergelijkbare manier. Echter … omdat de lijst een pointer naar een TObject hanteerd, zullen we eerst een zogenaamde “typecast” moeten doen zodat we bij “SomeNumber” kunnen komen. Een TObject heeft namelijk de “SomeNumber” niet, dus moeten we een typcast doen waarbij de compiler “weet” dat het een TMyClass betreft en niet een TObject.
Type casting wordt als volgt gedaan: TMyClass(ListBox1.Items.Objects[newItemIndex])
Om nu “SomeNumber” te benaderen gebruiken we: TMyClass(ListBox1.Items.Objects[newItemIndex]).SomeNumber
Tip: Je zult zien dat code completion ook werkt voor jouw eigen gemaakte class!
Nu even alles bij elkaar onder een Button geplakt (TButton, met de naam “Button1”), onder het 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; |
Vernietigen van jouw Object (Destroy)
Niet vergeten he: Objecten die JIJ maakt, moet JIJ ook vernietigen! Dus vernietig jouw object voor je een regel verwijderd:
1 2
| ListBox1.Items.Objects[newItemIndex].Destroy; // destroy object first
ListBox1.Items.Delete(newItemIndex); // remove line after that |
Of voor je de hele lijst leeg maakt:
1 2 3
| for Counter:=0 to ListBox1.Items.Count-1 do
ListBox1.Items.Objects[Counter].Destroy;
ListBox1.Clear; |
Complexere Object
Ons voorbeeld is maar een beetje miserabel, met een enkele integer. We kunnen zoveel en zo gek als we willen properties toevoegen, van verschillende data typen.
Een voorbeeld:
1 2 3 4 5 6 7
| TPerson = class
DataOfBirth: TDateTime;
Age: integer;
Gender: string;
Address: string;
Phone: string;
end; |
Je kunt zelfs functies toevoegen die berekeningen uitvoeren, of handelingen verrichten. Dit gaat echter een beetje buiten de scope van dit artikel, dus ik laat je daar zelf in duiken als je het nodig hebt.
Ad Blocking Gedetecteerd Vriendelijk verzoek om te overwegen Ad Blocking uit te zetten voor onze website.
We zijn afhankelijk van inkomen uit Advertenties om de website te kunnen draaien.
Je kunt ons ook op andere manieren ondersteunen (zie Ondersteun ons links bovenin).
Voorbeelden met verschillende Componenten
Ons voorbeeld was gebaseerd op een TListBox, maar zoals eerder gezegd, er zijn meerder componenten die dit ondersteunen.
Ik laat hieronder een aantal code regels zien voor een paar andere componenten.
Ze werken vaak hetzelfde (ListBox, ComboBox, RadioGroup), hebben soms een net andere naam (TMemo gebruikt “lines” waar de TListBox de term “items” gebruikt), en als voorbeeld een StringGrid waarbij je een Kolom en Regel nummer nodig hebt, en het toevoegen van tekst en object niet in een enkel statement gaat.
Niet zichtbare classen zoals TStrings en TStringList werken op dezelfde manier overigens.
Object Toevoegen
Basis voorbeeld: ListBox1.Items.AddObject('Test text',newItem);
Voorbeelden van Objecten Toevoegen
Component |
Code om een Object toe te voegen |
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); |
Benaderen van een Object
Basis voorbeeld: TMyClass(ListBox1.Items.Objects[newItemIndex]).SomeNumber
Tip: Kijk eerst of een Object wel bestaat
Als je zeker wilt zijn dat alles goed gaat, dan kan het zinvol zijn om te kijken of een object wel bestaat.
B.v. als volgt:
if Memo1.Lines.Objects[newItemIndex]<>nil then
Voorbeelden van benaderen van Object
Component |
Code voor het benaderen van een 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 |
Vernietigen van een Object
Basis voorbeeld: ListBox1.Items.Objects[newItemIndex].Destroy;
Voorbeelden van vernietigen van een Object
Component |
Code om een Object te vernietigen |
TMemo |
Memo1.Lines.Objects[newItemIndex].Destroy; |
TComboBox |
ComboBox1.Items.Objects[newItemIndex].Destroy; |
TStringGrid |
StringGrid1.Objects[1, 2].Destroy; |
TRadioGroup |
RadioGroup1.Items.Objects[newItemIndex].Destroy; |
Simpel Voorbeeld hoe we dit kunnen doen met minimale impact op jouw Code
Hier een voorbeeld hoe je het e.e.a. kunt doen zonder veel impact op bestaande code.
In dit voorbeeld voegen we een int64 toe als een object.
Definitie van een 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. |
Toevoegen als Object
In dit voorbeeld, kunnen we dankzij de constructor, een object in 1 regel toevoegen aan b.v. een TListbox:
1
| MyListBox.Items.AddObject('Some text here',TMyLargeNumber.Create(123456)); |
Teruglezen van de waarde in de Class
1
| SomeInt64 := TMyLargeNumber(MyListBox.Items.Objects[SomeIndex]).LargeNumber; |
Opruimen na gebruik
In het TListBox voorbeeld, moeten we het object wel opruimen na gebruik, en voor een enkel object is dat eenvoudig:
1
| TMyLargeNumber(MyListBox.Items.Objects[SomeIndex]).Destroy; |
Mocht je de hele lijst leeg willen maken, waar je voorheer zoiets gebruikte:
Gebruiken we nu:
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;
... |
Reacties
Er is slechts èèn reacie geplaatst welke je hieronder kunt lezen.
Je kunt jouw eigen opmerkingen plaatsen m.b.v. dit formulier, of een reactie op een bestaande opmerking plaatsen door op de "Beantwoorden" knop te klikken.
UPDATE:
Voorbeeld toegevoegd welke laat zien dat de impact op code echt minimaal kan zijn.
hans