Sunday, July 18, 2010

The in-basket 3 (whole lotta programming)

Another Friday has come and gone, meaning that I'd had another session with the OP regarding the Inbasket exam. I wrote to her during the week saying that the exam was about 80% finished and was in a testable state. Even so, our meeting resulted in requests for several changes: some of these were minor and some were a bit more than minor, but I've finished them all.

We decided that instant messages (IMs) have to be handled "immediately". This means in programming terms that the IMs have to be displayed in a modal dialog box; all the other forms appearing on the screen are non-modal. This wasn't a problem and the program ran fine after the change. I wanted to see what would happen when the clock runs out and there is a modal dialog box still displayed on the screen; the program terminated but a peculiar error message appeared about a query. It took me a while to figure out where this was coming from.

Every second there is a "timer interrupt"; this increments the seconds count and decrements the 'seconds left till the end of the exam' count. Should the seconds count be evenly divisible by 60, then a minute has passed; at this stage, the program checks to see whether there are any emails to be displayed and whether there are any IMs to be displayed. It so happens that my test exam has two IMs; when checking to see what would happen if the user does not handle the IM, I let my program run idly. The first IM popped up on time, the second didn't and when the program finished (the test exam runs only for five minutes - it saves time), there was the error message. Eventually the penny dropped: when the timer interrupt occurred and a minute had passed, then the program tried to display a modal dialog. But if there were a modal dialog already being displayed, there would be a problem as there can't be two simultaneously active modal dialogs in the same program. It became clear that all I needed was a boolean variable to guard the modal part of the timer loop; when the modal dialog begins to execute, the variable is set to true and when the dialog finishes, the variable is set to false. The program checks this guard variable before executing the modal dialog, and of course if the variable is true then the modal dialog is skipped. Now that I think of it, if a modal dialog is skipped, then it will never appear because its time will have passed. Hmmmm.

Once this problem was fixed, I told the OP that she could now test the program. Of course,  she runs 'her' exam and discovers a bug - an IM isn't appearing. I run 'my' exam and the IMs do appear. I check the query which pulls the IM out of the database in order to be displayed on the screen - perfectly fine. In the real exam, the IM appears after five minutes, which I spent idly fiddling about, only to discover that it wasn't appearing. I decided to save my time and so changed the IM's entrance time to one minute; lo and behold, the IM appears. At this stage, the problem became clear: as it happens, in the 'real' exam there is an IM and an ordinary message which are programmed to appear after five minutes. The code necessary to retrieve the ordinary message and display it was taking more than a second, so the IM code (which I thought I had fixed as described in the previous paragraph) wasn't executing. All that was needed was to turn off the timer before displaying the email and turning it on again immediately afterwards.

Both these problems remind me of programming TSRs in the long-forgotten DOS age. A better solution that my ad hoc fixes would be to use two different timers; one measures seconds and will be responsible for updating the 'time left' display whereas the other measures minutes and will be responsible for displaying messages. This will obviate the need for turning the timer off and on. I still have to figure out what to do if a user takes a long time to handle an IM and in the mean time a new IM is supposed to appear. Presumably I will have to store each undisplayed IM's id number in a queue and handle that queue.

I'm sure that this is much more complicated than the OP imagined and maybe it won't be necessary as the user will be interested in handling the IMs instead of ignoring them, because otherwise she won't be able to complete the exam.

I have to note the fact that writing this exam has certainly varied my programming diet and made me face challenges which either I have never faced before or haven't faced for a long time. This is what makes programming so much fun. I mean, when was the last time that  I had to use a queue in a program?

I want to pass on one little tip. In my programs, I often load a listbox with values taken from a database table (let's say the name field) whilst simultaneously storing the value's id field. This can be done as follows:

 with qQuery do
  begin
   open;
   while not eof do
    begin
     n:= lb.items.add (fieldbyname ('name').asstring);
     sendmessage (lb.handle, lb_setitemdata, n, fieldbyname ('id').asinteger);
     next
    end;
   close;
  end;

The id number is then retrieved thus:
 id:= sendmessage (lb.handle, lb_getitemdata, lb.itemindex, 0);

The key to this code is using the pair of Windows message, lb_setitemdata and lb_getitemdata. Despite the similarities between a listbox and a combobox, it transpires that there are no equivalent messages (like cb_setitemdata). So what's a jobbing programmer to do? Until recently I was forced to issue a database query in order to retrieve an id for a given name, but now I've found a much better solution:

comboxbox.items.clear;
 with qQuery do
  begin
   open;
   while not eof do
    begin
     combobox.items.AddObject (fieldbyname ('name').asstring,
                                                       tobject (fieldbyname ('id').asinteger);
     next
   end;
  close
end;

Accessing the id now becomes
with combobox do id:= longint (Items.Objects[Items.IndexOf(text)]);

I think that it should be possible to replace the 'indexof (text)' with 'itemindex'. The 'AddObject' method allows one to attach an object to each element in the 'items' array; a long integer takes up the same amount of storage as an object, so it can be stored by casting the integer as an object, and retrieved by casting the object as a longint.

No comments: