Эй, люди, помогите! Вы мне нужны, поймите! Эй, мне нужен кто-нибудь.
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)
Создание файла помощи

Попробовав различные инструменты для создания файлов с расширением .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 и получаем актуальную помощь.

Осталось поднакопить денег и купить лицензию, чтобы избавиться от водяных знаков 🙂
Добрый день. Можете пояснить строчку из статьи?
” Кроме того, в процедуре frmMain_OnShow() необходимо вызвать процедуру инициализации подсистемы помощи Help_Init(), передав в неё в качестве параметра форму-заставку.”
Вячеслав, добавил текст процедуры frmMain_OnShow(), вызов Help_Init() в строке 25. Параметр определяет форму-заставку, на которую не нужно добавлять кнопку вызова помощи.