Pascal Newsletter #7
The full source code examples of this issue are available for download.
![]() |
![]() |
Pascal Newsletter #7 INDEX 1. A FEW WORDS FROM THE EDITOR 2. DATABASE PROGRAMMING IN CODE - CREATE STRUCTURE - USE - APPEND (and REPLACE) - GO TOP, GO BOTTOM, SKIP, SKIP n and EOF - REPLACE - DELETE - INSERT - LOCATE - LOCATE...CONTINUE and SET FILTER - SET ORDER TO - SEEK and (SOFTSEEK or SET NEAR) - SET FILTER (RANGES) 3. THE NEWBIE & THE PRO - Indexes and pointers 4. LINKS ________________________________________________________________________ 1. A FEW WORDS FROM THE EDITOR We would like to thank all our subscribers who answered the question- naire we sent a couple of weeks ago, especially those who made comments since they have ALL been very useful. We apologize for not replying, but we hope you understand we received an avalanche of emails. The result is that definitely the format of the newsletter will remain more or less as it is now: fortnightly, 25K and plain text. A little bit more concise maybe. About the attachments, in the future we will send them only for those who choose to receive them (they are the great majority, but part of the minority clearly rejected this idea, so we feel there is no enough consensus to send attachments to all). The level of the newsletter will keep being more or less the same in average, but sometimes we will try to mix a bit of more and less advanced topics, or we will try to mix "advanced" with "trivial" explanations. Won't be an easy task... The writing rated as "good" and this is enough for us since we are not native English speakers, but we would like to improve if we can. We would appreciate it very much if someone could spot the common mistakes we make or tell us how he/she would have expressed this and that. This newsletter is not something we do for ourselves, but for you, so please don't wait till the next questionnaire in three months to make comments, critics, suggestions, propose topics, etc. Ok? Regards, Ernesto De Spirito eds2004 @ latiumsoftware.com ________________________________________________________________________ JfControls Library. Multi-language. Multi-appearance. Skins. Privileges. More than 40 integrated and customizable components. Impressive GUI. Centralized resources administration. Multiple programming problems solved. For Delphi 3-7 and C++ Builder 3-6. http://www.jfactivesoft.com/ ________________________________________________________________________ 2. DATABASE PROGRAMMING IN CODE Most of those who worked with an xBase language (like dBase, Fox or Clipper) seem to miss the possibility of doing everything in code, without getting involved with components on a form and an Object Inspector... Well, the good news for everybody is that Delphi allows everything that can be done visually to also be done by code. Here we will provide some examples of database programming using the BDE and in the future we will deal also with ADO (most things remain the same). To try the examples, please create a new application and in the form unit add DB and DBTables to the USES clause. CREATE STRUCTURE ================ To create a table we can use a code like the following: procedure TForm1.Button1Click(Sender: TObject); var Products: TTable; begin Products := TTable.Create(nil); try with Products do begin // Table definition DatabaseName := ExtractFilePath(Application.ExeName); TableType := ttDBase; TableName := 'Products.DBF'; with FieldDefs do begin // Field definitions with AddFieldDef do begin Name := 'ProductID'; DataType := ftSmallint; end; with AddFieldDef do begin Name := 'Description'; DataType := ftString; Size := 25; end; with AddFieldDef do begin Name := 'Price'; DataType := ftFloat; end; end; with IndexDefs do begin // Index definitions with AddIndexDef do begin Name := 'ByID'; Fields := 'ProductID'; end; with AddIndexDef do begin Name := 'ByDescr'; Fields := 'Description'; end; end; CreateTable; // Creates the table end; except Products.Free; raise; end; Products.Free; end; First we declare a TTable component and we create it with no owner (nil parameter), meaning we will be responsible of freeing the object. If we had written Products := TTable.Create(Self); instead, then the component would belong to Form1 and the form will free the table when it gets freed itself (for example when the app ends). Since we decided to "manually" free the object, we use a TRY block to insure that we get to do it even if an exception occurs. Then we do all the process of creating a table within a WITH block to save us typing... Without a WITH block we would have to write: Products.DatabaseName := ExtractFilePath(Application.ExeName); Products.TableType := ttDBase; Products.TableName := 'Products.DBF'; Products.FieldDefs.Clear; ... The advantage of not using a WITH block is that you can use the code insight features of the code editor and also sometimes your code is clearer. Using WITH you write less and your code is more efficient when the WITH expression resolves some reference (for example "with a.b do" or "with a[i] do" or in some nested WITHs) because the resolution is done once and the result is being kept in a CPU register (EBX to be precise) for faster access. Now, let's go back to the table properties... * DatabaseName is the name of the database the table belongs (or will belong) to. The value of this property can be either a directory (for example we used the directory where the application is located) or a BDE alias (for example DBDEMOS). * TableType defines the type of the table. Normally we only set it when we are creating a table. In the example we chose a dBase table. * TableName is the name of the table. For example for dBase and Paradox tables this is the name of the table file (the directory is determined by DatabaseName). * FieldDefs is a list of TFieldDef objects, each of them representing a field definition. The method AddFieldDef adds a field definition to the list and returns a reference to it, that we use in a WITH block to set the properties of this newly added field (for example: Name, DataType, Size, Precision, Required, etc.). * IndexDefs is a list of TIndexDef objects, each of them representing an index definition. The method AddIndexDef adds an index definition to the list and returns a reference to it, that we use in a WITH block to set the properties of this newly added index (for example: Name, Fields, etc.). If you want an index for more than one field, all you have to do is separate them with semicolons: Fields := 'Field1;Field2;Field3'; Once we set all these properties (IndexDefs being optional) we proceed to create the table invoking the CreateTable method. After this you should have a file named Products.DBF in your application's directory. USE === To open and close a table we can use the Active property or the Open and Close methods: procedure TForm1.Button2Click(Sender: TObject); var Products: TTable; begin Products := TTable.Create(nil); try with Products do begin DatabaseName := ExtractFilePath(Application.ExeName); TableName := 'Products.DBF'; Open; // Active := True; // Work with the table here Close; // Active := False; end; except Products.Free; raise; end; Products.Free; end; APPEND (and REPLACE) ==================== To add new records at the end of the table we can add some lines between the Open and Close method calls above: // APPEND BLANK // REPLACE ProductID WITH 137, Description WITH 'Sodas', ; // Price WITH 0.35 Append; FieldByName('ProductID').AsInteger := 137; FieldByName('Description').AsString := 'Sodas'; FieldByName('Price').AsFloat := 0.35; Post; Let's add a few more records to use them later in the examples: Append; FindField('ProductID').AsInteger := 243; FindField('Description').AsString := 'Cigars'; FindField('Price').AsFloat := 1.15; Post; Append; Products['ProductID'] := 395; Products['Description'] := 'Cookies'; Products['Price'] := 1.35; Post; Append; FieldValues['ProductID'] := 45; FieldValues['Description'] := 'Candies'; FieldValues['Price'] := 0.05; Post; Append adds a blank record (not blank actually since fields can have default values) but the operation is not completed until you call the Post method. As you can see, there are many ways of referencing the fields and their values: * FieldByName and FindField both return a reference to a TField object. The difference is that if the field name passed as parameter is not found, FieldByName raises an exception while FindField returns nil. * FieldValues is the default property of the TTable class (this means that for example "Table1.FieldValues['Field1']" is the same as just "Table1['Field1']") and represents an array of variants with the values of the fields. GO TOP, GO BOTTOM, SKIP, SKIP n and EOF ======================================= Now that we have four records ProductID Description Price 137 Sodas 0.35 243 Cigars 1.15 395 Cookies 1.35 45 Candies 0.05 we can move thru them: First; // Goes to the first record (GO TOP) ShowMessage(FieldByName('Description').AsString); // Sodas Last; // Goes to the last record (GO BOTTOM) ShowMessage(FieldValues['Description']); // Candies Next; // Goes to the next record (SKIP) if Eof then ShowMessage('EOF'); ShowMessage(Products['Description']); // Candies Prior; // Goes to the previous record (SKIP -1) ShowMessage(FieldValues['Description']); // Cookies MoveBy(-2); // Goes up two records (SKIP -2) ShowMessage(FieldByName('Description').AsString); // Sodas In xBase dialects, when you are at the end of file (EOF), you are positioned in a sort of blank record following the last record of the table, and the fields value zero, false, spaces or the blank date depending on the field types. Once you reached EOF, you can go back to the last record with SKIP -1. In Visual Basic, when you are at EOF, there is no current record (any attempt to access field values results in an error), but you can go back to the last record with MovePrevious. In Delphi, EOF is a flag indicating you attempted to move past the end of file, but you are still positioned in the last record (unless the table is empty when there is no current record). REPLACE ======= To replace the values of one or more fields, we use Edit to prepare the record for changes, then we update the fields and finally we save the changes with Post: // REPLACE Description WITH 'Diet Sodas', Price WITH 0.40 Edit; FieldByName('Description').AsString := 'Diet Sodas'; FieldByName('Price').AsFloat := 0.40; Post; DELETE ====== ShowMessage(FieldByName('Description').AsString); // Diet Sodas Delete; ShowMessage(FieldByName('Description').AsString); // Cigars In xBase languages, right after you have deleted a record, this record is still current and can be recovered (with RECALL). You can move to the next record with SKIP. In Visual Basic, after calling the Delete method of a Recordset there is no current record (any attempt to access field values results in an error), but you can go to next record with MoveNext. Deleted records are not recoverable (using VB Recordsets at least). In Delphi, the Delete method deletes the current record and the next record becomes current. Deleted records are not recoverable (using Delphi Datasets at least). INSERT ====== Last; Insert; // Can add before the last record or at the end FieldByName('ProductID').AsInteger := 137; FieldByName('Description').AsString := 'Sodas'; FieldByName('Price').AsFloat := 0.35; Post; Insert is almost like Append. Depending on the database type the new record can be added at the end of the table or be physically inserted before the current record. For DBFs, Insert is the same as Append. LOCATE ====== Locate finds the first record with the field values you specify. For example: // LOCATE FOR Description = 'CANDIES' Locate('Description', 'CANDIES', [loCaseInsensitive]); ShowMessage(FieldByName('Description').AsString); // Candies // LOCATE FOR ProductID = 243 .AND. Description = 'CIGARS' Locate('ProductID;Description', VarArrayOf([243,'CIGARS']), [loCaseInsensitive]); ShowMessage(FieldByName('Description').AsString); // Cigars You can search for partial keys (only affects string fields): Locate('Description', 'COOK', [loCaseInsensitive, loPartialKey]); ShowMessage(FieldByName('Description').AsString); // Cookies And you can perform case-sensitive searches: if not Locate('Description', 'CANDIES', []) then ShowMessage('CANDIES not found'); ShowMessage(FieldByName('Description').AsString); // Cigars Locate returns True if a record was found, and False otherwise. After an unsuccessful search, in xBase languages EOF gets to be the current record, in Visual Basic there is no current record, and in Delphi the current record doesn't change. LOCATE...CONTINUE and SET FILTER ================================ If we needed to match the next record/s that matches a criteria, in xBase we can do the following: LOCATE FOR <condition> WHILE .NOT. EOF() * Process record here CONTINUE ENDDO That can also be accomplished as: SET FILTER TO <condition> GO TOP WHILE .NOT. EOF() * Process record here SKIP ENDDO SET FILTER TO In Delphi Datasets we don't have an equivalent for CONTINUE, so we have to use filters. There are two ways of setting a filter: 1) Using the Filter property (and the FilterOptions property) FilterOptions := <filteroptions>; Filter := <condition>; // Sets the filter if FindFirst then repeat // Process record here until not FindNext; Filter := ''; // Removes the filter 2) Using the OnFilterRecord event OnFilterRecord := <filter event procedure>; // Sets the filter if FindFirst then repeat // Process record here until not FindNext; OnFilterRecord := nil; // Removes the filter We'll see both ways in the following example: type TForm1 = class(TForm) ... private { Private declarations } procedure AFilter(DataSet: TDataSet; var Accept: Boolean); public { Public declarations } end; ... procedure TForm1.AFilter(DataSet: TDataSet; var Accept: Boolean); begin Accept := Pos('K', DataSet['Company']) <> 0; end; procedure TForm1.Button3Click(Sender: TObject); var Customers: TTable; begin Customers := TTable.Create(nil); try with Customers do begin DatabaseName := 'DBDEMOS'; TableName := 'Customer.DB'; Active := True; FilterOptions := [foCaseInsensitive]; Filter := 'Country = ' + QuotedStr(InputBox( 'Filter', 'Enter Country to filter', 'bAhAmAs')); if FindFirst then repeat ShowMessage(FieldValues['Company'] + ' (' + FieldValues['Country'] + ')'); until not FindNext; Filter := ''; // Removes the filter ShowMessage('Now with the OnFilterRecord event'); OnFilterRecord := AFilter; if FindFirst then repeat ShowMessage(FieldValues['Company']); until not FindNext; OnFilterRecord := nil; // Removes the filter Active := False; end; except Customers.Free; raise; end; Customers.Free; end; FindFirst, FindNext, FindPrior and FindLast are like First, Next, Prior and Last, but they only move thru the records that match the filter criteria and they return a value (True if a record is found and False otherwise). Filtering via the Filter property is simpler because it can be done in situ and it is somehow more powerful in the sense that you can prompt advanced users for the whole condition: Customers.Filter := InputBox('Filter', 'Enter condition', 'Country = ''US'' OR Country = ''Bahamas'''); Using the OnFilterRecord event implies some more lines of code, but it might be faster since the BDE doesn't have to parse the filter condition, and it is somehow more powerful in the sense that you are not limited to a "simple" filter condition and in the event handler you can build your own logic to determine if a record should be filtered or not (you can call functions, use IFs, search for records in other tables, etc.). Finally you have to set the Accept parameter to True (the record matches the criteria) or False (the record should be excluded). SET ORDER TO ============ To determine which one is the active index all we have to do is set the IndexName property to the name of the index. For example: procedure TForm1.Button4Click(Sender: TObject); var Products: TTable; begin Products := TTable.Create(nil); try with Products do begin DatabaseName := ExtractFilePath(Application.ExeName); TableName := 'Products.DBF'; Open; // Active := True; // No index marked as primary key. Default is natural order ShowMessage(FieldByName('Description').AsString); // Cigars IndexName := 'ByID'; // SET ORDER TO ByID First; ShowMessage(FieldValues['Description']); // Candies IndexName := 'ByDescr'; // SET ORDER TO ByDescr First; ShowMessage(FieldValues['Description']); // Cookies IndexName := ''; // SET ORDER TO First; ShowMessage(FieldValues['Description']); // Cigars Close; // Active := False; end; except Products.Free; raise; end; Products.Free; end; SEEK and (SOFTSEEK or SET NEAR) =============================== There is a short way and a long way to perform a seek. Here is the long way: // SEEK Value1+Value2 // IF FOUND() // ... SetKey; // Clears the key FieldValues['Field1'] := Value1; FieldValues['Field2'] := Value2; if GotoKey then ... // SEEK Value1+Value3 // IF FOUND() // ... EditKey; // Edits the key FieldValues['Field2'] := Value3; if GotoKey then ... And here are the short versions for the above examples: if FindKey([Value1, Value2]) then ... if FindKey([Value1, Value3]) then ... The long way is a bit more performant, but the difference is only marginal, so we prefer the short one because it is much simpler. GotoKey and FindKey return True if they found an exact match and False otherwise. You can search for partial keys using GotoNearest and FindNearest procedures instead, that are the equivalent of calling SOFTSEEK in Clipper or using SET NEAR ON in FoxPro. Here are some examples: IndexName := 'ByID'; SetKey; FieldByName('ProductID').AsInteger := 137; if GotoKey then ShowMessage(FieldValues['Description']); // Sodas if FindKey([45]) then ShowMessage(FieldValues['Description']); // Candies IndexName := 'ByDescr'; SetKey; FieldByName('Description').AsString := 'Cigars'; if GotoKey then ShowMessage(FieldValues['Description']); // Cigars SetKey; FieldByName('Description').AsString := 'CANDIES'; if GotoKey then ShowMessage(FieldValues['Description']) else ShowMessage('CANDIES not found'); SetKey; FieldByName('Description').AsString := 'Co'; GotoNearest; if Copy(FieldValues['Description'],1,2) = 'Co' then ShowMessage(FieldValues['Description']); // Cookies FindNearest(['Can']); if Copy(FieldValues['Description'],1,3) = 'Can' then ShowMessage(FieldValues['Description']); // Candies SET FILTER (RANGES) =================== There is a third way to set a filter: setting a key range in the active index. For example: // SET ORDER TO ByID // SET FILTER TO ProductID >= 100 .AND. ProductID <= 300 // GO BOTTOM // ? Description // SEEK 45 // IF FOUND() // ? Description // ELSE // ? 'Candies not found' // ENDIF // SET FILTER TO IndexName := 'ByID'; SetRange([100],[300]); ApplyRange; Last; ShowMessage(FieldValues['Description']); // Cookies if FindKey([45]) then ShowMessage(FieldValues['Description']) else ShowMessage('Candies not found'); CancelRange; This would be it for now. We'll keep exploring the ways to do in Delphi those things that can be done in xBase languages in the next issue. The complete source code examples of this newsletter are available at: http://www.latiumsoftware.com/download/p0007.zip The examples of the last issue were uploaded a bit late. We apologize for the inconvenience. ________________________________________________________________________ 3. THE NEWBIE & THE PRO Indexes and pointers Here we present an example of a function that returns the position of the first occurrence of a character within a string. NEWBIE: function Scan(s: string; char c): integer; var i: integer: begin Result := 0; for i := 1 to Length(s) do begin if s[i] = c then begin Result := i; break; end; end; end; PRO: function Scan(const s: string; char c): integer; var i: integer: p: pchar; begin Result := 0; p := PChar(s); for i := 1 to Length(s) do begin if p^ = c then begin Result := i; break; end; inc(p); end; end; EXPLANATION: PChar stands for "pointer to char". The sentence "p := PChar(s);" makes p point to the first character in the string s, so saying s[1] is the same as saying p^ (that means "the character pointed by p"). The sentence "inc(p);" increments (i.e. add one to) the pointer, making it point to the next character, so the first time it is executed p will point to s[2] (so p^ = s[2]). The next time p is incremented, it will point to s[3] (so p^ = s[3]), and so on, so we have the same effect as if we used indexes. The difference is that with pointers the function is faster. Another change we made is adding "const" in the declaration of the string parameter. This tells the compiler that the string should be passed by reference (faster), but that we won't attempt to change it. POINTER ARITHMETIC: function Scan(const s: string; char c): integer; var p, q: pchar; begin p := PChar(s); q := StrScan(p, c); if q := nil then Result := 0 else Result := q - p + 1; end; Delphi provides a function named StrScan that takes a pchar instead of a string as a parameter and returns a pointer to the first occurrence of the character, instead of its position, so we use pointer arithmetic to get this position. For example, let's suppose we call "Scan('OOOXOOO', 'X');" 1) After "p := PChar(s);", p is the memory address of the first character in the string s. A memory address is a number (like an index in the memory array), for example $1000. 2) After "q := StrScan(p, c);" q points to the first occurrence of the character c in the string, so in the example q would be $1003. 3) To get position we use the formula "q - p + 1". In our example it would be: $1003 - $1000 + 1 = 3 + 1 = 4 +---+---+---+---+---+- | O | O | O | X | O | +---+---+---+---+---+- 1 2 3 4 5 | | | | | p = $1000 ----+ | | | | $1001 --------+ | | | $1002 ------------+ | | q = $1003 ----------------+ | $1004 --------------------+ So the found character is the fourth of the string. ________________________________________________________________________ 4. LINKS * Delphi applications with source code http://sunsite.informatik.rwth-aachen.de/delphi/ * Delphi Links in the Open Directory Project http://dmoz.org/Computers/Programming/Languages/Delphi/ * Delphi Links at Cetus-Links.org http://www.cetus-links.org/oo_delphi.html * Oberon Microsystems - Here you will find BlackBox, a component-based software development tool that uses Oberon, a successor of the Pascal language invented by the very same Prof. Niklaus Wirth. There is an educational version available for download (~6 Mb). Check it out and then tell us how you liked it. http://www.oberon.ch ________________________________________________________________________ YOU CAN HELP US We need your help to keep this newsletter going and growing. You can help by referring the newsletter to your colleagues: http://www.latiumsoftware.com/en/pascal/delphi-newsletter.php Or you can help by voting for us in some or all of these rankings to give more visibility to our web site and thus increase the number of subscriptions to this newsletter: http://www.sandbrooksoftware.com/cgi-bin/TopSite2/rankem.cgi?id=latium http://news.optimax.com/delphi/links/links.exe/click?id=70C517ECAE6E http://www.programmingpages.com/?r=latiumsoftwarecomenpascal http://www.top219.org/cgi-bin/vote.cgi?delphi&83 http://top100borland.com/in.php?who=20 http://top200.jazarsoft.com/delphi/rank.php3?id=latium http://213.65.224.200/cgi-bin/toplist.cgi/hits?Id=80 It's just a few seconds for you that REALLY mean a lot to us. ________________________________________________________________________ If you haven't received the full source code examples for this issue, you can get them from http://www.latiumsoftware.com/download/p0007.zip ________________________________________________________________________ This newsletter is provided "AS IS" without warranty of any kind. Its use implies the acceptance of our licensing terms and disclaimer of warranty you can read at http://www.latiumsoftware.com/en/legal.php where you will also find a note about legal trademarks. Articles are copyright of their respective authors and they are reproduced here with their permission. You can redistribute this newsletter as long as you do it in full (including copyright notices), without changes, and gratis. ________________________________________________________________________ Main page: http://www.latiumsoftware.com/en/pascal/delphi-newsletter.php Group home page: http://groups.yahoo.com/group/pascal-newsletter/ Subscribe/join: pascal-newsletter-subscribe@yahoogroups.com Unsubscribe/leave: pascal-newsletter-unsubscribe@yahoogroups.com Problems with your subscription? eds2004 @ latiumsoftware.com ________________________________________________________________________ Latium Software http://www.latiumsoftware.com/en/index.php Copyright (c) 2000 by Ernesto De Spirito. All rights reserved. ________________________________________________________________________ |
The full source code examples of this issue are available for download.
![]() |
Errors? Omissions? Comments? Please contact us!






