Pascal Newsletter #22
The full source code examples of this issue are available for download.
![]() |
![]() |
Pascal Newsletter #22 - 18-MAY-2001 INDEX 1. A FEW WORDS FROM THE EDITOR 2. DELPHI 6 3. VALIDATING EMAIL ADDRESSES IN DELPHI 4. OLD TIMES (II) - Opinion - By H.R Quiroga - A real programmer - Going back to the early eighties - The Holy War - Basic and Java - Present times - Where are we going 5. SEARCHING TEXT IN A MEMO FIELD 6. GREATIS PRINT SUITE - What is Greatis Print Suite? - Components - Download - License - More information 7. MISCELANEOUS STRING HANDLING FUNCTIONS - Counting occurrences in a string - Splitting a string in an array - Splitting a string in a string list - Converting MySql TimeStamps 8. COPY, INHERIT OR USE? 9. DELPHI ON THE NET ________________________________________________________________________ 1. A FEW WORDS FROM THE EDITOR AND THE LICENSE GOES TO... Roberto Martinez Olvera, 31 years old, Systems Executive of IEQSA, Querétaro (Mexico), is the winner of the Help & Manual Drawing, for having the number 059 (corresponding to the second prize of the Lottery of Cordoba, since no participant had the number 347 corresponding to the first prize). Congratulations! Help & Manual: http://www.ec-software.com/hmpage.htm ERRATA The routine presented in the article "Getting the BIOS serial number" in the last issue doesn't work on Windows NT/2000. You can see the comments at http://www.delphi3000.com/articles/article_2194.asp#Comments Best 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. DELPHI 6 If -like me- you though that Delphi 6 was just going to introduce some improvements over Delphi 5 and that Kylix portability was going to be the star in this new version of Delphi, you were totally wrong... While Microsoft's Visual Studio .NET is still in its first beta, Borland announced the availability of Delphi for June 8 and is already taking preorders. This way Delphi 6 will be the first RAD tool for Windows with full support for the new industry standard Web Services, enabling immediate and ongoing integration with emerging Web Services based vendor platforms like for example Microsoft's .Net and BizTalk, and Sun Microsystems's ONE. More information: * Borland unveils industry's first RAD Web Services development platform http://www.borland.com/about/press/2001/del6released.html * Borland enters Web services fray http://www.infoworld.com/articles/hn/xml/01/05/07/010507hnborland.xml * Borland aims to make Web services a "Snap" http://www.zdnet.com/eweek/stories/general/0,11011,2712635,00.html * Delphi gets corporate http://www.comp-buyer.co.uk/index71/newnews/newsarticle.php3?id=2124 * Screenshots and articles on Delphi 6 in the Delphi community http://community.borland.com/delphi/0,1419,1,00.html * What's new in Delphi 6 http://www.borland.com/delphi/del6/whatsnew.html * Delphi 6 feature matrix http://www.borland.com/delphi/del6/featurematrix/ * Delphi 6 system requirements http://www.borland.com/delphi/del6/sysreq.html * Delphi 6 editions and prices http://shop.borland.com/Category/0,1257,3-15-983,00.html ________________________________________________________________________ 3. VALIDATING EMAIL ADDRESSES IN DELPHI Nowadays it's very common that our programs store email addresses in databases as part of the data of personnel, customers, providers, etc. When prompting the user for an email address, how do we know if the entered value is formally correct? In this article I'll show you how to validate email addresses using a variation of the RFC #822. The RFC #822 rules the "STANDARD FOR THE FORMAT OF ARPA INTERNET TEXT MESSAGES". You can find it at: http://www.isi.edu/in-notes/rfc822.txt According to this rule, the following are valid email addresses: John Doe johndoe@server.com John Doe <johndoe@server.com> "John Doe" johndoe@server.com "John Doe" <johndoe@server.com> The purpose of my code is not to validate such things, but strictly what is necessary to reach a single recipient (like "johndoe@server.com"), that in the specification is referred as an "addr-spec", which has the form: local-part@domain local-part = one "word" or more, separated by periods domain = one "sub-domain" or more, separated by periods A "word" can be an "atom" or a "quoted-string": atom = one or more chars in the range #33..#126 except ()<>@,;:\/".[] quoted-string = A text enclosed in double quotes that can contain 0 or more characters (#0..#127) except '"' and #13. A backslash ('\') quotes the next character. A "sub-domain" can be a "domain-ref" (an "atom") or a "domain-literal": domain-literal = A text enclosed in brackets that can contain 0 or more characters (#0..#127) except '[', ']' and #13. A backslash ('\') quotes the next character. According to the RFC 822, extended characters (#128..#255) cannot be part of an email address, however many mail servers accept them and people use them, so I'm going to take them into account. The RFC 822 is very open about domain names. For a real Internet email address maybe we should restrict the domain part. You can read more about domain names in the RFC #1034 and RFC #1035 that you can find at: http://info.internet.isi.edu/in-notes/rfc/files/rfc1034.txt http://info.internet.isi.edu/in-notes/rfc/files/rfc1035.txt For the RFC 1034 and the RFC 1035, a domain name is formed by "sub- domains" separated by periods, and each subdomain starts with a letter ('a'..'z', 'A'..'Z') and should be followed by zero or more letters, digits and hyphens, but cannot end with a hyphen. We are going to consider that a valid domain should have at least two "sub-domains" (like "host.com"). Now that we have the rules clear, let's get to the work. The algorithm for the function resembles a states-transition machine. Characters of the string are processed in a loop, and for each character first we determine in which state the machine is and then we process the character accordingly, to determine if the machine should continue in that state, switch to a different state or produce an error (breaking the loop). These kind of algorithms are extensively treated in programming-algorithms textbooks, so let's get right to the code: function ValidEmail(email: string): boolean; // Returns True if the email address is valid // Author: Ernesto De Spirito <eds2004 @ latiumsoftware.com> const // Valid characters in an "atom" atom_chars = [#33..#255] - ['(', ')', '<', '>', '@', ',', ';', ':', '\', '/', '"', '.', '[', ']', #127]; // Valid characters in a "quoted-string" quoted_string_chars = [#0..#255] - ['"', #13, '\']; // Valid characters in a subdomain letters = ['A'..'Z', 'a'..'z']; letters_digits = ['0'..'9', 'A'..'Z', 'a'..'z']; subdomain_chars = ['-', '0'..'9', 'A'..'Z', 'a'..'z']; type States = (STATE_BEGIN, STATE_ATOM, STATE_QTEXT, STATE_QCHAR, STATE_QUOTE, STATE_LOCAL_PERIOD, STATE_EXPECTING_SUBDOMAIN, STATE_SUBDOMAIN, STATE_HYPHEN); var State: States; i, n, subdomains: integer; c: char; begin State := STATE_BEGIN; n := Length(email); i := 1; subdomains := 1; while (i <= n) do begin c := email[i]; case State of STATE_BEGIN: if c in atom_chars then State := STATE_ATOM else if c = '"' then State := STATE_QTEXT else break; STATE_ATOM: if c = '@' then State := STATE_EXPECTING_SUBDOMAIN else if c = '.' then State := STATE_LOCAL_PERIOD else if not (c in atom_chars) then break; STATE_QTEXT: if c = '\' then State := STATE_QCHAR else if c = '"' then State := STATE_QUOTE else if not (c in quoted_string_chars) then break; STATE_QCHAR: State := STATE_QTEXT; STATE_QUOTE: if c = '@' then State := STATE_EXPECTING_SUBDOMAIN else if c = '.' then State := STATE_LOCAL_PERIOD else break; STATE_LOCAL_PERIOD: if c in atom_chars then State := STATE_ATOM else if c = '"' then State := STATE_QTEXT else break; STATE_EXPECTING_SUBDOMAIN: if c in letters then State := STATE_SUBDOMAIN else break; STATE_SUBDOMAIN: if c = '.' then begin inc(subdomains); State := STATE_EXPECTING_SUBDOMAIN end else if c = '-' then State := STATE_HYPHEN else if not (c in letters_digits) then break; STATE_HYPHEN: if c in letters_digits then State := STATE_SUBDOMAIN else if c <> '-' then break; end; inc(i); end; if i <= n then Result := False else Result := (State = STATE_SUBDOMAIN) and (subdomains >= 2); end; Any collaboration to improve this function will be welcome. ________________________________________________________________________ 4. OLD TIMES (II) - Opinion - By H.R Quiroga A real programmer ----------------- Perhaps many of you have read the phrase "Real Programmers Don't Use Pascal", which belongs to an article published in 1983 in "Datamation" by Ed Post. Things have changed since then to the degree that many of the utilities of "PCMagazine" are made with Delphi, and if those programmers aren't "real", there's not much left for me. I don't think that my knowledge of C, Assembler, Fortran, Cobol, Clipper and other languages make me more "Real". Going back to the early eighties -------------------------------- When Borland's Turbo Pascal appeared, Microsoft was selling a Pascal compiler (I have cloudy memories of it, so please excuse if I am not right). It required three diskettes, compilation and many-steps linking, and several hundreds dollars per license, in addition to some text editor like Wordstar. On the other hand, "Turbo Pascal" needed 32K, had an integrated editor, generated 30K executables that were much smaller than those of the competition and got to cost less than 100 dollars. You'll understand why Microsoft eventually retired from that competition (it is possible that they have eliminated any historical record of this failure like the main character of Orwell's novel in 1984). One tried Turbo Pascal thinking about the ridiculous of the name. Soon it stopped from sounding ridiculous. There were some important losses in the way. Peace to the rests of "Topspeed Modula-2". The Holy War ------------ In computing, "Holy Wars" are based in nontechnical positions (almost religious) taken by means of syllogisms to technical positions. They are very funny or at least ingenious. There has always been wars of C versus something else. Obviously "C" versus "Pascal". The war didn't made much damage to either of them and both languages survive today. The Object Pascal compiler is extremely far from Pascal like perhaps the one of C++ to C. Paradoxically, this article looks very much like the artillery of a holy war ;-> and given the forum where I'm presenting this, it's almost like trying to convince the convinced. Anyway, the defects pointed to each language during the wars were very certain in their majority. Many changes in these languages are only for covering these terrible weaknesses. Basic and Java -------------- There has always been something very annoying in the persistence of BASIC, and it isn't only its origin, but also its poor evolution. BASIC survives because Microsoft maintains it; something we could also say of Pascal with respect to Borland (I believe that at some moment it's been the other way round). Perhaps C, derivates and Java are safe from this. Microsoft has tried in vain to spoil Java. Borland in a more discreet way contributed important things to its specification (read JavaBeans) without trying to appropriate it, and in this discreet way they maintain one of the best Java development tools. It will be interesting to see the future of Java and Borland. BASIC, with the terrible thing it is, still exists derived in Visual Basic. I hate to confront Visual Basic programmers who insist in that I should use it, without caring that I in fact I had used it and left it. Currently I'm thinking of giving some effort to Java. It seems to me that this will contribute something useful or at least it will give me the opportunity to reject it properly. Present times ------------- We already know that Pascal has presence nowadays, and significantly applied through Delphi. The interesting thing is to see where it is used and if we can recognize its maturity thru this. We know the NASA uses it. We also know that there are some commercial applications made with Delphi. What I would like to see is a complete Office Suite done with Delphi; nevertheless, I know this is a very difficult market, so it's only a dream so far. I must add that this dream has a Linux environment. Where are we going ------------------ Yes, first person in plural. Marco Cantù said "Dephi is not only a product. It's a community". This community seems impelled more and more by means of the Internet and it seems to grow more and more every day. It pleases to me to belong to the Delphi Community and I believe that we are many and enthusiastic, and for that reason we must make us notice. It doesn't matter that Visual Something sells more copies if the quality of our work is superior and fills us with more pleasure. It's inevitable to mention Kylix. Any prediction is yet a little ventured. I hope that eventually we hear of Delphi for Mac or something like this. Unlike the Mac, I believe that Linux won't diminish its impetus. ------------- To remember the history I recommend a look at (many precise data indicated here came from this source): "THE JARGON FILE, VERSION 2.9.12", known as "The Hacker's Dictionary". Other data were taken from articles by Marco Cantú (www.marcocantu.com) ------------------------------------ Copyright (c) 2001 H.R Quiroga ________________________________________________________________________ 5. SEARCHING TEXT IN A MEMO FIELD If you need to search for text in a memo field, you can do it by scanning the dataset moving record by record to see if the search text is present in the memo field or not: procedure TForm1.btnFindClick(Sender: TObject); var SearchStr: string; begin SearchStr := UpperCase(Edit1.Text); Table1.DisableControls; if Sender = btnFindFirst then Table1.First // Find First button else if not Table1.Eof then Table1.Next; // Find Next button while not Table1.Eof and (AnsiPos(SearchStr, UpperCase(Table1Notes.AsString)) = 0) do Table1.Next; Table1.EnableControls; if Table1.Eof then ShowMessage('Not found') end; The full source code of this example can be found in the archive attached to this newsletter. ________________________________________________________________________ 6. GREATIS PRINT SUITE What is Greatis Print Suite? ---------------------------- Greatis Print Suite is an extremely convenient set of components which provides advanced print and preview features into Delphi and C++ Builder applications. Components ---------- The suite contains: * TPrintJob: Main non-visual component of the suite which provides easy multipage printing * TPreview: Control which provides easy print preview * TPreviewWindow: Ready-to-use preview window * TPreviewToolbar: Ready-to-use toolbar which provides control of preview * TPreviewStatusBar: Ready-to-use status bar which provides display preview parameters * TPreviewComboBox: Combo box which provides display and control of preview scale * TPreviewLabel: Label to display preview parameters Greatis Print Suite Pro contains additional Print Jobs package - set of ready-to-use components to print grids, databases and tables with many abstract classes to create custom print jobs. * TSimpleGridPrintJob: Print job for easy print any graphic grid * TSimpleTextGridPrintJob: Print job for easy print any text grid * TDBGridPrintJob: Print job for easy print database grid * TStringGridPrintJob: Print job for easy print TStringGrid contents * TListViewPrintJob: Print job for easy print TListView contents * TStringsPrintJob: Print job for easy print text files and TStrings contents * TMultiPrintJob: Component for integrate other print jobs * TDraftPrintJob: Component for draft (thumbnail) printing Forget BeginDoc, EndDoc, NewPage and other low-level printing procedures, just draw your print job and Print Suite will handle the rest. Download -------- Compiled EXE-demo, printable documentation in PDF-format and trial versions of all Print Suite Pro components are included in demo kit, available from the following address: http://www.greatis.com/printsuitedemo.zip (~735K) An evaluation copy is available upon request. License ------- Print Suite costs US$ 29.95-39.95 for a single-user license. More information ---------------- See more information on Print Suite home page http://www.greatis.com/printsuite.htm For more information, contact Greatis Software <b-team@greatis.com> ________________________________________________________________________ 7. MISCELANEOUS STRING HANDLING FUNCTIONS - By Ernesto De Spirito Just in case someone finds them useful, here go a few string handling functions I've been asked for in my "Delphi Help Desk". Counting occurrences in a string ================================ The following functions return the number of occurrences of a char or a substring within a string or ANSI string: interface function Occurs(const str: string; c: char): integer; overload; function Occurs(const str: string; const substr: string): integer; overload; function AnsiOccurs(const str: string; const substr: string): integer; implementation uses sysutils; function Occurs(const str: string; c: char): integer; // Returns the number of times a character occurs in a string var p: PChar; begin Result := 0; p := PChar(Pointer(str)); while p <> nil do begin p := StrScan(p, c); if p <> nil then begin inc(Result); inc(p); end; end; end; function Occurs(const str: string; const substr: string): integer; // Returns the number of times a substring occurs in a string var p, q: PChar; n: integer; begin Result := 0; n := Length(substr); if n = 0 then exit; q := PChar(Pointer(substr)); p := PChar(Pointer(str)); while p <> nil do begin p := StrPos(p, q); if p <> nil then begin inc(Result); inc(p, n); end; end; end; function AnsiOccurs(const str: string; const substr: string): integer; // Returns the number of times a substring occurs in a string // ANSI version var p, q: PChar; n: integer; begin Result := 0; n := Length(substr); if n = 0 then exit; q := PChar(Pointer(substr)); p := PChar(Pointer(str)); while p <> nil do begin p := AnsiStrPos(p, q); if p <> nil then begin inc(Result); inc(p, n); end; end; end; Splitting a string in an array ============================== The following functions split a string in parts separated by a substring and return the parts in a dynamic string array: interface type TStringArray = array of string; function Split(const str: string; const separator: string = ','): TStringArray; function AnsiSplit(const str: string; const separator: string = ','): TStringArray; implementation uses sysutils; function Split(const str: string; const separator: string): TStringArray; // Returns an array with the parts of "str" separated by "separator" var i, n: integer; p, q, s: PChar; begin SetLength(Result, Occurs(str, separator)+1); p := PChar(str); s := PChar(separator); n := Length(separator); i := 0; repeat q := StrPos(p, s); if q = nil then q := StrScan(p, #0); SetString(Result[i], p, q - p); p := q + n; inc(i); until q^ = #0; end; function AnsiSplit(const str: string; const separator: string): TStringArray; // Returns an array with the parts of "str" separated by "separator" // ANSI version var i, n: integer; p, q, s: PChar; begin SetLength(Result, AnsiOccurs(str, separator)+1); p := PChar(str); s := PChar(separator); n := Length(separator); i := 0; repeat q := AnsiStrPos(p, s); if q = nil then q := AnsiStrScan(p, #0); SetString(Result[i], p, q - p); p := q + n; inc(i); until q^ = #0; end; Example: procedure TForm1.Button1Click(Sender: TObject); var a: TStringArray; i: integer; begin a := Split('part1,part2,part3'); for i := 0 to Length(a) - 1 do begin // Will show three dialogs ShowMessage(a[i]); // 'part1', 'part2', 'part3' end; end; Splitting a string in a string list =================================== The following functions split a string in parts separated by a substring and return the parts in a string list that may be passed as third parameter or created by the function (and in this latter case it must be freed by the caller): interface uses classes; function SplitStrings(const str: string; const separator: string = ','; Strings: TStrings = nil): TStrings; function AnsiSplitStrings(const str: string; const separator: string = ','; Strings: TStrings = nil): TStrings; implementation uses sysutils; function SplitStrings(const str: string; const separator: string; Strings: TStrings): TStrings; // Fills a string list with the parts of "str" separated by // "separator". If Nil is passed instead of a string list, // the function creates a TStringList object which has to // be freed by the caller var n: integer; p, q, s: PChar; item: string; begin if Strings = nil then Result := TStringList.Create else Result := Strings; try p := PChar(str); s := PChar(separator); n := Length(separator); repeat q := StrPos(p, s); if q = nil then q := StrScan(p, #0); SetString(item, p, q - p); Result.Add(item); p := q + n; until q^ = #0; except item := ''; if Strings = nil then Result.Free; raise; end; end; function AnsiSplitStrings(const str: string; const separator: string; Strings: TStrings): TStrings; // Fills a string list with the parts of "str" separated by // "separator". If Nil is passed instead of a string list, // the function creates a TStringList object which has to // be freed by the caller // ANSI version var n: integer; p, q, s: PChar; item: string; begin if Strings = nil then Result := TStringList.Create else Result := Strings; try p := PChar(str); s := PChar(separator); n := Length(separator); repeat q := AnsiStrPos(p, s); if q = nil then q := AnsiStrScan(p, #0); SetString(item, p, q - p); Result.Add(item); p := q + n; until q^ = #0; except item := ''; if Strings = nil then Result.Free; raise; end; end; Examples: procedure TForm1.Button1Click(Sender: TObject); begin SplitStrings(Edit1.Text, ', ', ListBox1.Items); end; procedure TForm1.Button2Click(Sender: TObject); var Parts: TStrings; begin Parts := nil; try Parts := SplitStrings(Edit1.Text, ', '); ShowMessage('First part is "' + Parts[0] + '"'); finally Parts.Free; end; end; Converting MySql TimeStamps =========================== The following functions convert MySql full timestamps (strings in the format 'YYYYMMDDHHMMSS') to a Variant in TDateTime format and viceversa. uses sysutils; function MySqlTimeStampToDateTime(const TimeStamp: string): variant; // Converts a MySql TimeStamp to a Delphi TDateTime begin if TimeStamp = '' then Result := Null else Result := EncodeDate(StrToInt(Copy(TimeStamp, 1, 4)), StrToInt(Copy(TimeStamp, 5, 2)), StrToInt(Copy(TimeStamp, 7, 2))) + EncodeTime(StrToInt(Copy(TimeStamp, 9, 2)), StrToInt(Copy(TimeStamp, 11, 2)), StrToInt(Copy(TimeStamp, 13, 2)), 0); end; function DateTimeToMySqlTimeStamp(DateTime: variant): string; // Converts a Delphi TDateTime in a variant to a MySql TimeStamp begin if VarType(DateTime) in [varNull, varEmpty] then Result := '' else Result := FormatDateTime('yyyymmddhhnnss', DateTime); end; Example: procedure TForm1.Button1Click(Sender: TObject); var timestamp: string; begin timestamp := DateTimeToMySqlTimeStamp(Now); ShowMessage(timestamp + #13#13 + DateTimeToStr(MySqlTimeStampToDateTime(timestamp))); end; ________________________________________________________________________ 8. COPY, INHERIT OR USE? Code reusability saves us time and effort, increasing our productivity. Object-oriented programming has something to do with that, and in the case of Delphi we can reuse forms and even entire projects. For example if we have a form with a table, a dbgrid, a navigator and several buttons, we can save it like a model in the Object Repository to reuse it in several parts of our application or other applications. The same for a standard form of the type "Save, Don't save, Cancel". To add a form to the repository you have to right-click it and select "Add to Repository..." in the context menu. To save a project in the repository choose "Add to Repository..." from the Project menu. To use a form of the repository in our application, in the File menu we chose New and in the New Items dialog we click the Forms tab to see the forms available in the repository. Then we select the form we want, the method of use (Copy, Inherit, or Use) and click the OK button. The differences between these three methods of use are described briefly here: COPY: Creates a form that is copy of the form that is in repository. The changes you make to the copy won't affect the form in the repository (nor other projects that use it), and changes made to the form in the repository won't affect forms previously copied from it. This option is used when the form in the repository is just a base to work, with a very low level of standardization. Full-adaptation is possible. INHERIT: It creates a form that derives from the form in the repository. Changes made to this derived form (inherited) won't affect the form in the repository, but the inverse is not true. This option is used when the form in the repository is well standardized but it is desired to allow some adaptation. It's the most powerful way to use a form. USE: It adds the form of the repository to your project. It's not a copy, but the form of the repository itself, and thus any modification that you make to it will apply to other projects that USE or INHERIT it. This option is used when the form of the repository is a standard and is defined in itself (it doesn't require particular adaptations for each case/application). ________________________________________________________________________ 9. DELPHI ON THE NET * Delphi Database Programming Course - by Zarko Gajic Free online database programming course for beginner Delphi developers focused on ADO techniques. A new chapter have been added in the last two weeks (Chapter 8 "Data filtering"). http://delphi.about.com/compute/delphi/library/weekly/aa010101a.htm * Learning Assembler with Delphi - by Ian Hodger http://www.delphi3000.com/articles/article_2245.asp * Starting an application as an icon in the system tray - by D. Souchard http://www.delphi3000.com/articles/article_1606.asp * QuickReports Manuals and Tutorials - by QuSoft http://195.12.232.172/Downloaddoc.html ________________________________________________________________________ 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/p0022.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) 2001 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!






