Improving the example

In most Windows programs the most important functions can not only be reached through the menu but also via hotkeys and a toolbar. The hotkeys are very easy. Just set the "shortcut" property of a menu entry to anything you want. To get a toolbar in your form you need to add a ToolBar component from the Win32 palette. By right-clicking on it you can add new switches. The images on the switches are provided by a component called ImageList. You can find it in the Win32 palette as well. Add one ImageList to your form. To add images to the ImageList double-click on it and choose add. First you need to create a couple of images (for the buttons: new, open, save, print, font). Use the picture editor (tools menu in Delphi). Choose New - Bitmap. Make it 20x20 in size. Save the bitmaps in the same directory as your project. Add all the bitmaps to the ImageList. Set the "Images" property of the toolbar to your ImageList (ImageList1). Set the ImageIndex of each toolbutton to the correct value.

To Connect the toolbutton to the functions you already wrote, simply double-click on the toolbuttons. Call your functions from there. For the open-button this looks like this:

procedure TForm1.ToolButton2Click(Sender: TObject);
begin
  Open1Click(self);
end;

Self is a pointer to the object of which self is a property. Think of it as a loop: it point back to itself (TForm1 in the example). This might sound weird at first, but turns out to be very useful for cases like this one.

In real applications menu entries that do not make sense at the time are grayed out. We should do this with Save. This is easy to achieve. Every time the file is changed, it would make sense to save it. So we have to enable Save after changed:= true and to disable it after changed:= false. Just put in a line saying

  Save1.enabled:= false;

after every changed:= false. (Save1.enabled:= true after changed:= true) and set enabled to false in the object inspector.

Currently the save as-function simply overwrites existing files. To add a dialog box that asks the user whether he wants to overwrite an existing file, you first have to check if a file with that name already exists. Use FileExists to do this:

procedure TForm1.SaveAs1Click(Sender: TObject);
begin
  if SaveDialog1.Execute then
    if FileExists(SaveDialog1.Filename) then
    begin
      if MessageDlg('Do you want to overwrite the existing file?',
        mtWarning,[mbYes, mbNo],0)= idYes then
      begin
        RichEdit1.Lines.SaveToFile(SaveDialog1.Filename);
        filename:= SaveDialog1.Filename;
        changed:= false;
      end;
    end else
    begin
      RichEdit1.Lines.SaveToFile(SaveDialog1.Filename);
      filename:= SaveDialog1.Filename;
      changed:= false;
    end;
end;

First, the SaveDialog is shown. If the user inputs a filename and clicks yes, the program checks if a file with that name already exists. If so the user is asked whether he wants to overwrite the file. MessageDlg is passed the text in the dialog box, a "kind" of box (mtWarning in this case), and a combination of buttons to show. It returns the ID of the button that was clicked (idYes).

There is no much use in the possibility to change a few options in the program if the user has to do it all over when he starts the program again. What you obviously need here is a way to save the options. Bill Gates wants you to save them to the Windows registry. If you don't want this you are supposed to at least put your config file in the c:\windows directory. Now, please go to Explorer, change to the c:\windows directory and try to name the applications that created all the files that are in it (I have about 400 even so I try to clean it occasionally). My point is: Do NOT write anything to the registry (look at it with regedit.exe, its huge as well) or any directory that does not belong to your program unless you have a special reason for doing so. What harm does it do if your program saves its configuration to its own directory? If the user wants to delete the program he can simply delete the directory and be sure that everything is gone and that there are no side-effects.

Delphi provides a class that helps you to deal with configuration files (called Ini-files in Windows). The class is called TIniFile and encapsulates all the functionality you usually need to save options to a file. It provides reading and writing of string, integer, booleans and also organized your ini-file in sections.

When your program exits you want to save the options. The TForm.OnClose event occurs whenever a form is closed. trigger it and put in the following code:

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var
  AIniFile: TIniFile;
begin
  AIniFile:= TIniFile.Create(IniDir+'\'+'editor.ini');
  with AIniFile do begin
    WriteString('Config', 'FontName', RichEdit1.Font.Name);
    WriteInteger('Config', 'FontSize', RichEdit1.Font.Size);
    WriteInteger('Config', 'Left', Left);
    WriteInteger('Config', 'Top', Top);
    WriteInteger('Config', 'Width', width);
    WriteInteger('Config', 'Height', height);
  end;
  AIniFile.Free;
end;

You also need to add a IniDir: String variable to the TForm1 class. It is used to save the directory that was current directory when the program was started (the user could change this). As you see from the source, the AIniFile needs to be initialized first using TIniFile.create(PATHNAME);. Then the options are written to the file. Finally AIniFile is released again.

Reading the options is somewhat more difficult because you cannot just trigger the OnCreate event and then read and set the options. When TForm1.OnCreate occurs, RichEdit is not created yet. You need to delay the process. Add a timer component from the "system" palette to your form. Set its interval property to 5 (it is measured in milliseconds). The component only has one event: OnTime (occurs when the interval is over). Put the following source in its procedure:

procedure TForm1.Timer1Timer(Sender: TObject);
var
  AIniFile: TIniFile;
begin
  AIniFile:= TIniFile.Create(getcurrentdir+'\'+'editor.ini');
  IniDir:= getcurrentDir;
  with AIniFile do begin
    RichEdit1.Font.Name:= ReadString('Config', 'FontName', 'Courier New');
    RichEdit1.Font.Size:= ReadInteger('Config', 'FontSize', 10);
    left:= ReadInteger('Config', 'Left', 0);
    top:= ReadInteger('Config', 'Top', 0);
    width:= ReadInteger('Config', 'width', 0);
    if width= 0 then width:= screen.width - 50;
    height:= ReadInteger('Config', 'Height', 0);
    if height= 0 then height:= screen.height - 50;
  end;
  AIniFile.Free;
  Timer1.Interval:= 0;
  Timer1.Free; Timer1:= Nil;
end;

First the AIniFile is initialized. The file is stored in the current directory (determined with GetCurrentDir) and called 'editor.ini'. Then the current directory is saved in the IniDir variable. The options are read. As a last step the inteval of the timer is set to 0 so no more OnTimer events will occur. Since we don't need the timer any more we can free it. It helps a lot to set objects you freed to Nil. If you want to find out later if the Object is initialized you can simply check for MyObject<>Nil.

back to top

Help files

A real application usually includes a help file as well. If you want to create one for your program please refer to other documentation. It would be off-topic to discuss this here.

Sixth Day Eighth Day
Back to tutorial main page.


Please e-mail me with any comments!
© 27.12.96 Sebastian Boßung Home