Help, I need somebody
Help, not just anybody
Help, you know I need someone
Help!

The Beatles

Once the program grows out of short pants, it will definitely need help. More precisely, the user of your program will need help: even if he is a professional in his field, he needs to get used to the interface. And in some cases, it doesn’t hurt to talk about the processes that you have automated.

My Visual Database supports the ability to call a help file. To do this, the TApplication class has properties and methods for working with the standard Windows help system.

  • HelpFile – a property where you can specify the location of the help file
  • HelpContents – the method that opens the help file
  • HelpContext – a method that opens the help file on the desired page
  • HelpIndex – a method that opens a keyword search
  • HelpKeyword – a method that opens a search for any words

Refining the Hotkey module

Usually, help is accessed using the F1 function key or the “Help” item in the main menu. To implement this functionality in the “Production” program, you will need several scripts, as well as modification of existing ones. We are talking about the Hotkey.pas module, which contains the implementation of the hotkeys. The fact is that the scripts I suggested earlier are suitable for use with forms of the TAForm class, but do not work with forms that are descendants of TForm. We are talking about service forms of the application: settings form, authorization form, etc., for which you may also need to connect the help system.

The fact is that in MVDB for some event handlers there are two options for handler procedures: standard (Delphi) and modified (MVDB). Let’s add a standard hotkey press handler on the form:

procedure Hotkey_OnKeyDownST(Sender: TObject; var Key: Word; ShiftState: TShiftState);
// processing hotkeys - according to the Delphi standard
var
  tmpForm:TForm;
  tmpButton: TdbButton;
  tmpName:string;
  tmpComponent: TComponent;
  i:integer;
begin
  tmpForm := TForm(Sender);
  tmpName := '';
  for i := 0 to HOTKEY_COUNT-1 do
  begin
    // match found
    if (Key = HotKey_Code[i]) and (ShiftState = HotKey_ShiftState[i]) then
    begin
      tmpName := HotKey_Command[i];
      // looking for a button
      FindC(tmpForm,tmpName,tmpComponent,False);
      if tmpComponent <> nil then
      begin
        if tmpComponent is TdbButton then
        begin
          TdbButton(tmpComponent).Click;
          Key := 0;
        end
        else
        if tmpComponent is TMenuItem then
        begin
          TMenuItem(tmpComponent).Click;
          Key := 0;
        end
      end
      else // search in the main menu...
      begin
        FindC(tmpForm,tmpName,tmpComponent,False);
        if tmpComponent <> nil then
        begin
          if tmpComponent is TMenuItem then
            TMenuItem(tmpComponent).Click
        end
      end;
      break;
    end;
  end;
end;

Code language: Delphi (delphi)

When finalizing the procedure, I took into account the fact that forms can be nested: the main form can display a table view form, which in turn can also contain another form. Therefore, after the hotkey is triggered, the Key variable is set to zero (25,31) to prevent the handler from triggering on the parent form.

Please note that the change in the input parameters (1) affected the way of describing the combinations of pressing the main key and auxiliary keys (Ctrl, Alt and Shift), which led to a change in the method of checking pressing (15), namely: the replacement of three separate arrays HotKey_Shift, HotKey_Alt and HotKey_Ctrl for one:

HotKey_ShiftState: array of TShiftState; // click flagsCode language: Delphi (delphi)

These changes have affected the Hotkey_Init() method, in which a click handler is attached to the specified form and hotkeys are declared.

procedure Hotkey_Init(Sender:TObject);
// connection to the form
// Sender - the form for which the hotkey system is connected
var
  i:integer;
begin
  if not (Sender is TForm) then
  begin
    RaiseException('Hotkey_Init() - parameter is not a form');
    exit;
  end;
  //
  TForm(Sender).KeyPreview := True;
  TForm(Sender).OnKeyDown := 'Hotkey_OnKeyDownSt';
  //
  // array initialization
  SetLength(HotKey_Code, HOTKEY_COUNT);
  SetLength(HotKey_ShiftState, HOTKEY_COUNT);
  SetLength(HotKey_Command, HOTKEY_COUNT);
  // setup hotkeys
  i := 0;
  // show help
  HotKey_Code[i] := VK_F1;
  HotKey_ShiftState[i] := 0;
  HotKey_Command[i] := 'btnHelp';
  inc(i);
  // Add a note
  HotKey_Code[i] := VK_F2;
  HotKey_ShiftState[i] := 0;
  HotKey_Command[i] := 'btnNew';
  inc(i);
  // edit entry
  HotKey_Code[i] := VK_F4;
  HotKey_ShiftState[i] := 0;
  HotKey_Command[i] := 'btnEdit';
  inc(i);
  // delete entry
  HotKey_Code[i] := VK_F8;
  HotKey_ShiftState[i] := 0;
  HotKey_Command[i] := 'btnDelete';
  inc(i);
  // show "About" window
  HotKey_Code[i] := VK_F11;
  HotKey_ShiftState[i] := 0;
  HotKey_Command[i] := 'mniAboutEx';
  inc(i);
  //
end;
Code language: Delphi (delphi)

The Hotkey_Init() method has become more compact, but now to specify the involved service keys, you need to specify the sum of the values of special constants:

  • ssAlt – Alt key pressed
  • ssCtrl – Ctrl key pressed
  • ssShift – Shift key pressed

Here is an example if you need to assign a hotkey to the Shift+Ctrl+F5 combination

  HotKey_Code[i] := VK_F5;
  HotKey_ShiftState[i] := ssCtrl+ssShift;Code language: Delphi (delphi)

Help module

This module consists of several procedures and functions:

  • Help_Init – help subsystem initialization
  • Help_Show – help call handler
  • Help_OnClick – help call handler with section selection
  • Help_GetIDH – function to convert string identifier to help file ID
  • Help_IDHGenerator – source code snippet generator

Since the hot key F1 is used to call the help, it is necessary to add an invisible button to each form of the application, which will be responsible for calling this very help. We will make an exception for the intro form. Firstly, there is nothing to tell about this form, and secondly, modification of this form during its display leads to undesirable visual effects – the splash screen disappears.

procedure Help_Init( ASplashForm:TForm);
// initialization of the help system
var
  tmpForm: TForm;
  tmpButton: TdbButton;
  i: integer;
  tmpFileName:string;
begin
  // a hidden help call button is added to each form.
  // except for the form with the splash screen
  for i := 0 to Screen.FormCount - 1 do
  begin
    tmpForm := TForm(Screen.Forms[i]);
    if tmpForm <> ASplashForm then
    begin
      tmpButton := TdbButton.Create( tmpForm );
      tmpButton.Parent := tmpForm;
      tmpButton.Name := 'btnHelp';
      tmpButton.Visible := False; // invisible TODO: add the option to make this button visible on the toolbar.
      tmpButton.OnClick := 'Help_OnClick';
      Hotkey_Init(tmpForm);
    end;
  end;
  // the help file will have the same name as the application file, but with the extension chm.
  tmpFileName := Application.ExeName;
  delete( tmpFileName, length(tmpFileName) - 2, 3 );
  Application.HelpFile := tmpFileName +'chm';
end;

procedure Help_OnClick(Sender:TObject; var Cancel:boolean);
// handling the help call button
var
  tmpForm:TAForm;
begin
  CForm(Sender,tmpForm);
  Application.HelpContext( Help_GetIDH( tmpForm.Name ) ); // get ID from form name.
end;
Code language: PHP (php)

In addition to initializing the help subsystem, this procedure connects a hotkey handler (21) for each form.

In order to map a help section to each form, we need the Help_GetIDH() function, which in our case should convert the form names into an identifier. It looks like a simple case:

function Help_GetIDH( AName: string ):integer;
// get help index by string id
begin
  case AName of
  'frmMain': Result := $014;
  'frmdbCoreImageViewer': Result := $02;
  'frmOptionsdbCore': Result := $0D2;
  'frmdbCoreAbout': Result := $02;
  'frmMySQLLogin': Result := $02;
  'frmdbCoreExport': Result := $02;
  'frmdbCoreImport': Result := $02;
  'frmdbCoreImportProgress': Result := $02;
  'frmdbCoreLogin': Result := $02;
  'frmdbCoreUsers': Result := $02;
  'frmdbCoreUserForm': Result := $02;
  'frmdbCoreUserChangePassword': Result := $02;
  'frmExportCsvProgress': Result := $02;
  'frmItemType': Result := $01E;
  'efmItemType': Result := $0246;
  'frmItem': Result := $01C;
  'efmItem': Result := $0245;
  'frmContr': Result := $01B;
  'efmContr': Result := $0244;
  'frmProductItem': Result := $01C;
  'efmProductItem': Result := $0288;
  'frmUnit': Result := $01F;
  'efmUnit': Result := $0247;
  'frmDocType': Result := $020;
  'efmDocType': Result := $0248;
  'frmOperDoc': Result := $021;
  'efmOperDoc': Result := $05F;
  'frmOper': Result := $021;
  'efmOper': Result := $098;
  'frmProduction': Result := $060;
  'frmPItem': Result := $01D;
  'efmPItem': Result := $0D1;
  'frmPItemRest': Result := $0B5;
  'efmProd': Result := $0288;
  'frmBalance': Result := $022;
  'frmPBalance': Result := $023;
  'frmOperRep': Result := $024;
  //
  else Result := $02;
  end;
end;
Code language: Delphi (delphi)

For large projects, it takes a long time to write this procedure manually, so I decided to add an auxiliary procedure that generates a text blank of the case statement, in which all that remains is to put down the actual IDs (I’ll tell you where to get them a little later).

procedure Help_IDHGenerator;
// generate a code snippet for the Help_GetIDH function
// call once
var
   i:integer;
   tmpList:TStringList;
   tmpDefIDH:string;
begin
   tmpDefIDH := '$02'; // default code.
   tmpList := TStringList.Create;
   tmpList.Add(' case AName of');
   for i := 0 to Screen.FormCount - 1 do
   begin
     tmpList.Add(' '''+Screen.Forms[i].Name+''': Result := '+tmpDefIDH+';');
   end;
   tmpList.Add(' else Result := '+tmpDefIDH+';');
   tmpList.Add('end;');
   tmpList.SaveToFile( ExtractFilePath(Application.ExeName)+'\Script\IDH_FormList.txt' );
   tmpList.Free;
end;
Code language: Delphi (delphi)

To call help from the main menu, you need a simple Help_Show() procedure that can be used as an event handler when a menu item is selected:

procedure Help_Show(Sender:TObject);
// display help file
begin
  Application.HelpContents;
end;
Code language: Delphi (delphi)

Other improvements

To add a help call from the main menu, you will need to modify the UserApp_InitForm() procedure: add a new branch in which the general help call will be displayed and transfer the “About” form display there. I have not been able to migrate yet, since the procedure for migrating menu nodes to MVDB is rather cumbersome, so I decided to hide the old menu item and add a new one, adding some useful procedures:

procedure UserApp_InitForm;
// form initialization
var
  tmpItem:TMenuItem;
begin
  // splitters
  Splitter_Create( frmItem.pgcDetail, frmItem.panMain, alBottom);
  Splitter_Create( frmOperDoc.pgcDetail, frmOperDoc.panMain, alBottom);
  // menu
  //
  tmpItem := Menu_Add('','Reports',nil,1, '-');
  Menu_Add('Show_frmBalance','Balance',tmpItem);
  Menu_Add('Show_frmPBalance','Batteries (batches)',tmpItem);
  Menu_Add('Show_frmOperRep','Movement',tmpItem);
  //
  tmpItem := Menu_Add('','References',nil,1, '-');
  Menu_Add('Show_frmContr','Accounts',tmpItem);
  Menu_Add('Show_frmItem','Nomenclature',tmpItem);
  Menu_Add('Show_frmPItem','Parts',tmpItem);
  Menu_Add('Show_frmItemType','Item type',tmpItem);
  Menu_Add('Show_frmUnit','Units ',tmpItem);
  Menu_Add('Show_frmDocType','Document Type',tmpItem);
  //
  Menu_Add('Show_frmOperDoc','Document Log',frmMain.mniFile,0);
  Menu_Add('','-',frmMain.mniFile,1, '-');
  //
  tmpItem := Menu_Add('HelpTopic','?',nil,5, '-');
  Menu_Add('Help','Help',tmpItem,0,'Help_Show');
  Menu_Add('','-',tmpItem,1, '-');
  Menu_Add('AboutEx','About',tmpItem,2,'App_ShowCoreAbout');
  Menu_HideItem('mniAbout');

  Form_ShowOnWinControl( frmProductItem, frmItem.tshProductItem );
// Form_ShowOnWinControl( frmPItem, frmItem.tshPItem );
  Form_ShowOnWinControl( frmOper, frmOperDoc.tshOper );
end;

procedure Menu_HideItem(AItemName:string);
// hide the main menu item
var
  tmpForm:TAForm;
  tmpItem:TMenuItem;
begin
  tmpForm := MainForm; // work with the menu on the main form
  FindC(tmpForm,AItemName,tmpItem);
  tmpItem.Visible := False;
end;

procedure App_ShowCoreAbout(Sender:TObject);
// displaying the standard "About" form
begin
  frmdbCoreAbout.ShowModal;
end;Code language: Delphi (delphi)

In addition, in the frmMain_OnShow() procedure, it is necessary to call the help subsystem initialization procedure Help_Init(), passing the splash screen form as a parameter (line 25).

procedure frmMain_OnShow(Sender: TObject; Action: string);
// display the main form
var
  tmpForm:TForm;
  tmpStartTime: TTime;
  tmpSleepTime: integer;
begin
  // if necessary, check the license
  License_Init;
  //
  tmpForm := Splash_Create;
  tmpForm.Show;
  Application.ProcessMessages;
  tmpStartTime := Time();
  //
  // there should be any initialization here, which can take a long time
  App_InitSystemVar; // initialization of system variables
  Images_Init; // initialization of the image loading subsystem
  Style_Init; // style activation
  Style_CreateOptions; // add style to standard docker
  App_InitAboutForm; // initialization of the About form
  //
  Help_Init(tmpForm); // initialize the help system.
  //
  UserApp_InitForm; // create forms
  UserApp_InitVar; // set up variables
  UserApp_InitBase; // database initialization
  // guaranteed delay, but no more than specified in APP_SPLASH_DELAY
  tmpSleepTime := SPLASH_DELAY_MIN - Trunc( (Time() - tmpStartTime)*24*60*60*1000 );
  if tmpSleepTime > 0 then
    Sleep(tmpSleepTime);
  tmpForm.Close;
  tmpForm.Free;
  //
  // display the name and version of the program in the header of the main form
  frmMain.Caption := APP_NAME + ' '+App_GetVersion;
end;

Code language: Delphi (delphi)

Creating a help file

https://www.drexplain.com

After trying various tools for creating .chm files, I chose DrExplain. What is special about her? I will list its advantages, which became decisive for me when choosing:

  • The program is available in 11 languages
  • Detailed documentation
  • Built-in HTML editor
  • One-click document generation
  • Generation of documents in various formats (.chm, .html, .pdf, .rtf)
  • Customizable generation templates for each format
  • Embedded image capture and annotation system
  • Built-in spell checker
  • Partition readiness statuses
  • Flexible generator and environment settings

This program is interesting not only for individual but also for corporate use. It has the ability to collaborate on documentation, both through the developer’s server and through its own server. It is possible to publish finished documentation on the developer’s server.

The only drawback of this program is that it is paid. The unregistered version adds watermarks to images, and “Unregistered version” is displayed on every page. But for commercial use, this multifunctional tool may well justify the cost of acquiring it.

For more information about the program itself, the cost of licenses and the conditions for providing additional online services, please visit the manufacturer’s website: https ://www.drexplain.com

Practical use of DrExplain

Despite the ease of use, the creation of full-fledged documentation takes a lot of time. This must be taken into account when planning development.

The documentation process itself is intuitive, which again is a plus for DrExplain, whose user interface is close to perfect. Therefore, I will not describe in detail how to create documentation, but will only talk about the main thing.

The key point of documentation is the integration of help with the application: setting the section IDs in the Help_GetIDH function. To do this, find the desired Hekp ID

and write it in the corresponding line of the procedure, associating it with the application form.

DrExplain allows you to generate files for inclusion in Delphi (*.inc) projects that contain Help ID as a list of constants. But the peculiarity of MVDB is that it does not have a separate directive for including .inc files, and if uses is used, then MVDB requires the presence in the included begin end keywords file. Therefore, I don’t see any practical use of this feature in MVDB projects yet.

Result

Added a help subsystem and changed the “Production” version to 1.0. Run the application, press F1 and get the latest help.

It remains to save up money and buy a license to get rid of watermarks 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *