Friday, November 04, 2011

User defined menus

In our weekly meeting today, the Occupational Psychologist raised the possibility of adding user defined menus to her management program. At first I demurred, as the menu is something which is fixed at compile time, but later I figured out a way in which to implement this.

Fortunately, I have already defined a table ('progs') in the program which lists all the forms which I use to track program usage. To this table I added two new fields: the option's name in Hebrew, as it appears in the main menu, and a flag to show whether this is indeed an option which appears on the main menu. Then I added to the form which adds data to the above table fields so that I could mark the required forms.

The next stage was to define a table ('usermenu') which contains the data regarding the user defined menus: an id, the user's internal id number, the menu option's program number and the display order of the option. Then I defined a pair of forms which allow the user to maintain a list of her options, to set the order and to add/remove items.

I haven't gone into much detail regarding the above because it's all fairly straight forward. The challenging part was figuring out how to read the table and turn the entries into real menu entries at run time. Creating menu entries at real time is not difficult, but connecting a random menu entry to the correct event handler (what should the program do when the new option is clicked) is the crux of the matter.

I handled (sorry about the pun) this by finding the pre-defined event handler for the menu option and copying its event handler into the new menu option. For example, if there is an entry in the 'usermenu' table for the program 'DoDockets', then the function FindOption, which appears in the code below, traverses the fixed menu structure looking for a menu item whose caption is 'DoDockets'. The function exits when the match is found, and this menu item's event handler is copied.

The program knows that it is to add the dynamic entries to a main menu whose name is mnUser.
procedure TMainForm.FormShow(Sender: TObject);
var
 item, original: tmenuitem;
 menucaption: string[31];

 Function FindOption (const s: string): TMenuItem;
 var
  found: boolean;
  i: integer;
  tmp: tmenuitem;

 begin
  found:= false;
  i:= -1;
  while not found do
   begin // traverse main menu
    inc (i);
    tmp:= mainmenu1.items[i].find (s);
    if tmp <> nil then
     begin
      found:= true;
      result:= tmp
     end
   end
 end;

begin
// handle user defined menu
 with qUserMenu do
  begin
   params[0].asinteger:= user;
   open;
   while not eof do
    begin
     menucaption:= fieldbyname ('hebrew').asstring;
     original:= FindOption (menucaption);
     item:= TMenuItem.Create (self);
     item.caption:= menucaption;
     item.OnClick:= original.OnClick;
     mnUser.Insert (fieldbyname ('disporder').asinteger, item);
     next
    end;
   close
  end;
end;
I think that this is a really neat piece of code. It occurred to me when I was documenting this that I could add hot keys to the dynamic menu options: F1 would activate the first option, F2 the second, etc. At the moment, the F keys have been assigned to what I thought were the important options but the importance changes as time goes by.

No comments: