Saturday, December 26, 2009

Neat tricks in the 'management' program

I wrote about two weeks ago about a management program which I am developing for my weekend job. This program incorporates many techniques which I've never used before, and I want to talk about two of them here. The use of the ttabcontrol is such a first, as was the use of the tdbnavigator, although that has now been retired and has been replaced with a tdbgrid.The navigator control was causing more problems in terms of ease of use than it was solving, and doing away with it enabled me to remove three dialogs from the program, making it simpler and smaller.

The first technique which I want to share here is the solution to a problem which has plagued me since I started working with the dbExpress components and the Firebird database. In order to develop the program, the data module contains an TSQLConnection component which holds data regarding the location of the database. This component has to be left open during development time, but I aways had to remember to remove the database location from the component before installing the program on my client's computer. I found the other day a better discussion of the problem, and the article cited also purports to give a solution.

I have learnt a great deal about Delphi programming from Zarko Gajic's 'About Delphi' site, but I have to say that in this case, he is wrong - or at least, his solution does not work. There was enough information given which enabled me to find a working solution, which is what I present here.

The source problem is that the sqlconnection gets opened almost immediately on program invocation, even before code in the project file (dpr) executes. Thus any attempt at correcting or changing the connections' properties post facto are doomed to failure. What Gajic gets right is that the sqlconnection component's "BeforeConnect" event has to be hooked into. This is code which - as its name suggests - gets called before the connection to the database is opened. Gajic's solution involves creating a new constructor and various boolean fields, but seems to be lacking a line or two, without which the component does not know that it has to change its properties. A much simper solution follows, which does not involve private constructors nor any fol-de-rol. One simply has to write an event handler for the 'BeforeConnect' event as shown below:
procedure TDM.SQLConnection1BeforeConnect(Sender: TObject);
var
 dir: string;

begin
 with TRegIniFile.create (regpath) do
  begin
   dir:= ReadString ('firebird', progname, '');
   free
  end;

 with sqlconnection1 do
  begin
   close;
   params.values['database']:= dir;
   loginprompt:= false;
  end;
end;
I keep the database locations in an .INI file; this is what is being retrieved in the first half of the code (with TRegIniFile.create ...). The second half (with sqlconnection1 do ...) is where the action happens; the first statement closes the connection, which presumably was left open at design time. The second statement inserts the new location of the database, and the third statement is optional - sometimes I forget to set the loginprompt property. This simple code solves the problem! After this event fires, Delphi will open the connection, and as now the database location is correct, the database will open correctly. 

I hate to think how much time I have wasted because of this problem. Several times I have installed my firebird programs with the sqlconnection open and pointing to the wrong location, causing embarrassment and lost time when I have tried to demonstrate the programs. This is one technique which will soon be migrating to other programs!

The second technique is to do with non-modal dialogs. Until now, I have never used such dialogs, preferring instead to use their modal siblings. Whilst I was aware that maybe the modal dialog is not the correct solution to every problem, it seemed much easier (from a user's point of view). The management program is the first program that I have written that uses non-modal dialogs seriously. There are several tables containing data which could be classed as 'secondary' - customers, therapists, types of activity and so on. These tables are accessed rarely after the data have been defined, but they still have to be accessible. I chose to define a non-modal dialog which allows data entry for all of these tables; one (non-modal) dialog displays all the names of the various records for all the tables, and then specific (modal) dialogs allow editing of all the chosen record's fields. 

I was asked how one could display two such dialogs at the same time. No problem, I replied, as I swiftly displayed one dialog, clicked on the main dialog, then displayed a second dialog. But wait a minute: where had the first dialog gone? I knew that I hadn't closed it, but I couldn't see it anywhere. It turns out that it had "hidden itself" behind the main dialog and thus to all intents and purposes had disappeared. After thinking about it, I realised that I had to write an event handler for an 'OnDeactivate' event (if such an event existing), minimising the dialog. I assumed that if there were an 'OnActivate' event (one which used to be ubiquitous with me, but now I don't use it, preferring 'OnShow' instead as this gets called only once in a program's lifetime), then there should be an 'OnDeactivate' event. Indeed there is, and the implementation is simple. 

procedure TDoTables.FormDeactivate(Sender: TObject);
begin
 showwindow (handle, sw_minimize)
end;

Although I promised only to write about two techniques, here is a third which I have started to use. Frequently, my programs' main dialog would be defined as a modal dialog box; such a form cannot be resized which is what I wanted. Unfortunately, this also meant that such dialogs cannot be minimised; I developed a technique of defining the F1 key as a 'minimise key', trapping this keypress and minimising the dialog. A much better solution is to define the form's borderstyle as 'bsSingle' and to set the bordericons property as [biSystemMenu, biMinimize]. 

I know that these are techniques which I should have absorbed ten years ago, but at the time there was so much material to assimilate that these little points got missed. Programs got written, they worked and they even solved problems, so what is basic Windows behaviour was ignored. Better late than never.....

No comments: