DelphiBasics
  Home  |  Writing a more meaningful program
 Documents
 Tutorials

 Writing your first program
 Writing your second program
 Amending this program

 Delphi data types
   Numbers
   Text (strings and chars)
   Sets and enumerations
   Arrays
   Records

 Programming logic
   Looping
   SubRoutines
   Exception handling

 Dates and times

 Files

 Pointers

 Printing text and graphics

 Object Orientation basics
   Memory leaks!
   Inheritance
   Abstraction
   Interfaces
   An example class

 References

 Standard components

 Articles

 A brief history of Delphi

 Usability : file handling

 Usability : reference books

 Author links

  Writing a more meaningful program
A simple spelling corrector
In this tutorial, we will build a more extensive program that performs very very simple spelling correction on a text file. It illustrates a number of important features of Delphi, and some of the power of the Delphi Run Time Library.
 
Our program asks the user to load a text file by pressing a 'Load file' button on the main program form. When the user has selected a file from the file selection dialog that appears, the contents are displayed in a TMemo box on the form, and a ghosted button - 'Correct' - is then enabled. The file line count is shown.
 
Pressing 'Correct' will correct mis-spellings of the word 'the' (something the author is often guilty of). Stats about the changes will be shown. When done, the 'Save file' button is enabled, allowing the file to be saved.
 
More than one file can be loaded in a session, and none have to be saved (no warnings are given when saving or when failing to save).
 

Designing the form
It is important when writing programs (applications) to design for ease of use by the user, and not ease of use for you, the coder. This is actually a very difficult thing to do, but it will vastly improve the acceptance of your program. The traditional, and still sensible approach is to design the external appearance and actions of your program, and then work down to code from there. Fortunately, Delphi, like other object oriented programs makes the transition easy.
 

 
In the screen shot above, our form is shown built with 1 Memo box (TMemo), 4 labels (TLabel), and 3 buttons (TButton). The Memo box will be used to show the file text. The red arrows show where under the Standard graphical item tab to select these 'widgets' (a term used to describe a graphical control item). You click on the widget you want, and then drag its size on the form. The final label is shown with the drag corners.
 
Notice that each widget has a name shown on it. For the Memo box, this is the initial contents that will be shown (our code will reset this to blank). For the labels and buttons, these are called the 'captions'.
 
The first thing we will do is to change the button captions. Click on Button1, and you will see its attributes in the Object Inspector window. Change the caption from 'Button1' to 'Load file'. Likewise change the Button2 caption to 'Save file' and Button3 to 'Correct'.
 
As well as changing captions, we will change the names from the default values Delphi used to something more useful. Memo1 becomes MemoBox. And the buttons are now named LoadButton, SaveButton and CorrectButton. The labels can stay as they are.
 
Our form is now designed!
 

Linking code to the form
Object oriented programming revolves around objects and how they interact together. We have form, memo box, label and button objects. If we double click on any of them, Delphi will create code for the clicked widget. When you run the program, clicking the widget will run the code. As simple as that (except that you can do a whole lot more - see the Events tab of the Object Inspector. When you have added an action, a link to your code will appear there).
 
Acting upon form creation
So we will first double click on an empty part of the form. This will create the following code in your program unit, and position your cursor within it:
 
 procedure TForm1.FormCreate(Sender: TObject);
 begin
 
 end;

This code, as its name implies, will be executed when your form is created (do not worry in this tutorial about the Sender parameter). This is very useful. It is effectively the start of your application. More importantly, it is the earliest part of your application when you can access the widgets on the form. We need this access because we will be working with them - initialising them, as in the code here:
 
 procedure TForm1.FormCreate(Sender: TObject);
 begin
  // Set the title of the form - our application title
   Form1.Caption := 'Very simple spell corrector';
 
  // Disable all except the load file button
   SaveButton.Enabled    := false;
   CorrectButton.Enabled := false;
 
  // Clear the file display box
   MemoBox.Clear;
 
  // Enable scroll bars for this memo box - this allows us to scroll up
  // and down and left and right to see all the text
   MemoBox.ScrollBars := ssBoth;
 
  // Do not allow the user to directly type into the displayed file text
   MemoBox.ReadOnly := true;
 
  // Set the font of the memo box to a mono-spaced one to ease reading
   MemoBox.Font.Name := 'Courier New';
 
  // Set all of the labels to blank
   Label1.Caption := '';
   Label2.Caption := '';
   Label3.Caption := '';
   Label4.Caption := '';
 
  // Create the open dialog object - used by the GetTextFile routine
   openDialog := TOpenDialog.Create(self);
 
  // Ask for only files that exist
   openDialog.Options := [ofFileMustExist];
 
  // Ask only for text files
   openDialog.Filter := 'Text files|*.txt';
 
  // Create the string list object that holds the file contents
   fileData := TStringList.Create;
 end;

For now, do not worry about the openDialog statements. They'll be covered below.
 
The last thing we have done is to create a string list (click on it in the code above to learn more). fileData refers to a TStringList variable we have defined earlier (before the implementation section of the unit):
 
 var
  // Global definitions in our unit
   Form1: TForm1;
   fileName : String;
   fileData : TStringList;
   openDialog : TOpenDialog;

A string list is literally that - an object variable that can hold a variable number of strings. We will be using this to hold the contents of our text file. Before we can do that, we must create (instantiate) a TStringList object. This creation process allocates storage for the object and initialises it internally. It is defined as global so that all of the widget action code can work with it. Be careful with global data - you should keep it to a minimum.
 
So when our program now starts, the form is cleared of extraneous labelling, and other bits and pieces are set in place, such as scroll bars for the memo box (note that you can do this at the form design stage - take a look at the properties for the memo box).
 
Acting upon 'Load file' button clicking
Double click the 'Load file' button and more code will be inserted into your unit:
 
 procedure TForm1.LoadButtonClick(Sender: TObject);
 begin
 
 end;

We want to load up a user selected file. So we need to let the user select using a file selection dialog:
 
 procedure TForm1.LoadButtonClick(Sender: TObject);
 begin
  // Display the file selection dialog
   if openDialog.Execute then       // Did the user select a file?
   begin
     ...

Now we can see that we have used the openDialog defined in the global variable section, and as initialised in the form creation routine. Let's look again at that initialisation:
 
  // Create the open dialog object - used by the GetTextFile routine
   openDialog := TOpenDialog.Create(self);
 
  // Ask for only files that exist
   openDialog.Options := [ofFileMustExist];
 
  // Ask only for text files
   openDialog.Filter := 'Text files|*.txt';

The file selection (open) dialog object must first be created. It can then be used as many times as we wish. We then configure the dialog to display only existing text files. Click on the TOpenDialog keyword above to learn more.
 
Returning to the LoadButton code, we now load the file specified by the user:
 
   if openDialog.Execute then       // Did the user select a file?
   begin
    // Save the file name
     fileName := openDialog.FileName;
 
    // Now that we have a file loaded, enable the text correction button
     CorrectButton.Enabled := true;
 
    // Load the file into our string list
     fileData.LoadFromFile(fileName);
   end;

We save the name of the file (which includes its full path) so that we can save to it later. We enable the correct text button, and then do the elegant bit - load the file into a string list (the one we defined earlier) using just one statement! We can now access any line of the file by index number!
 
Finally we do the following:
 
  // Display the file in the file display box
   MemoBox.Text := fileData.Text;
 
  // Clear the changed lines information
   Label1.Caption := '';
   Label2.Caption := '';
   Label3.Caption := '';
 
  // Display the number of lines in the file
   Label4.Caption := fileName+' has '+IntToStr(fileData.Count)+
                     ' lines of text';

we display this complete file in another very elegant statement - fileData.Text converts the string list into one big string which is then written to the memo box for display.
 
We clear the labels except the fourth. Here we use another feature of the TStringList class - the Count property. It gives the count of lines in the loaded file.
 
Acting upon the 'Correct' button clicking
Before we look at the Correct Button code, let us look at a new data type, an enumerated type, introduced in this unit:
 
 type
   TheMisSpelled = (TEH, ETH, EHT);    // Enumeration of 'the' miss-spellings

This defines the 3 mis-spellings of the word 'the' that our code will correct in loaded files. See the Sets and enumerations tutorial for more on enumerations. Here we limit our spell corrector to a tiny range of correction values, as enumerated here. These values, TEH, ETH and EHT are simply placeholders. We use strings to do the checking and corrections.
 
Double click on the 'Correct' button, and enter the following code:
 
 procedure TForm1.CorrectButtonClick(Sender: TObject);
 var
   text : String;
   line : Integer;
   changeCounts : array[TEH..EHT] of Integer;
 
 begin
  // Set the changed line counts
   changeCounts[TEH] := 0;
   changeCounts[ETH] := 0;
   changeCounts[EHT] := 0;
 
  // Process each line of the file one at a time
   for line := 0 to fileData.Count-1 do
   begin
    // Store the current line in a single variable
     text := fileData[line];
 
    // Change the 3 chosen basic ways of mis-spelling 'the'
     if ChangeText(text, TEH) then Inc(changeCounts[TEH]);
     if ChangeText(text, ETH) then Inc(changeCounts[ETH]);
     if ChangeText(text, EHT) then Inc(changeCounts[EHT]);
 
    // And store this padded string back into the string list
     fileData[line] := text;
   end;
 
  // And redisplay the file
   MemoBox.Text := fileData.Text;
 
  // Display the changed line totals
   if changeCounts[TEH] = 1
   then Label1.Caption := 'Teh/teh changed on 1 line'
   else Label1.Caption := 'Teh/teh changed on '+
                          IntToStr(changeCounts[TEH])+' lines';
 
   if changeCounts[ETH] = 1
   then Label2.Caption := 'eth changed on 1 line'
   else Label2.Caption := 'eth changed on '+
                          IntToStr(changeCounts[ETH])+' lines';
 
   if changeCounts[EHT] = 1
   then Label3.Caption := 'eht changed on 1 line'
   else Label3.Caption := 'eht changed on '+
                          IntToStr(changeCounts[EHT])+' lines';
 
  // Finally, indicate that the file is now eligible for saving
   SaveButton.Enabled := true;
 
  // And that no more corrections are necessary
   CorrectButton.Enabled := false;
 end;

As we make changes, we keep count of the changed lines in the changeCounts array. Notice that it has bounds defined by the enumeration type we defined. We call a new routine ChangeText to do the changes on every string in the string list (the for loop indexes the string list from the first string (index 0) to the last (one less than the number of strings)). We increment counts if the called routine says that it made changes (it returns a Boolean value). Here is that routine:
 
 function TForm1.ChangeText(var Text: String; theType: TheMisSpelled): Boolean;
 var
   changed : Boolean;
 begin
  // Indicate no changes yet
   changed := false;
 
  // First see if the string contains the desired string
   case theType of
     TEH :
       if AnsiContainsStr(Text, 'teh') or AnsiContainsStr(Text, 'Teh') then
       begin
         Text := AnsiReplaceStr(Text, 'teh', 'the');   // Starts lower case
         Text := AnsiReplaceStr(Text, 'Teh', 'The');   // Starts upper case
         changed := true;
       end;
     ETH :
       if AnsiContainsStr(Text, 'eth') then
       begin
         Text := AnsiReplaceStr(Text, 'eth', 'the');   // Lower case only
         changed := true;
       end;
     EHT :
       if AnsiContainsStr(Text, 'eht') then
       begin
         Text := AnsiReplaceStr(Text, 'eht', 'the');   // Lower case only
         changed := true;
       end;
   end;
 
  // Return the changed status
   Result := changed;
 end;

Click on any blue keywords to learn more. The routine is called 3 times per line - each time it is passed one of the mis-spelling enumeration types. The Case statement routes to the right code to perform according to the type. We set the returned value - Result - to true if we have changed the line. Notice that the Ansi commands are case sensitive.
 
Returning to the LoadButton code above, the last things we do are to redisplay the changed string list in the memo box and display changed line stats. We also disable the CorrectButton and enable the SaveButton.
 
Phew!
 
Acting on the 'Save file' button clicking
Finally, double clicking the save button allows us to write code to save the now changed file:
 
 procedure TForm1.SaveButtonClick(Sender: TObject);
 begin
  // Simply save the contents of the file string list
   if fileName <> '' then
     fileData.SaveToFile(fileName);
 
  // And disable the file save button
   SaveButton.Enabled := false;
 end;

This is a lot simpler - if we have a valid file name (this is a double check that we have something to save), then we use the string list SaveToFile method. Easy! Then we disable the save button.
 

Putting it all together
Below is the full code, along with a sample before and after text file contents :
 
 // Full Unit code.
 // -----------------------------------------------------------
 
 unit Unit1;
 
 interface
 
 uses
   SysUtils,  StrUtils,
   Forms, Dialogs, Classes, Controls, StdCtrls;
 
 type
   TheMisSpelled = (TEH, ETH, EHT);    // Enumeration of 'the' mis-spellings
   TForm1 = class(TForm)
    // Visual objects inserted by Delphi
     LoadButton    : TButton;
     SaveButton    : TButton;
     CorrectButton : TButton;
     MemoBox       : TMemo;
     Label1        : TLabel;
     Label2        : TLabel;
     Label3        : TLabel;
     Label4        : TLabel;
 
    // Methods added by Delphi
     procedure LoadButtonClick(Sender: TObject);
     procedure SaveButtonClick(Sender: TObject);
     procedure CorrectButtonClick(Sender: TObject);
 
   private
    // Method added by the author
     function  ChangeText(var Text : String; theType : TheMisSpelled) : Boolean;
 
   published
    // Constructor added by Delphi
     procedure FormCreate(Sender: TObject);
   end;
 
 var
  // Global definitions in our unit
   Form1: TForm1;
   fileName : String;
   fileData : TStringList;
   openDialog : TOpenDialog;
 
 implementation
 {$R *.dfm}// Include form definitions
 
 
 // Procedure called when the main program Form is created
 procedure TForm1.FormCreate(Sender: TObject);
 begin
  // Set the title of the form - our application title
   Form1.Caption := 'Very simple spell corrector';
 
  // Disable all except the load file button
   SaveButton.Enabled    := false;
   CorrectButton.Enabled := false;
 
  // Clear the file display box
   MemoBox.Clear;
 
  // Enable scroll bars for this memo box
   MemoBox.ScrollBars := ssBoth;
 
  // Do not allow the user to directly type into the displayed file text
   MemoBox.ReadOnly := true;
 
  // Set the font of the memo box to a mono-spaced one to ease reading
   MemoBox.Font.Name := 'Courier New';
 
  // Set all of the labels to blank
   Label1.Caption := '';
   Label2.Caption := '';
   Label3.Caption := '';
   Label4.Caption := '';
 
  // Create the open dialog object - used by the GetTextFile routine
   openDialog := TOpenDialog.Create(self);
 
  // Ask for only files that exist
   openDialog.Options := [ofFileMustExist];
 
  // Ask only for text files
   openDialog.Filter := 'Text files|*.txt';
 
  // Create the string list object that holds the file contents
   fileData := TStringList.Create;
 end;
 
 
 // Procedure called when the file load button is pressed
 procedure TForm1.LoadButtonClick(Sender: TObject);
 begin
  // Display the file selection dialog
   if openDialog.Execute then       // Did the user select a file?
   begin
    // Save the file name
     fileName := openDialog.FileName;
 
    // Now that we have a file loaded, enable the text correction button
     CorrectButton.Enabled := true;
 
    // Load the file into our string list
     fileData.LoadFromFile(fileName);
   end;
 
  // And display the file in the file display box
   MemoBox.Text := fileData.Text;
 
  // Clear the changed lines information
   Label1.Caption := '';
   Label2.Caption := '';
   Label3.Caption := '';
 
  // Display the number of lines in the file
   Label4.Caption := fileName+' has '+IntToStr(fileData.Count)+' lines of text';
 end;
 
 
 // Procedure called when the file save button is pressed
 procedure TForm1.SaveButtonClick(Sender: TObject);
 begin
  // Simply save the contents of the file string list
   if fileName <> '' then
     fileData.SaveToFile(fileName);
 
  // And disable the file save button
   SaveButton.Enabled := false;
 end;
 
 
 // Procedure called when the correct text button is pressed
 procedure TForm1.CorrectButtonClick(Sender: TObject);
 var
   text : String;
   line : Integer;
   changeCounts : array[TEH..EHT] of Integer;
 begin
  // Set the changed line counts
   changeCounts[TEH] := 0;
   changeCounts[ETH] := 0;
   changeCounts[EHT] := 0;
 
  // Process each line of the file one at a time
   for line := 0 to fileData.Count-1 do
   begin
    // Store the current line in a single variable
     text := fileData[line];
 
    // Change the 3 chosen basic ways of mis-spelling 'the'
     if ChangeText(text, TEH) then Inc(changeCounts[TEH]);
     if ChangeText(text, ETH) then Inc(changeCounts[ETH]);
     if ChangeText(text, EHT) then Inc(changeCounts[EHT]);
 
    // And store this padded string back into the string list
     fileData[line] := text;
   end;
 
  // And redisplay the file
   MemoBox.Text := fileData.Text;
 
  // Display the changed line totals
   if changeCounts[TEH] = 1
   then Label1.Caption := 'Teh/teh changed on 1 line'
   else Label1.Caption := 'Teh/teh changed on '+
                          IntToStr(changeCounts[TEH])+' lines';
 
   if changeCounts[ETH] = 1
   then Label2.Caption := 'eth changed on 1 line'
   else Label2.Caption := 'eth changed on '+
                          IntToStr(changeCounts[ETH])+' lines';
 
   if changeCounts[EHT] = 1
   then Label3.Caption := 'eht changed on 1 line'
   else Label3.Caption := 'eht changed on '+
                          IntToStr(changeCounts[EHT])+' lines';
 
  // Finally, indicate that the file is now eligible for saving
   SaveButton.Enabled := true;
 
  // And that no more corrections are necessary
   CorrectButton.Enabled := false;
 end;
 
 
 // Function to change a type of 'the' mis-spelling in a string
 // Returns true if the string was changed
 function TForm1.ChangeText(var Text: String; theType: TheMisSpelled): Boolean;
 var
   changed : Boolean;
 begin
  // Indicate no changes yet
   changed := false;
 
  // First see if the string contains the desired string
   case theType of
     TEH :
       if AnsiContainsStr(Text, 'teh') or AnsiContainsStr(Text, 'Teh') then
       begin
         Text := AnsiReplaceStr(Text, 'teh', 'the');   // Starts lower case
         Text := AnsiReplaceStr(Text, 'Teh', 'The');   // Starts upper case
         changed := true;
       end;
     ETH :
       if AnsiContainsStr(Text, 'eth') then
       begin
         Text := AnsiReplaceStr(Text, 'eth', 'the');   // Lower case only
         changed := true;
       end;
     EHT :
       if AnsiContainsStr(Text, 'eht') then
       begin
         Text := AnsiReplaceStr(Text, 'eht', 'the');   // Lower case only
         changed := true;
       end;
   end;
 
  // Return the changed status
   Result := changed;
 end;
 
 end.

The displayed file before correction
 Teh cat sat on eth mat
 The cat did not sit on eth mat or teh floor
 
 Teh teh teh eth eth eht eht
 Final line of 5.

The displayed file after correction
 The cat sat on the mat
 The cat did not sit on the mat or the floor
 
 The the the the the the the
 Final line of 5.

The displayed statistics
 Teh/teh changed on 5 lines
 eth changed on 4 lines
 eht changed on 2 lines
 The file has 5 lines

 
 

Delphi Basics © Neil Moffatt All rights reserved.  |  Home Page