Эй, люди, помогите! Вы мне нужны, поймите! Эй, мне нужен кто-нибудь.

The Beatles

Как только программа вырастает из коротких штанишек, ей обязательно понадобится помощь. Точнее, помощь потребуется пользователю вашей программы: даже, если он является профессионалом в своем деле, ему нужно освоиться с интерфейсом. А в некоторых случаях не помешает рассказать и о процессах, которые вы автоматизировали.

My Visual Database поддерживает возможность вызова файла справки. Для этого у класса TApplication имеются свойства и методы для работы со стандартной справочной системой Windows.

  • HelpFile – свойство, в котором можно указать расположения файла помощи
  • HelpContents – метод, который открывает файл помощи
  • HelpContext – метод, который открывает файл помощи на нужной странице
  • HelpIndex – метод, который открывает поиск по ключевым словам
  • HelpKeyword – метод, который открывает поиск по любым словам

Доработка модуля Hotkey

Обычно для вызова помощи используют функциональную клавишу F1 или пункт “Помощь” в главном меню. Для реализации данного функционала в программе “Производство” понадобится несколько скриптов, а также модификация уже существующих. Речь идет о модуле Hotkey.pas, в котором находится реализация работы горячих клавиш. Дело в том, что предложенные мной ранее скрипты подходят для использования с формами класса TAForm, но не работают с формами, которые являются наследниками TForm. Речь идет о служебных формах приложения: форме настройки, форме авторизации и т.д., для которых тоже может понадобится подключение системы помощи.

Дело в том, что в MVDB для некоторых обработчиков событий существует два варианта процедур обработчика: стандартный (Delphi) и модифицированный (MVDB). Добавим стандартный обработчик нажатия горячей клавиши на форме:

procedure Hotkey_OnKeyDownST (Sender: TObject; var Key: Word; ShiftState: TShiftState);
// обработка горячих клавиш - по стандарту Delphi
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
    // найдено совпадение
    if (Key = HotKey_Code[i]) and (ShiftState = HotKey_ShiftState[i]) then
    begin
      tmpName := HotKey_Command[i];
      // ищем кнопку
      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 // ищем в главном меню...
      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)

При доработке процедуры я учёл тот факт, что формы могут быть вложенными: на главной форме может отображаться форма табличного представления, которая в свою очередь также может содержать другую форму. Поэтому после срабатывания горячей клавиши в переменную Key заносится нулевое значение (25,31), чтобы предотвратить срабатывание обработчика на родительской форме.

Обратите внимание, что изменение входных параметров (1) затронуло способ описания сочетаний нажатия основной клавиши и служебных (Ctrl, Alt и Shift), что привело к изменению способа проверки нажатия (15), а именно: замена трёх отдельных массивов HotKey_Shift, HotKey_Alt и HotKey_Ctrl на один:

HotKey_ShiftState: array of TShiftState; // флаги нажатияCode language: Delphi (delphi)

Эти изменения затронули метод Hotkey_Init(), в котором к указанной форме привязывается обработчик нажатий и объявляются горячие клавиши.

procedure Hotkey_Init(Sender:TObject);
// подключение к форме
// Sender - форма, для которой подключается система горячих клавиш
var
  i:integer;
begin
  if not (Sender is TForm) then
  begin
    RaiseException('Hotkey_Init() - параметр не является формой');
    exit;
  end;
  //
  TForm(Sender).KeyPreview := True;
  TForm(Sender).OnKeyDown := 'Hotkey_OnKeyDownSt';
  //
  // инициализация массивов
  SetLength(HotKey_Code,HOTKEY_COUNT);
  SetLength(HotKey_ShiftState,HOTKEY_COUNT);
  SetLength(HotKey_Command,HOTKEY_COUNT);
  // настройка горячих клавиш
  i := 0;
  // показать помощь
  HotKey_Code[i] := VK_F1;
  HotKey_ShiftState[i] := 0;
  HotKey_Command[i] := 'btnHelp';
  inc(i);
  // добавить запись
  HotKey_Code[i] := VK_F2;
  HotKey_ShiftState[i] := 0;
  HotKey_Command[i] := 'btnNew';
  inc(i);
  // редактировать запись
  HotKey_Code[i] := VK_F4;
  HotKey_ShiftState[i] := 0;
  HotKey_Command[i] := 'btnEdit';
  inc(i);
  // удалить запись
  HotKey_Code[i] := VK_F8;
  HotKey_ShiftState[i] := 0;
  HotKey_Command[i] := 'btnDelete';
  inc(i);
  // показать окно "О программе"
  HotKey_Code[i] := VK_F11;
  HotKey_ShiftState[i] := 0;
  HotKey_Command[i] := 'mniAboutEx';
  inc(i);
  //
end;
Code language: Delphi (delphi)

Метод Hotkey_Init() стал более компактным, но теперь для указания задействованных служебных клавиш нужно указывать сумму значений специальных констант:

  • ssAlt – нажата клавиша Alt
  • ssCtrl – нажата клавиша Ctrl
  • ssShift – нажата клавиша Shift

Вот пример, если понадобится назначить горячую клавишу для сочетания Shift+Ctrl+F5

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

Модуль Help

Данный модуль состоит из нескольких процедур и функций:

  • Help_Init – инициализация подсистемы помощи
  • Help_Show – обработчик вызова помощи
  • Help_OnClick – обработчик вызова помощи с выбором раздела
  • Help_GetIDH – функция преобразования строкового идентификатора в ID файла помощи
  • Help_IDHGenerator – генератор фрагмента исходного кода

Так как для вызова помощи используется горячая клавиша F1, то необходимо на каждую форму приложения добавить невидимую кнопку, которая будет отвечать за вызов этой самой помощи. Исключение сделаем для формы-заставки. Во-первых рассказывать об этой форме нечего, а во-вторых, модификация данной формы во время её отображения приводит к нежелательным визуальным эффектам – пропаданию картинки-заставки.

procedure Help_Init( ASplashForm:TForm);
// инициализация системы помощи
var
  tmpForm: TForm;
  tmpButton: TdbButton;
  i: integer;
  tmpFileName: string;
begin
  // на каждую форму добавляется скрытая кнопка вызова помощи.
  // кроме формы с заставкой запуска
  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; // невидимая TODO: добавить опцию добавления этой кнопки на панель инструментов - видимой.
      tmpButton.OnClick := 'Help_OnClick';
      Hotkey_Init(tmpForm);
    end;
  end;
  // файл помощи будет называться также, как файл приложения, но иметь расширение chm.
  tmpFileName := Application.ExeName;
  delete( tmpFileName, length(tmpFileName) - 2, 3 );
  Application.HelpFile := tmpFileName +'chm';
end;

procedure Help_OnClick(Sender:TObject; var Cancel:boolean);
// обработка кнопки вызова помощи
var
  tmpForm:TAForm;
begin
  CForm(Sender,tmpForm);
  Application.HelpContext( Help_GetIDH( tmpForm.Name ) ); // получать ID из имени формы.
end;


Code language: PHP (php)

Кроме инициализации подсистемы помощи, данная процедура подключает для каждой формы обработчик горячих клавиш (21).

Для того, чтобы каждой форме сопоставить раздел помощи, потребуется функция Help_GetIDH(), которая в нашем случае должна преобразовывать названия формы в идентификатор. Выглядит она как простой case:

function Help_GetIDH( AName: string ):integer;
// получить индекс помощи по строковому идентификатору
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)

Для крупных проектов писать эту процедуру вручную долго, поэтому я решил добавить вспомогательную процедуру, которая генерирует текстовую заготовку оператора case, в которой останется только проставить актуальные ID (где их брать я расскажу чуть позже).

procedure Help_IDHGenerator;
// генерируем фрагмент кода для функции Help_GetIDH
// вызвать один раз
var
  i:integer;
  tmpList:TStringList;
  tmpDefIDH:string;
begin
  tmpDefIDH := '$02'; // код по умолчанию.
  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)

Для вызова помощи из главного меню потребуется простая процедура Help_Show(), которую можно использовать в качестве обработчика события при выборе пункта меню:

procedure Help_Show(Sender:TObject);
// отображение файла помощи
begin
  Application.HelpContents;
end;
Code language: Delphi (delphi)

Прочие доработки

Для добавления вызова помощи из главного меню потребуется модифицировать процедуру UserApp_InitForm(): добавить новую ветку, в которой будет отображаться вызов общей справки и перенести туда отображение формы “О программе”. Перенести у меня пока не получилось, так как процедура переноса узлов меню в MVDB получается довольно громоздкой, поэтому я решил скрыть старый пункт меню и добавить новый, добавив несколько полезных процедур:

procedure UserApp_InitForm;
// инициализация форм
var
  tmpItem:TMenuItem;
begin
  // сплиттеры
  Splitter_Create( frmItem.pgcDetail, frmItem.panMain, alBottom);
  Splitter_Create( frmOperDoc.pgcDetail, frmOperDoc.panMain, alBottom);
  // меню
  //
  tmpItem := Menu_Add('','Отчеты',nil,1, '-');
  Menu_Add('Show_frmBalance','Остатки',tmpItem);
  Menu_Add('Show_frmPBalance','Остатки (партии)',tmpItem);
  Menu_Add('Show_frmOperRep','Движение',tmpItem);
  //
  tmpItem := Menu_Add('','&Справочники',nil,1, '-');
  Menu_Add('Show_frmContr','&Контрагенты',tmpItem);
  Menu_Add('Show_frmItem','&Номенклатура',tmpItem);
  Menu_Add('Show_frmPItem','Партии',tmpItem);
  Menu_Add('Show_frmItemType','&Вид номенклатуры',tmpItem);
  Menu_Add('Show_frmUnit','&Единицы измерения ',tmpItem);
  Menu_Add('Show_frmDocType','Тип документов',tmpItem);
  //
  Menu_Add('Show_frmOperDoc','Журнал документов',frmMain.mniFile,0);
  Menu_Add('','-',frmMain.mniFile,1, '-');
  //
  tmpItem := Menu_Add('HelpTopic','?',nil,5, '-');
  Menu_Add('Help','Помощь',tmpItem,0,'Help_Show');
  Menu_Add('','-',tmpItem,1, '-');
  Menu_Add('AboutEx','О программе',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);
// скрытие пункта главного меню
var
  tmpForm:TAForm;
  tmpItem:TMenuItem;
begin
  tmpForm := MainForm; // работаем с меню на главной форме
  FindC(tmpForm,AItemName,tmpItem);
  tmpItem.Visible := False;
end;

procedure App_ShowCoreAbout(Sender:TObject);
// отображение стандартной формы "О программе"
begin
  frmdbCoreAbout.ShowModal;
end;
Code language: Delphi (delphi)

Кроме того, в процедуре frmMain_OnShow() необходимо вызвать процедуру инициализации подсистемы помощи Help_Init(), передав в неё в качестве параметра форму-заставку (строка 25).

procedure frmMain_OnShow (Sender: TObject; Action: string);
// отображение главной формы
var
  tmpForm:TForm;
  tmpStartTime: TTime;
  tmpSleepTime: integer;
begin
  // при необходимости проверяем лицензию
  License_Init;
  //
  tmpForm := Splash_Create;
  tmpForm.Show;
  Application.ProcessMessages;
  tmpStartTime := Time();
  //
  // тут должна быть всяческая инициализация, которая может занять продолжительное время
  App_InitSystemVar; // инициализация системных переменных
  Images_Init; // инициализация подcистемы загрузки картинок
  Style_Init; // активация стиля
  Style_CreateOptions; // добавить стиль в стандартное окно настройки
  App_InitAboutForm; // инициализация формы About
  //
  Help_Init( tmpForm ); // инициализировать систему помощи.
  //
  UserApp_InitForm; // создать формы
  UserApp_InitVar; // настроить переменные
  UserApp_InitBase; // инициализация базы данных
  // гарантированная задержка, но не более указанной в APP_SPLASH_DELAY
  tmpSleepTime := SPLASH_DELAY_MIN - Trunc( (Time() - tmpStartTime)*24*60*60*1000 );
  if tmpSleepTime > 0 then
    Sleep(tmpSleepTime);
  tmpForm.Close;
  tmpForm.Free;
  //
  // отобразить в заголовке главной формы название и версию программы
  frmMain.Caption := APP_NAME + ' '+App_GetVersion;
end;

Code language: Delphi (delphi)

Создание файла помощи

https://www.drexplain.ru

Попробовав различные инструменты для создания файлов с расширением .chm, я остановил свой выбор на программе DrExplain. Что же в ней особенного? Перечислю её достоинства, которые стали для меня решающими при выборе:

  • Русскоязычный интерфейс (программа доступна на 11 языках)
  • Подробная документация на русском языке
  • Встроенный HTML редактор
  • Генерация документа в один клик
  • Генерация документов в различных форматах (.chm,.html,.pdf,.rtf)
  • Настраиваемые шаблоны генерации для каждого формата
  • Встроенная система захвата и аннотирования изображений
  • Встроенная система проверки орфографии
  • Статусы готовности разделов
  • Гибкие настройки генераторов и среды

Эта программа интересна не только для индивидуального но и для корпоративного использования. У неё имеется возможность совместной работы над документацией, как через сервер разработчика, так и через собственный сервер. Имеется возможность публикации готовой документации на сервере разработчика.

Единственным недостатком данной программы является то, что она платная. Незарегистрированная версия добавляет в изображения водяные знаки, а на каждой странице красуется надпись “Unregistered version“. Но при коммерческом использовании данный многофункциональный инструмент вполне может оправдать затраты на его приобретение.

Подробней о самой программе, стоимости лицензий и условиях предоставления дополнительных онлайн сервисов можно узнать на сайте производителя: https://www.drexplain.com

Практическое использование DrExplain

Несмотря на удобство в работе, создание полноценной документации занимает много времени. Это необходимо учитывать при планировании разработки.

Сам процесс создания документации интуитивно понятен, что опять же является плюсом для программы DrExplain, пользовательский интерфейс которой близок к совершенству. Поэтому я не буду подробно описывать, как именно создавать документацию, а скажу только о главном.

Ключевым моментом документирования является интеграция справки с приложением: прописывание ID разделов в функции Help_GetIDH. Для этого находим нужный Hekp ID

и прописываем его в соответствующей строке процедуры, ассоциируя его с формой приложения.

DrExplain позволяет генерировать файлы для включения в проекты Delphi (*.inc), в которых находятся Help ID как перечень констант. Но особенность MVDB в том, что у него нет отдельной директивы для включения файлов .inc, а если использовать uses, то MVDB требует наличия в подключаемом файле ключевых слов begin end. Поэтому практической пользы от такой возможности в проектах MVDB я пока не вижу.

Результат

Добавил подсистему помощи и поменял версию “Производство” на 1.0. Запускаем приложение, жмем F1 и получаем актуальную помощь.

Осталось поднакопить денег и купить лицензию, чтобы избавиться от водяных знаков 🙂

2 комментария к «Help!»
  1. Добрый день. Можете пояснить строчку из статьи?
    ” Кроме того, в процедуре frmMain_OnShow() необходимо вызвать процедуру инициализации подсистемы помощи Help_Init(), передав в неё в качестве параметра форму-заставку.”

    1. Вячеслав, добавил текст процедуры frmMain_OnShow(), вызов Help_Init() в строке 25. Параметр определяет форму-заставку, на которую не нужно добавлять кнопку вызова помощи.

Добавить комментарий для Master Отменить ответ

Ваш адрес email не будет опубликован. Обязательные поля помечены *