You are on page 1of 12

Start Forum Nieuws Artikelen Links Nieuwsbrief NLDelphi?

E-mail Statistieken PasConvert Chat

TbigInifileDataset
Geplaatst door GolezTrol op 07-10-03

Jos Visser

Hoewel er veel kritiek is op het gebruik van inifiles als database, merk ik uit de praktijk dat er vrij vaak voor
deze oplossing wordt gekozen als het om een beperkt aantal gegevens gaat. Het grote voordeel is namelijk
dat inifiles klein en makkelijk onderhoudbaar zijn (zonder aparte tools) en er geen drivers op het systeem
geïnstalleerd hoeven te worden. Het is alleen jammer dat de migratie van inifiles naar een 'echte' database
vrij lastig is.

Als oplossing hiervoor heb ik een dataset afgeleide gemaakt die inifiles kan lezen. Het maken van deze
dataset heeft de nodige moeite gekost aangezien TDataset, en dan met name de interne struktuur ervan
vrijwel niet is gedocumenteerd. Veel van de open source datasets zijn slecht gedocumenteerd of hebben
zoveel extra code voor het verwerken van de data, dat nauwelijks te zien is wat er nu daadwerkelijk bij de
dataset hoort. Door de kleine hoeveelheid code die nodig is voor het lezen uit inifiles hoop ik dat dit artikel
duidelijk kan laten zien hoe een dataset nu eigenlijk in elkaar zit.

Voor we beginnen..

Ik ga er voor het artikel vanuit dat de lezer een zekere basiskennis heeft in het programmeren met Delphi en
enigszins bekend is met het gebruik van datasets en inifiles. Mijn component is dan wel toevallig een
component, maar verder is daar in de implementatie weinig van te merken. Alle specifieke features van een
component zijn al opgenomen in de TDataset basisklasse. Hier heb ik niets aan toe te voegen.
Voor meer informatie over het gebruik van inifiles verwijs ik graag naar het artikel "Het gebruik van ini-files"
van Leon van Meijl.
Voor een goede basisuitleg over het maken van componenten kun je het artikel "Zelf een component
bouwen" van Matthijs lezen.

De meeste code is opgenomen in het artikel, maar sommige eenvoudige methods zijn alleen in de tekst
beschreven. De hele code is sowieso beschikbaar, dus je hoeft de gaten niet zelf te dichten. Ik heb voor het
overzicht besloten dat ik met de code voorbeelden beter kan focussen op de echte inhoud dan op wat kleine
bijdingetjes.

Ik gebruik in dit artikel de termen 'database' en 'tabel' door elkaar. In feite is een database een verzameling
tabellen, aldanniet met onderlinge relaties, maar in dit voorbeeld gaat het toch om een enkele tabel en
vervaagt dat onderscheid dus een beetje.

Ik heb nog geen methods geïmplementeerd voor het bewerken van de data. Deze dataset is dus read-only.

TDataset

TDataset werkt doorgaans samen met TDataSource. TDataSource is een component dat een lijst data
controls (zoals TDBGrid of TDBEdit) bijhoudt en deze voorziet van gegevens. TDataSource is dus de
tussenpersoon tussen TDataset en de data controls. TDataset is een basisklasse voor een component dat
specifieke data (bijvoorbeeld uit een database) vertaalt naar een standaard notatie en is zodoende dus in
feite weer de tussenpersoon tussen de data (in de database) en de TDataSource.
De data controls en TDataSource zijn in feite af. Hier hoeft niets aan te gebeuren. TDataset is een abstracte
klasse. Dat wil zeggen dat een aantal methods wel zijn gedeclareerd, maar nog geen implementatie hebben.
Deze implementatie moet worden gedaan in een afgeleide van TDataset. Er zijn al veel van deze afgeleiden
te vinden. In Delphi vind je bijvoorbeeld al TTable en TQuery. Ook zijn er veel third party datasets te vinden.
Veel van deze datasets koppelen bijvoorbeeld naar ODBC, naar Outlook of misschien naar een andere voor
de hand liggende database, maar ik ga in mijn TDataset afgeleide koppelen naar een inifile. Ik wil dat elke
sectie in mijn inifile als een record beschouwen. De items dienen als velden. Eén van de secties bevat de
velddefinities. Wanneer de implementatie af is, dan is mijn dataset in staat deze gegevens te leveren aan een
datasource.

Nader bekeken

Een tabel bestaat uit rijen ofwel records. Elk record bestaat uit velden. Een dataset heeft velddefinities
(fielddefs) waarin per veld staat aangegeven wat de naam van het veld is en welk type data erin staat. Het
werken met velden is geregeld met TField objecten. Een TField object werkt samen met de database om de
data op te halen of weg te schrijven in het juiste formaat en om deze waarde weer te geven. Voor elk veld
heeft de dataset een TField object en elk van deze objecten wijst naar een veld in het actieve record. Voor ik
verder ga met uitleg over de dataset-specifieke methods, ga ik eerst even een begin maken met mijn class:

TbigInifileDataset = class(TDataset)
private
FRecCount: Integer;
FRecNo: Integer;
FBufSize: Integer;
FRecords: TStringList;
FFieldDefSection: string;
FFileName: string;
FInifile: TCustomInifile;
FCached: Boolean;

Je ziet hier dat ik een object afleid van TDataset en een aantal private fields declareer. Deze private fields
beschrijven respectievelijk het aantal records, het huidige record, de grootte van een record buffer, een lijst
met de sectienamen, de naam van de sectie die de velddefinities bevat, de bestandsnaam van de inifile en
de keuze om de inifile te 'cachen'. Dit laatste wil zeggen dat de inifile geheel wordt ingelezen in het
geheugen. Dit heeft als nadeel dat je niet met 'live' data werkt. Iemand zou dus de inifile kunnen aanpassen
zonder dat je dit merkt. Het voordeel is dat het vele malen sneller is. Zelfs met enkele records is een non-
cached inifile al heel traag.

Het cachen van een inifile is trouwens heel eenvoudig. Delphi heeft de TMemInifile class die de hele inifile in
één keer inlaadt. Verder werkt TMemInifiles exact als een TIniFile. Zowel TIniFile als TMemInifile zijn afgeleid
van TCustomInifile. Ik kan voor de interactie met de inifile ook gebruik maken van de methods die zijn
geïntroduceerd door TCustomInifile. Daarom is FInifile ook van dit type.

NB
Delphi voorziet ook in een TRegistryInifile. Dit is een wrapper rond de Windows registry die ook is afgeleid
van TCustomInifile. Deze zou later ook toegevoegd kunnen worden om uit de registry te lezen zonder al
teveel aanpassingen te doen aan de TbigInifileDataset.

Records bijhouden

TDataset houdt een korte lijst bij van record pointers. Hierin moet voldoende data staan om het record mee
terug te vinden. Hoe deze data eruit ziet mag je zelf weten. De gegevens van een (tabel)record kunnen we
opslaan in het volgende (Delphi)record:

type
TRecInfo = record
RecordNo: integer;
BookmarkFlag: TBookmarkFlag;
end;
PRecInfo = ^TRecInfo;

Het belangrijkste gegeven hierin is RecordNo. Hierin wordt het nummer van het record opgeslagen. Dit is de
index van het record in de FRecords stringlist. De bookmark is voor het onthouden van een positie zodat
daar later naar kan worden teruggesprongen. Hier kom ik later op terug. Voor het werken met record pointers
heeft TDataset de volgende methods bedacht die ik ga overriden:

protected
function AllocRecordBuffer: PChar; override;
procedure FreeRecordBuffer(var Buffer: PChar); override;
procedure InternalInitRecord(Buffer: PChar); override;
function GetRecord(Buffer: PChar; GetMode: TGetMode; DoCheck:
Boolean): TGetResult; override;
function GetRecordSize: Word; override;

Ik was zelf behoorlijk in de war, want ik dacht in eerste instantie dat buffer groot genoeg moest zijn voor het
hele record en dat alle data van het record dus op een specifieke manier in die pointer moest worden
ingevuld. Dit bleek niet waar te zijn. Op een rijtje:
AllocRecordBuffer reserveert een stukje geheugen waar we record informatie (in dit geval TRecInfo) in
kunnen zetten om het record mee terug te kunnen vinden.
FreeRecordBuffer geeft dit geheugen weer vrij.
InternalInitRecord initialiseert het geheugen door de buffer te vullen met null chars.
GetRecord vult de buffer met info over het betreffende record. De 'GetMode' parameter bepaalt of dit het
huidige record is, of dat we een 'prior' of 'next' actie moeten doen om het record voor of na het huidige record
te kiezen.
GetRecordSize geeft FBufSize terug. Deze wordt in de constructor geïnitialiseerd op SizeOf(TRecInfo).

function TbigInifileDataset.AllocRecordBuffer: PChar;


begin
GetMem(Result, FBufSize);
end;

procedure TbigInifileDataset.FreeRecordBuffer( var Buffer: PChar);


begin
FreeMem(Buffer, FBufSize);
end;

function TbigInifileDataset.GetRecord(Buffer: PChar; GetMode: TGetMode;


DoCheck: Boolean): TGetResult;
begin
Result := grOk;
case GetMode of
gmPrior: begin
if FRecNo <= 0 then
begin
Result := grBOF;
FRecNo := - 1;
end
else Dec(FRecNo);
end;
gmCurrent: begin
if (FRecNo < 0) or (FRecNo >= FRecCount) then
Result := grError;
end;
gmNext: begin
if FRecNo >= FRecCount- 1 then
begin
Result := grEOF;
end else
Inc(FRecNo);
end;
end;

if Result = grOk then


begin
with PRecInfo(Buffer)^ do
begin
RecordNo := FRecNo;
BookmarkFlag := bfCurrent;
end;
end;
end;

Je ziet hier allereerst hoe de buffer wordt gealloceerd en vrijgegeven met AllocRecordBuffer en
FreeRecordBuffer.
In GetRecord zie je hoe er door de dataset wordt gebladerd. Wanneer het huidige record wordt opgevraagd
(gmCurrent), dan wordt gekeken of er wel een huidig record is. FRecNo is dan een geldige index in de
FRecords stringlist. Valt deze buiten het bereik, dan wordt de waarde grError teruggegeven. Wanneer een
record achteruit of vooruit wordt gesprongen (gmPrior/gmNext), dan wordt de waarde grBOF (Begin Of File)
of grEOF (End Of File) teruggegeven om aan te geven dat het begin of het einde van de tabel is bereikt.
Intern is dit af te lezen aan FRecNo. Deze zal -1 zijn voor BOF of FRecCount voor EOF. Wanneer FRecNo
tussen 0 en FRecCount-1 ligt dan is dat de index van het betreffende record.
Wanneer alles goed gaat en FRecNo naar een bestaand record wijst, dan wordt de waarde grOk
teruggegeven en wordt de buffer gevuld met informatie over het huidige record.

Navigatie

Er moeten een aantal functies geïmplementeerd worden voor het navigeren tussen records. Dit zijn de
volgende functies:

procedure InternalFirst; override;


procedure InternalLast; override;
procedure InternalSetToRecord(Buffer: PChar); override;
function GetRecordCount: integer; override;
function GetRecNo: Integer; override;
procedure SetRecNo(Value: Integer); override;

GetRecordCount geeft FRecCount terug. InternalFirst, InternalLast en InternalSetToRecord springen


naar respectievelijk BOF, EOF of een opgegeven recordnummer. Dit doen ze simpelweg door FRecNo in te
stellen op -1, FRecCount, of een getal van 0 tot FRecCount-1. De 'Buffer' parameter is een pointer naar een
TRecInfo waarin het betreffende recordnummer staat zoals is te zien in de volgende code. GetRecNo en
SetRecNo vragen en zetten het huidige recordnummer, maar doen hierbij wat synchronisatie zodat de
cursorpositie blijft kloppen met de positie in bijvoorbeeld een DBGrid.

procedure TbigInifileDataset.InternalSetToRecord(Buffer: PChar);


begin
FRecNo := PRecInfo(Buffer)^.RecordNo;
end;

procedure TbigInifileDataset.SetRecNo(Value: integer);


begin
if (Value >= 0) and (Value <= FRecCount) then
begin
FRecNo := Value- 1;
ReSync([]);
end;
end;

function TbigInifileDataset.GetRecNo: integer;


begin
UpdateCursorPos;
if (FRecNo = -1) and (FRecCount > 0) then
Result := 1
else
Result := FRecNo + 1;
end;

NB
SetRecNo verlaagt het meegegeven recordnummer met 1. In GetRecNo wordt er 1 bij opgeteld. Dit is omdat
TDataset z'n records telt van 1 tot Count, maar intern tel ik van 0 tot Count-1 omdat het interne
recordnummer een index in een stringlist is.

Initialisatie

Voordat we gegevens uit kunnen lezen moeten we de dataset natuurlijk eerst kunnen openen en sluiten.
Hiervoor implementeren we de volgende methods die door TDataset zijn geïntroduceerd:

procedure TbigInifileDataset.InternalOpen;
begin
try
if Cached then
FIniFile := TMemInifile.Create(FFileName)
else
FIniFile := TIniFile.Create(FFileName);
InternalInitFieldDefs;
if DefaultFields then
CreateFields;
BindFields(True);
InternalRefresh;
except
raise
end;
end;

procedure TbigInifileDataset.InternalClose;
begin
FreeAndNil(FIniFile);
BindFields(False);
if DefaultFields then
DestroyFields;
end;
procedure TbigInifileDataset.InternalRefresh;
var
i: Integer;
begin
inherited;
// MemInifile opnieuw inladen
if Cached then
TMemInifile(FInifile).Rename(FInifile.Filename, True);

FInifile.ReadSections(FRecords);
i := FRecords.IndexOf(FFieldDefSection);
if i > -1 then
FRecords.Delete(i);
FRecCount := FRecords.Count;
FRecNo := -1;
end;

InternalOpen maakt een TInifile object aan. Afhankelijk van de Cached property wordt gekozen tussen een
TMemInifile of een TIniFile. InternalInitFieldDefs leest de velddefinities in. Als de velddefinities zijn
ingeladen worden de TField objecten gemaakt met CreateFields en geïnitialiseerd met BindFields.
Vervolgens wordt InternalRefresh aangeroepen. Deze method wordt ook aangeroepen als het programma
de Refresh method van de dataset aanroept. We maken hiervan gebruik om de records (de secties in de
inifile) opnieuw in te lezen. Als we gebruik maken van een TMemInifile dan moet eerst het bestand opnieuw
worden ingelezen. Dit zouden we kunnen doen door de MemInifile te free-en en opnieuw te createn, maar de
rename method is beter geschikt. Deze is eigenlijk gemaakt om een andere inifile in te lezen, maar we
kunnen natuurlijk ook opnieuw dezelfde inifile uitlezen.

NB
Ik maak gebruik van de FileName property van de inifile in plaats van onze eigen property. Ik wil namelijk niet
dat er ineens een andere inifile wordt gebruikt als de gebruiker (of eigenlijk de programmeur) een andere
filename in heeft gesteld. Dit mag alleen gebeuren bij het sluiten en opnieuw openen van de dataset. De
'gewone' inifile blijft tenslotte ook bij de oude file en bovendien worden bij een refresh niet de velddefinities
opnieuw ingelezen. Wanneer er dus overgeschakeld moet worden naar een andere file, of moet worden
geswitched tussen cached of niet cached, dan moet de dataset worden heropend voordat de nieuwe
instellingen worden gebruikt.

Voor het uitlezen van data alloceert het TField object een buffer die door de dataset gevuld moet worden.
Deze buffer wordt vervolgens door het TField object vertaald naar een stringweergave om te kunnen worden
weergegeven in bijvoorbeeld een TDBGrid. De indeling in de buffer wordt bepaald door het type van het veld.
Een inifile bestaat normaal alleen uit strings, maar ik wilde daar toch wat meer mee. Daarom heb ik de
FieldDefs sectie verzonnen. In deze sectie staan alle velden opgesomd en staat een beschijving van het type
achter de veldnaam. De fielddefs sectie kan er als volgt uitzien: [FieldDefs]
Naam=Text*50
Adres=Text*50
Postcode=Text*7
Plaats=Text*50
Aantal Vingers=Numeric
Geboortedatum=Date
Eindcijfer=Float

Het is belangrijk om te weten hoeveel tekens er gereserveerd moeten worden voor een waarde, en ook voor
de weergave is het interessant om te weten hoe groot een veld kan zijn. Voor een numerieke waarde is dat
wel duidelijk, maar voor tekst niet. Daarom heb ik de mogelijkheid gegeven om een maximale lengte op te
geven achter de 'Text' aanduiding. Wanneer er geen specifieke lengte wordt opgegeven, dan wordt deze
gezet op 2048. Dit is de grootte voor de buffer die door TInifile.ReadString wordt gebruikt. Een string uit een
inifile kan dus nooit langer zijn dan 2048 tekens.

De taak voor InternalInitFieldDefs is hiermee duidelijk geworden. De velddefinities in deze sectie moeten
worden geïnterpreteerd en toegevoegd aan de dataset:

procedure TbigInifileDataset.InternalInitFieldDefs;
var
NewName: string;
NewType: TFieldType;
NewSize: Integer;

function ParseFieldDef(ADef: string): Boolean;


begin
NewType := ftUnknown;
if SameText(Copy(ADef, 1, 4), 'Text') then
begin
NewType := ftString;
if Copy(ADef, 5, 1) = '*' then // Lui, scheelt een length check :)
begin
NewSize := StrToIntDef(Copy(ADef, 6, 4), MAXFIELDSIZE);
if (NewSize <= 0) or (NewSize > MAXFIELDSIZE) then
NewSize := MAXFIELDSIZE;
end;
end;
if SameText(Copy(ADef, 1, 7), 'Numeric') then
NewType := ftInteger;
if SameText(Copy(ADef, 1, 7), 'Boolean') then
NewType := ftBoolean;
if SameText(Copy(ADef, 1, 5), 'Float') then
NewType := ftFloat;
if SameText(Copy(ADef, 1, 4), 'Date') then
NewType := ftDate;
if SameText(Copy(ADef, 1, 4), 'Time') then
NewType := ftTime;

case NewType of
ftInteger, ftBoolean, ftFloat, ftDate, ftTime:
NewSize := 0;
end;
// Zonder geldige velddefinitie gaan we uit van een maxlength string.
Result := NewType <> ftUnknown;
if not Result then
begin
NewType := ftString;
NewSize := MAXFIELDSIZE;
end;
end;

var
Defs: TStringList;
i: Integer;
s: string;
begin
FieldDefs.Clear;
Defs := TStringList.Create;
try
FInifile.ReadSectionValues(FFieldDefSection, Defs);
for i := 0 to Defs.Count-1 do
begin
ParseFieldDef(Defs.Values[Defs.Names[i]]);
FieldDefs.Add(Defs.Names[i], NewType, NewSize);
end;
finally
Defs.Free;
end;
end;

Zoals je ziet worden alle items in de FieldDefs sectie geparsed. Bij text-fields wordt er ook gezocht naar een
veldlengte. Wanneer deze niet wordt gevonden dan wordt de maximumlengte van 2048 gebruikt. Deze heb ik
in de constante MAXFIELDSIZE gezet. Andere ondersteunde types zijn Numeric (Integer), Boolean, Float
(Double), Date en Time.

De naam van de sectie waarin de velddefinities staan heb ik opgeslagen in FFieldDefSection. Deze is private
en wordt in de constructor ingesteld. De naam van de sectie staat nu dus nog altijd vast, maar er kan
eenvoudig een property van gemaakt worden.

Gegevensoverdracht

Wat we dan verder nog moeten doen is de functie maken waarmee de gegevens uit kunnen worden gelezen.
TDataset declareert daarvoor de GetFieldData functie:

function GetFieldData(Field: TField; Buffer: Pointer): Boolean; override;

Ook bij deze functie dacht ik eerst dat hij anders werkte. Ik verwachtte dat Buffer de record pointer was en
dat Field het veld was dat gevuld moest worden, maar het is andersom. Het record staat namelijk al goed op
het moment van aanroep. De gegevens moeten dus uit het actieve record worden gelezen. De gegevens van
het veld dat gelezen moet worden staan in het het Field object. Buffer is de buffer waarin de data moet
worden geplaatst. Deze buffer is groot genoeg gemaakt voor het datatype van het betreffende field. Dit kan
dus een buffer zijn voor een tekst van een opgegeven aantal tekens, of een kleinere buffer voor bijvoorbeeld
een datum of een getal. Het precieze formaat waarin de data moet worden aangeleverd heb ik teruggezocht
in de DataConvert method van TDateTime. Hierin zitten een aantal functies die diverse datatypen
converteren van de ene indeling naar de andere. Hieruit heb ik af kunnen leiden hoe bijvoorbeeld een datum
in de buffer moet worden geplaatst. De code is als volgt:

function TbigInifileDataset.GetFieldData(Field: TField;


Buffer: Pointer): Boolean;
var
SourceBuffer : PChar;
RNo: Integer;
Rec, Fld: string;
TimeStamp: TTimeStamp;
begin
Result := False;

SourceBuffer := ActiveBuffer;
if SourceBuffer = nil then
Exit;

RNo := PRecInfo(SourceBuffer)^.RecordNo;
if (RNo > -1) and (RNo < FRecCount) then
begin
Rec := FRecords[RNo];
Fld := Field.FieldName;

Result := FInifile.ValueExists(Rec, Fld);

if Result then
begin
case FieldDefs.Find(Fld).DataType of
ftString:
// Gebruikmakend van de max opgegeven lengte.
StrPLCopy(Buffer, FIniFile.ReadString(Rec, Fld,
''), Field.Size);
ftInteger:
Integer(Buffer^) := FInifile.ReadInteger(Rec, Fld, - 1);
ftBoolean:
Integer(Buffer^) := Ord(FInifile.ReadBool(Rec, Fld, False));
ftFloat, ftCurrency:
Double(Buffer^) := FIniFile.ReadFloat(Rec, Fld, 0);
ftDate:
try
TimeStamp := DateTimeToTimeStamp(FIniFile.ReadDate(Rec, Fld,
0));
TDateTimeRec(Buffer^).Date := TimeStamp.Date;
except
Result := False;
end;
ftTime:
try
TimeStamp := DateTimeToTimeStamp(FIniFile.ReadTime(Rec, Fld,
0));
TDateTimeRec(Buffer^).Time := TimeStamp.Time;
except
Result := False;
end;
else
// Niet ondersteund datatype (zou niet moeten kunnen).
Result := False;
end;
end;
end;
end;

In de code zie je de volgende zaken terugkomen:

 ActiveBuffer (een functie van TDataset) geeft de record pointer van het actieve record terug, of nil
als er geen actief record is.
 Het Field object bevat informatie over de naam van het veld en het datatype.
 De functie geeft False terug om aan te geven dat er geen data beschikbaar is. Dit gebeurt wanneer
de waarde niet in de inifile staat of wanneer de data niet geconverteerd kan worden naar het
gewenste datatype.
 Strings worden afgekapt op de maximale veldlengte. Als er namelijk een langere string in de iniFile
staat, dan wordt er buiten de gealloceerde buffer geschreven wat theoretisch voor Access Violations
of andere problemen kan zorgen (al heb ik er toevalligerwijs geen problemen mee gehad voordat ik
deze check erin bouwde).
 Integer en Double kunnen gewoon getypecast worden, Date en Time moeten worden geconverteerd.
Bookmarks

De volgende methods moeten worden geïmplementeerd om met bookmarks te kunnen werken:

function GetBookmarkFlag(Buffer: PChar): TBookmarkFlag; override;


procedure SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag); override;
procedure GetBookmarkData(Buffer: PChar; Data: Pointer); override;
procedure SetBookmarkData(Buffer: PChar; Data: Pointer); override;
procedure InternalGotoBookmark(Bookmark: Pointer); override;

GetBookmarkFlag wordt gebruikt om de status (TBookmarkFlag) van de gegeven record pointer op te


vragen.
SetBookmarkFlag stelt de opgegeven bookmarkflag in, in de meegegeven record pointer.
TBookmarkFlag kan één van de volgende waarden zijn:

bfCurrent: Huidige record is een normaal geldig record


bfBOF: We staan aan het begin van de tabel. Deze waarde wordt in een nieuw record ingesteld als er met
Insert een rij wordt ingevoegd in een lege tabel.
bfEOF: We staan aan het eind van de tabel. Wordt toegepast als er met Append een record wordt
toegevoegd.
bfInsert: Wordt toegepast als er met Insert een rij wordt ingevoegd in een niet-lege tabel.

Deze bookmarkflags worden vooral gebruikt zodat de dataset weet wat hij met het huidige record aan moet.
Wanneer GetBookmarkFlag bijvoorbeeld bfBOF teruggeeft, dan wordt InternalFirst aangeroepen zodat ook
intern naar het begin van de tabel wordt gesprongen.

GetBookmarkData slaat bookmark info op waarmee het meegegeven record (Buffer is een record pointer)
mee kan worden teruggevonden.
SetBookmarkData zet de gegevens van de bookmark (in Data) terug in de record pointer (Buffer). De positie
van het record wordt dus in feite ingesteld.

Net als een record pointer is ook de bookmark data naar eigen inzicht toe te passen. Het gaat erom dat het
record ermee is terug te vinden. We zouden hiervoor dus weer de TRecInfo kunnen gebruiken, maar
aangezien we voor de bookmark zelf de BookmarkFlag niet nodig hebben, volstaat het om het nummer van
het record op te slaan. Om dit op te slaan hebben we natuurlijk wel een buffer nodig waar een integer in past.
In de constructor wordt daarom de BookmarkSize property ingesteld op SizeOf(Integer). TDataset alloceert
een buffer van deze grootte. De Data parameter bevat een pointer naar deze buffer van 4 bytes, waarin de
integer kan worden opgeslagen. De Buffer parameter is een record pointer.

InternalGotoBookmark stelt FRecNo in op het record dat is opgeslagen in de bookmark.

Het is een hoop uitleg, maar de code valt mee:

function TbigInifileDataset.GetBookmarkFlag(Buffer: PChar): TBookmarkFlag;


begin
Result := PRecInfo(Buffer).BookmarkFlag;
end;

procedure TbigInifileDataset.SetBookmarkFlag(Buffer: PChar;


Value: TBookmarkFlag);
begin
PRecInfo(Buffer).BookmarkFlag := Value;
end;
procedure TbigInifileDataset.GetBookmarkData(Buffer: PChar; Data: Pointer);
begin
Integer(Data^) := PRecInfo(Buffer).RecordNo;
end;

procedure TbigInifileDataset.SetBookmarkData(Buffer: PChar; Data: Pointer);


begin
PRecInfo(Buffer).RecordNo := Integer(Data);
end;

procedure TbigInifileDataset.InternalGotoBookmark(Bookmark: Pointer);


begin
FRecNo := Integer(Bookmark^);
end;

En verder...

Ik ben bijna door mijn stof heen. Alleen de volgende methods zijn nog niet besproken:

procedure InternalHandleException; override;


function IsCursorOpen: Boolean; override;
function GetCanModify: Boolean; override;

InternalHandleException regelt de interne foutafhandeling. Deze heb ik simpelweg doorverwezen naar


Application.HandleException(Self).
IsCursorOpen wordt gebruikt om te controleren of de dataset actief is. In mijn implementatie geeft deze True
terug als FInifile <> nil.
GetCanModify geeft terug of de data gewijzigd kan worden. Voorlopig is deze dataset nog read-only omdat
de methods die nodig zijn voor bewerkingen nog niet zijn geïmplementeerd. Daarom geeft deze functie altijd
False terug.

Tot slot

Ik hoop dat ik met dit artikel een tipje van de sluier heb opgelicht. Hoewel deze dataset nu wel werkt, is hij
nog niet af. Deze basis dataset is niet flexibel genoeg om uit te breiden. De fielddefinities moeten op een
intelligentere manier kunnen worden bepaald zodat dit niet persé in de inifile hoeft te worden opgenomen. De
dataset moet nog worden uitgebreid met functionaliteit om te kunnen schrijven in de inifiles. Ook wil ik de
dataset geschikt maken voor lezen en schrijven naar de registry.

Hoewel ik de dataset in eerste instantie voor mezelf heb geschreven vond ik het toch leuk en leerzaam om
de uitleg in een artikel te gieten. Ik hoop dat jullie het net zo leuk en leerzaam vinden om het te lezen.Het
doorgronden van zo'n spaarzaam gedocumenteerd object is altijd makkelijker als je een leidraad hebt. Ik heb
dan ook veel afgekeken van de TOutlookDataset van Chris Lichti.

De prefix 'big' in TbigInifileDataset is een beetje het stempel dat ik op eigengemaakte componenten wil
zetten. Het is eigenlijk zo'n roze beest met een krulstaart, maar mocht je het aan willen bevelen bij stinkend
rijke zuurpruimen die daar misschien de humor niet van inzien, dan mag je gerust zeggen dat het van het
engelse 'big' is om daarmee de grootsheid van het component aan te geven.

Op www.goleztrol.nl/inids.htm kun je de volledige source van het component downloaden zoals het in het
artikel is beschreven. Als ik het component wijzig of uitbreid dan zal ik die vernieuwde versie daar ook ter
download aanbieden.
Wil je reageren op dit artikel? Dat kan op het forum. Er zijn op dit moment 5 reacties

Je kunt de schrijver van dit artikel helpen door het artikel een cijfer en een nivo te geven. De schrijver
kan deze informatie wellicht weer in één van de volgende artikelen gebruiken en andere lezers
kunnen door het stemmen meteen een eerste indruk krijgen van dit artikel. Je kunt je stem uitbrengen
door lid te worden van de NLDelphi community. Je kunt je hier aanmelden.
Uitgebrachte
stemmen
Aantal stemmen: 10
Gemiddeld cijfer (1-5): 4.5
Gemiddeld nivo (1-3): 2.8

Alle artikelen in de categorie Databases


Alle artikelen van GolezTrol
Alle artikelen

Copyright © 2004 NLDelphi.com

You might also like