Pagina1 van 1

Lazarus – Simpel informatie aan lijsten toevoegen met AddObject

Lazarus – Simpel informatie aan lijsten toevoegen met AddObject
   1

Sommige componenten in Lazarus Pascal (en Delphi), en dan vooral die componenten die met lijsten van strings werken, staan het vaak toe om aanvullende informatie toe te voegen aan de individuele regels door middel van objecten. Met name de functie “AddObject” is hier uitstekend voor, maar wordt helaas vaak over het hoofd gezien.

Met deze optie kun je dus meer informatie aan een regel in een lijsten koppelen, die niet meteen zichtbaar is in de lijst.

In dit korte artikel, laat ik je zien hoe je snel en eenvoudig meer informatie kunt toevoegen, zonder veel extra werk.




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.

 

Het Toevoegen van Extra Informatie (data)

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:

  1. Maak een Object aan en initialiseer het
  2. Voeg het Object en de String toe aan de lijst met AddObject of InsertObject
  3. Lees de waarden van jouw objecten met de “objects” property
  4. 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):


var newItem : 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.

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:


1
MyListBox.Clear;

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;    

...

 

Ondersteun ons ...


Jouw ondersteuning wordt zeer gewaardeerd, en hoeft zelfs niets te kosten. Bijvoorbeeld door links naar ons te delen op social media, of andere websites.

Andere vormen kunnen ook gratis zijn (b.v. shoppen op Amazon).
Alle opbrengsten worden gebruikt voor web-hosting kosten, project hardware en software, koffie, etc.

Hartelijk dank voor wie al heeft bijgedragen!
Het is altijd geweldig om te zien hoe men mijn artikeltjes en applicaties weet te waarderen.

Merk op dat het klikken op affiliate links een kleine commissie voor ons kunnen genereren - dit wordt zeer gewaardeerd.

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.



Jouw Opmerking ...

Plaats hier geen grote bestanden (zoals source codes, log files of config files). Gebruik hiervoor het Forum.

Delen:
*
*
Laat me per email weten als er nieuwe reacties zijn.
       Je kunt jouw RSS reader gebruiken om reacties te volgen.


Tweaking4All gebruikt de gratis Gravatar dienst voor Avatar weergave.