10 февраля 2011 г.

Как использовать справку в программах Delphi

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

Содержание:
  1. Как Delphi программы работают с файлами справки.
  2. Практика: указываем файл справки.
  3. Поддержка справки в Delphi программах.
  4. Практика: простой вызов статической справки.
  5. Практика: динамический вызов контекстно-зависимой справки.
  6. Обзор распространённых форматов файлов справок.
  7. Поддержка форматов справки в Delphi.
  8. Важное примечание: модальные окна.
  9. Заключение.

to topКак Delphi программы работают с файлами справки

Прежде чем ваша Delphi программа сможет показывать справку - вы должны сказать вашему приложению (т.е. программе), что она (справка) у вас вообще есть. Делается это установкой свойства HelpFile у объекта Application или формы.

В каждом VCL приложении Delphi есть глобальный объект Application ("Приложение") типа TApplication. У этого объекта есть свойства и методы, влияющие на всё приложение целиком. И для активации справки у него есть свойство Application.HelpFile.

Когда этому свойству присвоено значение, вы можете использовать методы и свойства объекта Application и ваших форм (окон), чтобы работать с вашей справкой. Значением свойства является имя файла справки вашей программы. Формат справки может быть любым из поддерживаемых вашим приложением (об этом чуть позже). Сейчас только отметим, что разные форматы файлов справок имеют разные возможности (и внешний вид!). Отличается также и поддержка этих форматов в самой Delphi. Поэтому, будет ли работать та или иная возможность, обсуждаемая ниже - будет зависеть от того, какой формат файла справки вы выберите. Но, опять-таки, об этом позже.

Итак, помимо объекта Application, свойство HelpFile есть и у объектов класса TForm. Когда приложению поступает команда по работе со справочной системой (например, пользователь нажал F1), то Delphi приложение будет использовать файл справки, указанный в Application.HelpFile для выполнения этой команды. Но если при этом активна форма, у которой задано её свойство HelpFile, то для выполнения команды будет использовать свойство формы, а не приложения.

Иными словами, Form.HelpFile просто изменяет Application.HelpFile в контексте одной конкретной формы. Если в вашей программе есть только один файл справки, то вы не должны устанавливать свойства HelpFile у форм, а установить свойство HelpFile у объекта Application.

to topПрактика: указываем файл справки

Вы можете установить свойство HelpFile программно (кодом) или указать в свойствах проекта.

Программно: Project / View Source
program Project1;

uses
  Forms,
  SysUtils, // <- Добавлено
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}

begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.HelpFile := ExtractFilePath(Application.ExeName) + 'Help\HelpFile.hlp'; // <- Добавлено
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.
В этом примере файл справки должен называться HelpFile.hlp и лежать в подпапке Help вашей программы. Разумеется, вы должны заменить эту строчку своими данными.

Через GUI: Project / Options / Application:


Здесь есть целых три проблемы:
  1. Нажатие на кнопку "Browse" покажет вам только *.hlp файлы. Чтобы выбрать файл другого формата, вам нужно выбрать в фильтре диалога открытия опцию "All Files (*.*)".
  2. Выбор файла справки кнопкой "Browse" занесёт в поле полный путь к файлу вида "C:\Users\Александр\Documents\RAD Studio\Projects\DemoProject\Help\HelpFile.hlp" - это не очень удачная идея, ведь на машине, куда ваша программа будет потом установлена, этот файл будет лежать в совершенно ином месте. Лучшая идея - обрезать путь до относительного: ".\Help\HelpFile.hlp".
  3. Проблема с предыдущим пунктом в том, что относительный путь считается относительно текущего каталога. Который обычно равен папке вашей программы, но далеко не всегда. Лучшим решением будет исправить этот момент так:
    Application.HelpFile := ExtractFilePath(Application.ExeName) + Application.HelpFile;
    Что даёт вам 100% гарантию работы, но... не проще ли просто использовать программную установку пути изначально, не трогая GUI?
Что касается индивидуальных файлов справок для форм - то к ним применимы те же слова: вы можете выставлять свойства программно:
procedure TForm1.FormCreate(Sender: TObject);
begin
  HelpFile := ExtractFilePath(Application.ExeName) + 'Help\MainForm.hlp';
end;
или через инспектор объектов:


И снова: проблемы с относительными/полными путями ведут к тому, что первый способ с программной установкой выглядит намного привлекательнее.

to topПоддержка справки в Delphi программах

Как только Delphi приложение узнает, что у него есть файл справки, оно даст вам семь стандартных способов показать справку:
  1. Нажатие F1
    Эта функция реализует контекстую справку. Когда пользователь нажимает F1, приложение ищет в файле справке тему (topic) с номером контекста (context number) или ключевым словом (keyword), заданными в свойствах компонента, имеющего фокус. Если найдена одна или несколько тем - то открывается файл справки, показывая эти темы в отдельном окне.
     
  2. Кнопка с вопросиком в заголовке окна
    Эта кнопка активирует контекстно-зависимый режим справки на форме - так называемая функция "What's this?" ("Что это такое?"). Когда пользователь нажимает на эту кнопку, форма временно отключается, а курсор меняет форму на стрелочку с вопросиком:
    Нажатие на любом элементе управления в этом режиме заставит приложение выполнить поиск темы в файле справки, ассоциированной с этим компонентом. Если тема найдена, то она будет показана во всплывающем окошке.

    Обычно этот способ не используется в современных программах. Исторически он появился в 16 разрядных Windows, когда ещё не было всплывающих подсказок. Сегодня же вместо этого функционала намного проще использовать обычное свойство Hint.
     
  3. Кнопка "Справка" с Kind = bkHelp


    Это простой способ вызвать справку, не создавая ни строчки кода. Нажатие на кнопку откроет тему с номером контекста или ключевым словом, заданными в свойствах либо самой кнопки, либо (обычно) формы.
     
  4. Application.HelpCommand
    Это гибкий метод, который позволяет вам выполнить любую команду с вашим файлом справки. Подробнее эта команда рассмотрена ниже.
     
  5. Application.HelpJump
    Это вспомогательная функция, которая является обёрткой к Application.HelpCommand с командой "jump". Она позволяет вам отобразить тему справки по её ID (имени).
     
  6. Application.HelpContext
    Это вспомогательная функция, которая является обёрткой к Application.HelpCommand. Она позволяет вам показать тему по так называемому номеру контекста (context number).
     
  7. Application.HelpKeyword
    Это вспомогательная функция, которая является обёрткой к Application.HelpCommand. Она позволяет вам показать тему по ключевому слову (keyword).
     
Функции 1, 2 и 3 работают автоматически. Вам не нужно ничего делать, кроме как привязать темы справки к компонентам (через номера контекстов или по ключевым словам). Как сделать такую привязку - чуть позже...

to topПрактика: простой вызов справки

... а пока мы разберём самые простые методы работы со справкой - показ фиксированных окон и тем без учёта контекста, используя последние четыре метода из списка в предыдущем пункте.

Каждое серьёзное Windows приложение имеет пункт меню "Справка" вроде такого:


Или хотя бы такого:


Когда вы нажимаете на подобные пункты меню - появляется окно, называемое (в терминах Microsoft) "Finder". Оно выглядит как-то так:


Или так (в этом случае окно Finder включает в себя и окно с темой):


Или так:


Как открыть справку

Итак, как вам открыть окно Finder? На самом деле, это очень просто: поместите этот код в обработчик нажатия пункта меню:
procedure TForm1.miHelpContentsClick(Sender: TObject);
begin
  Application.HelpCommand(HELP_FINDER, 0);
end;
Этот код покажет окно Finder на той вкладке, которая была открыта в последний раз.

Как открыть оглавление справки

Оглавление (Table Of Contents) - это первая вкладка окна Finder. Это так же просто, как открыть окно Finder, но команда будет выглядеть так:
procedure TForm1.miHelpContentsClick(Sender: TObject);
begin
  Application.HelpCommand(HELP_TAB, 0);
end;
Примечание: в некоторых версиях Delphi константа HELP_TAB не определена. В этом случае добавьте в подходящее место программы это определение:
const
  HELP_TAB = 15;

Примечание: в зависимости от используемой справочной системы эта команда также может сразу отобразить и тему по умолчанию.

Как открыть тему по умолчанию

Тема по умолчанию - это тема, которая была указана как тема по умолчанию в настройках файла справки. Если таковой не указано, то темой по умолчанию считается первая тема в оглавлении. Команда для открытия темы по умолчанию выглядит так:
procedure TForm1.miHelpDefaultClick(Sender: TObject);
begin
  Application.HelpCommand(HELP_CONTENTS, 0);
end;

Историческая справка
Что происходит, когда вы открываете старый файл справки из 16 битных Windows? Открывается окно, показывающее тему. Эта тема называется темой по умолчанию (default topic).

32 битный файл справки формата WinHelp состоит из двух файлов (16 битный - из одного): .HLP, который содержит темы и оглавление - .CNT. Если вы удалите .CNT файл и откроете .HLP файл, то увидите тему по умолчанию.

В прошлом веке, когда Windows 3.x ещё была передовыми технологиями, тема по умолчанию была единственным способом показать какой-то обзор (содержание). Эта тема обычно была первой темой в файле справки и содержала ссылки на другие темы. Вот почему Microsoft называла её "CONTENTS" (Оглавление). Если эта тема явно не задана, то темой по умолчанию считается первая тема в файле справки.

Вот почему команда
Application.HelpCommand(HELP_CONTENTS, 0);
НЕ покажет вам содержание, несмотря на название "CONTENTS". Это команда для открытия темы по умолчанию.

Как открыть индекс справки

Вкладка индекс (Index) показывает алфавитный список ключевых слов (keyword). Ключевые слова должны быть заданы в свойствах тем. Если ключевые слова вообще не определены - эта вкладка не показывается.

Следующий пример показывает, как можно открыть справку на вкладке индекса:
procedure TForm1.ShowKeywordIndex;
var
  Command: array[0..255] of Char; // примечание: этот код также работает с D2009+
begin
  Command := 'SEARCH()';
  Application.HelpCommand(HELP_FORCEFILE, 0);
  Application.HelpCommand(HELP_COMMAND, Longint(@Command));
end;

Примечание: вы также можете вставить ключевое слово (keyword) для поиска внутри скобок в этой команде, но гораздо проще использовать для этого готовый метод: Application.HelpKeyword (см. ниже).

Как открыть поиск по справке

Вкладка поиска (Find) показывает диалог полнотекстового поиска по справке. Если в файле справки отключены возможности полнотекстового поиска, то эта вкладка вообще не показывается.

Следующий пример открывает окно справки с активным окном поиска:
procedure TForm1.ShowFullTextSearch;
var
  Command: array[0..255] of Char; // примечание: этот код также работает с D2009+
begin
  Command := 'FIND()';
  Application.HelpCommand(HELP_FORCEFILE, 0);
  Application.HelpCommand(HELP_COMMAND, Longint(@command));
end;

Как открыть тему по имени

Для показа темы по имени вы можете использовать метод Application.HelpJump, который принимает один строковый параметр - ID (имя) темы. Например:
procedure TForm1.ShowTopicByName;
begin
  Application.HelpJump('mytopic');
end;
Этот метод возвращает True, если тема существует и была найдена, и False - в противном случае. Однако, в последнем случае также автоматически показывается сообщение пользователю о том, что тема не существует.
Если в вашей Delphi команды HelpJump нет, то вот её код:
function TForm1.HelpJump(const JumpID: string): Boolean;
var
  Command: array[0..255] of Char;
begin
  Result := True;
  if InvokeHelp(HELP_CONTENTS, 0) then
  begin
    StrLFmt(Command, SizeOf(Command) - 1, 'JumpID("","%s")', [JumpID]);
    Result := Application.HelpCommand(HELP_COMMAND, Longint(@Command));
  end;
end;


Как открыть тему по номеру контекста

А для показа темы по контекстному номеру вы можете использовать метод Application.HelpContext, который принимает один числовой параметр - номер контекста. Например:
procedure TForm1.ShowTopicByNumber;
begin
  Application.HelpContext(2);
end;
Аналогично, этот метод возвращает True, если тема существует и была найдена, и False - в противном случае.
Если в вашей Delphi команды HelpContext нет, то вот её код:
function TForm1.HelpContext(const AContext: Integer): Boolean;
begin
  Result := Application.HelpCommand(HELP_CONTEXT, AContext);
end;


Обычно вы не вызываете эту команду вручную - ведь вы можете просто ассоциировать компонент с темой, используя свойства (про это - ниже).

Эта команда покажет тему в отдельном окне. Если вы хотите использовать всплывающее окно - используйте другую команду:
procedure TForm1.ShowTopicByNumberInPopup;
begin
  Application.HelpCommand(HELP_CONTEXTPOPUP, 2);
end;

Как открыть тему по ключевому слову

Интересовались ли вы когда-нибудь, как работает справка среды Delphi? Когда вы выделяете свойство компонента в инспекторе объектов или устанавливаете на него курсор в коде и нажимаете F1, то вы получаете описание этого свойства.

Это работает при помощи ключевых слов. Когда вы нажимаете F1, в справке ищутся темы с ключевым словом, которое указано под курсором или в инспекторе объектов. Для этого файл справки должен содержать список ключевых слов, ассоциированных с темами. Вы задаёте это при создании файла справки (указывая ключевые слова в свойствах темы).

Для показа темы по ключевому слову используется метод Application.HelpKeyword, который принимает один строковый параметр - ключевое слово. Например:
procedure TForm1.ShowTopicByKeyword;
begin
  Application.HelpKeyword('example');
end;
Примечание: если вам нужно отобразить вложенное ключевое слово, то нужно разделять их ровно двумя пробелами:
procedure TForm1.ShowTopicByNestedKeyword;
begin
  Application.HelpKeyword('root  sub');
end;
Если в вашей Delphi команды HelpKeyword нет, то вот её код:
function TForm1.HelpKeyword(const AKeyword: String): Boolean;
var
  Command: array[0..255] of Char;
begin
  StrLcopy(Command, PChar(AKeyword), SizeOf(Command) - 1);
  Result := Application.HelpCommand(HELP_KEY, Integer(@Command));
end;


А как теперь закрыть справку

Нет ничего проще:
procedure TForm1.CloseHelp;
begin
  Application.HelpCommand(HELP_QUIT, 0);
end;

to topПрактика: вызов справки с контекстом

Контекстная справка (context sensitive help или contextual help) даёт мгновенную поддержку пользователям, не заставляя их покидать контекст, в котором они работают. Она показывает информацию о конкретном объекте, с которым работает пользователь. Обычно она отвечает на вопросы вроде "Что это такое?", "Зачем это использовать?" и "Что и как с этим делать?". Она очень полезна именно тем, что пользователь не отключается от процесса работы. Основные способы для контекстной справки включают в себя:
  • Контекстные темы в файле справки
  • Кнопки вызова справки (например, кнопка Help рядом с OK и Cancel в диалогах)
  • Всплывающие подсказки (показываются при наведении мыши на объект)
  • Сообщения в строке статуса (StatusBar)
Когда я говорю про контекстную справку в этом посте, я имею в виду первые два пункта. Вторые два пункта в этой статье не рассматриваются. Второй пункт реализуется либо ручным вызовом справки одним из методов, которые мы рассмотрели выше, либо назначением кнопки вида (Kind) bkHelp (обработчик при этом реализовывать не требуется).

Итого, сейчас нам осталось поговорить про первый пункт.

Как включается контекстная справка

Контекстная справка обычно вызывается одним из трёх основных способов:
  • Нажатие F1, когда фокус ввода находится на каком-то элементе управления. Это основной способ показа контекстной справки. Когда пользователь застревает, он нажимает F1, а программа показывает ему тему справки, в зависимости от того, где он находится. Этот способ поддерживается Delphi и вам не нужно писать для него код.
     
  • Функция "What's this?". Это либо кнопка с вопросиком в заголовке окна, либо аналогичная кнопка с вопросиком на панели инструментов (Toolbar-е), либо пункт меню Справка/Что это такое?. Как я уже говорил, это старый режим работы со справкой. Я не буду его особо рассматривать, потому что в современных программах гораздо проще и удобнее использовать всплывающие подсказки.
     
  • Кнопка с Kind = bkHelp или аналогичный способ. Этот способ нельзя полностью отнести к контекстой справке - ведь когда вы щёлкаете на кнопку, то фокус уходит с текущего элемента управления. Тем не менее, я перечисляю этот способ, как ещё один вариант автоматического вызова справки без написания кода.
     

Как реализуется контекстная справка

Показ темы в контекстно-зависимой справке требует идентификации темы по числовому номеру, называемому номером контекста (context number). В более поздних версиях Delphi также возможен показ контекстой справки по ключевым словам. По умолчанию в файле справки не задаются ни номера контекстов, ни ключевые слова. Вы должны либо вводить их вручную, либо (только для номеров контектов) вы можете включить в опциях автоматическую нумерацию (в некоторых продвинутых редакторах файлов справок, типа Help & Manual).

Возможно, вы уже заметили эти свойства в инспекторе объектов - это свойство HelpContext и свойство HelpKeyword:


Свойство HelpType определяет, какое свойство нужно использовать для вызова контекстной справки. Обычно используются номера контекстов, а не ключевые слова - этот вариант является вариантом по умолчанию для свойства HelpType.

Для работы контекстной справки или эти номера (не важно - созданные вами или проставленные автоматически) или ключевые слова должны быть присвоены темам и установлены в свойства компонентов на форме. Номера должны быть уникальны как в файле справки, так и в Delphi проекте. Вы можете присвоить один и тот же номер двум и более компонентам - но это будет означать, что эти компоненты привязаны к одной и той же теме. В отличие от номеров контекста, ключевые слова не обязаны быть уникальными. Если с одним ключевым словом связано более одной темы, то вам будет показано окно, из которого вы сможете выбрать нужную тему.

Когда пользователь вызывает контекстную справку (любым из способов, описанных в предыдущем пункте - либо через F1, либо через функцию "What's this?", либо кнопкой "Справка") - то показывается тема справки, контекстный номер которой указан в свойстве HelpContext. Либо же, если HelpType установлено в htKeyword, то показывается тема(ы) с ключевым словом из свойства HelpKeyword. Для работы контекстной справки вам не нужно делать ничего, кроме как проставить номера контекстов темам и компонентам. Всю работу Delphi сделает автоматически.

Вам не нужно проставлять номера контекстов или ключевые слова абсолютно всем компонентам на форме - ведь свойства HelpContext и HelpKeyword наследуется. Т.е. если вдруг у компонента нужное свойство не задано (равно нулю или пустой строке соответственно), то используется свойство HelpContext/HelpKeyword его родителя (в смысле Parent). Если же это свойство не задано и у родителя - то проверяется свойство родителя родителя. И так далее, вплоть до формы. Если же свойство не задано и у формы, то контекстная справка вообще не вызывается. Если вы используете кнопку вызова справки с Kind = bkHelp, то вы обязаны присвоить номер контекста или ключевое слово либо ей, либо форме.

Итак, суммируя: чтобы в вашей программе работала контекстная справка, вам нужно:
  1. Вы должны указать приложению, что у вас есть файл справки. Это общее действие для любого типа справки в Delphi приложении.

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

     
  3. Вы должны присвоить контекстные номера (либо ключевые слова) форме и компонентам на ней в вашем Delphi приложении, чтобы ассоциировать элементы управления в приложении с темами справки. Как минимум, вы должны указать эти номера (ключевые слова) для форм.

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

to topОбзор распространённых форматов файлов справок

На сегодняшний день существуют такие форматы справок (из самых распространённых):
  • 16 битный Windows Help aka WinHelp 1.0 (.HLP) - проприетарный формат файлов справки, разработанный компанией Microsoft для организации справочной системы. Исходная информация подготавливается в форматах RTF (текст) и BMP (изображения), а затем с использованием компилятора генерируется бинарный файл с расширением HLP. Поддержки Unicode нет. Может быть декомпилирован в исходные файлы. Разработан в 1990 и поддерживается в Win16, Win32, WinNT до XP включительно. Устарел в 1995. Ограничено доступен в Windows Vista, Windows 7, Windows 8 и Windows 8.1 - поддержка для него должна быть установлена дополнительно. Не доступен в Windows 10 и выше. Сегодня в здравом уме не используется никем.
     
  • 32 битный Windows Help aka WinHelp 2.0-4.0 (.HLP + .CNT) - улучшенный вариант формата .HLP, появившийся в Windows 95. Поддерживается в Win32/WinNT до Windows XP включительно. Поддержки Unicode нет. Устарел в 2006. Ограничено доступен в Windows Vista, Windows 7, Windows 8 и Windows 8.1 - поддержка для него должна быть установлена дополнительно (в этот run-time не включены некоторые возможности - например, макросы). Не доступен в Windows 10 и выше. Сегодня в основном используется старыми приложениями или теми, кому нужны специальные функции формата WinHelp.
     
  • Compiled HTML Help aka HTML Help 1.0-1.4 (.CHM) - основной и самый популярный формат файлов справок, выпущенный в 1997 и поддерживаемый начиная с Windows 98 во всех Windows, включая Windows Vista, Windows 7, Windows 8, Windows 8.1 и Windows 10. Представляет собой скомпилированный файл, полученный из обычных HTML-файлов со специальной разметкой. По этой причине формат стал популярным для создания e-books. Может быть декомпилирован в исходные файлы. Однако в этот формат уже не вносятся изменения и новые возможности, а лишь устраняются найденные уязвимости. Последняя (актуальная) версия формата: 1.4. Microsoft планирует заменить его на какой-нибудь другой формат в будущем. Поддержка Unicode ограничена.
     
  • Assistance Platform 1.0 (.H1S) - достаточно специфичный формат справки, поскольку используется только для расширения встроенной справки самой ОС OEM партнёрами. Понятно, что он не предназначен для общего использования.
     
  • Microsoft Help aka HTML Help 2.0 (.HXS) - формат справки, используемый Visual Studio 2002/2003/2005/2008 и последними версиями Delphi. Разработан в 2001. Поддерживает Unicode. Сжатый файл .HxS получается из набора тем, написанных в HTML (похоже на .CHM). В отличие от всех прочих форматов, этот формат справки НЕ предназначен для программ общего назначения. Дело в том, что просмотрщик справки этого формата (DExplore - "Document Explorer") не входит в комплект ни одной ОС Windows на сегодняшний день. Поддержка справки этого формата (run-time) устанавливается только вместе с Visual Studio или Delphi. Плюс ко всему вы не можете таскать вместе со своей программой установщик для этой справки (согласно лицензии).
     
  • Microsoft Help System 1.x aka HTML Help 3.0 (.MSHC) - формат справки, используемый Visual Studio 2010. Не путать с Microsoft Help (.HXS)! Разработан в 2009. Поддерживает Unicode. Аналогично формату HTML Help 2.0/HXS - НЕ предназначен для программ общего назначения, хотя ограничений на просмотрщик уже нет (справку можно просматривать в любом браузере), но run-time по прежнему требуется. Файл .MSHC является обычным переименованным .ZIP файлом. Компилятор не требуется - файлы содержимого, содержащие ссылки друг на друга, просто укладываются в архив. Сильные стороны формата: открытость, быстрота, простота и прозрачная интеграция с online-справкой.
     
  • Microsoft Help System 2.x (.MSHC) - формат справки, используемый Visual Studio 2012 (VS11), 2013 и 2015, а также в Windows 8, 8.1 и, частично, Windows 10. Не путать с Microsoft Help (.HXS)! Не путать с HTML Help 2.0! Разработан в 2012. По сути, это тот же формат, что и предыдущий (Help System 1.x/HTML Help 3.0), основанный на .MSHC файлах (т.е. переименованный .ZIP). Формат файлов не меняется (обратно-совместим), меняется только run-time (API заменён на COM-интерфейсы). Более того, этот run-time теперь является встроенным в Windows, начиная с Windows 8 (Windows.Help.Runtime.dll) - хотя встроенный в Windows просмотрщик (viewer) является базовым и не имеет многих возможностей полноценного просмотрщика, поставляемого с Visual Studio 2012. Предполагается, что разработчики будут создавать свои решения на основе предлагаемого COM API. Этот формат справки имеет потенциал заменить собой .CHM и стать новой стандартной системой справки в будущем.
     
  • HTML (.HTML) - представляет собой просто набор обычных HTM/HTML файлов. Понятно, что такой формат достаточно ограничен и обычно не имеет никаких преимуществ перед .CHM - окромя полной поддержки Unicode.
     
  • Portable Document Format (.PDF) - формат, созданный Adobe Systems в 1993. Обычно этот формат используется для печати справки (ещё точнее - руководства) на бумаге, а не как файл справки в программе.
     
  • Web/Online - вообще не файловая справка. Представляет собой открытие web-сайта со страничкой справки на нём.
     
Официально рекомендации Microsoft на октябрь 2010-го выглядят так:
  • Не используйте .HLP.
  • Для программ общего назначения используйте .CHM.
  • Если вы OEM партнёр MS, который хочет расширить центр справки и поддержки Windows в Windows Vista или Windows 7 - то используйте .H1S.
  • Если вам нужна интеграция в справочную систему Visual Studio 2002 – 2008 (примечание от меня, не от MS: или Delphi), то используйте .HxS.
  • Если вам нужна интеграция в справочную систему Visual Studio 2010 - 2015, то используйте .MSHC.
  • .H1S, .HxS и .MSHC не доступны для программ общего назначения, а .HLP устарел.
P.S. Начиная с Windows 8 в ОС есть run-time для .MSHC, но нет толкового просмотрщика. Документация в MSDN скудна, а перспективы формата - туманны.

Итак, если подытожить - для Delphi программ у вас есть выбор из трёх вариантов:
  • .HLP (WinHelp 4.0)
  • .CHM (HTML Help 1.x)
  • Web/Online
Из них .CHM и Web/Online являются основными, а .HLP используется только в особых случаях. Учтите, что справка в .HLP не будет работать на Windows 10 и более поздних системах, а для её работы в Windows Vista, Windows 7, Windows 8 и Windows 8.1 пользователю необходимо вручную установить недостающие компоненты операционной системы (они обрезаны, нет поддержки макросов, например).

to topПоддержка форматов справки в Delphi

.HLP

Поддержка этого формата есть во всех версиях Delphi. В Delphi 7 и ниже вам ничего специально делать не надо. В Delphi 2005 и выше вам нужно подключить модуль WinHelpViewer в любую секцию uses.

.CHM

Штатная поддержка этого формата есть в Delphi 2005 и выше. Для этого вам нужно только указать в любом uses модуль HTMLHelpViewer (как обычно: чем раньше - тем лучше).

Примечание: не подключайте одновременно WinHelpViewer и HTMLHelpViewer. Либо первый, либо второй, либо ни один, но не оба сразу.

Для Delphi 7 и ниже вы можете использовать моё решение. Использовать его не менее просто - просто распакуйте архив в папку вашего проекта и добавьте модуль HTMLHelpViewerEx в любой uses вашего проекта.

Это решение также может быть использовано в Delphi 2005 и выше, если вас не устраивает стандартное решение (модуль HTMLHelpViewer). И снова: подключайте в uses только один модуль (либо WinHelpViewer, либо HTMLHelpViewer, либо HTMLHelpViewerEx, либо ни одного).

См. табличку ниже для сравнения возможностей форматов и их реализации.

Web/Online

Поскольку стандарта на эти системы справок не существует, то в Delphi нет никакой стандартной поддержки справки этого формата. Вам придётся делать её самостоятельно. Проще всего это сделать, назначив обработчик события Application.OnHelp. В обработчике вам нужно организовать реакцию на команды, которые реально использует ваше приложение, например:
type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    function ApplicationHelp(Command: Word; Data: Longint; var CallHelp: Boolean): Boolean;
    ...
  end;

...

procedure TForm1.FormCreate(Sender: TObject);
begin
  Application.OnHelp := ApplicationHelp;
end;

function TForm1.ApplicationHelp(Command: Word; Data: Longint; var CallHelp: Boolean): Boolean;

  procedure OpenURL(const AURL: String);
  var
    SEI: TShellExecuteInfo;
  begin
    FillChar(SEI, SizeOf(SEI), 0);
    SEI.cbSize := SizeOf(SEI);
    {$IFDEF UNICODE}
    SEI.fMask := SEE_MASK_UNICODE;
    {$ENDIF}
    SEI.Wnd := Handle;
    SEI.lpVerb := 'open';
    SEI.lpFile := PChar(AURL);
    SEI.nShow := SW_SHOWNORMAL;
    if not ShellExecuteEx(@SEI) then
      RaiseLastOSError;
  end;

var
  DataStr: String;
begin
  // Пока установим признак успешного выполнения
  Result := True;

  case Command of
    // Открытие заголовочной страницы
    HELP_FINDER, HELP_TAB, HELP_CONTENTS:
      OpenURL(Application.HelpFile);
    // Поиск по ключевому слову
    HELP_KEY:
      OpenURL(Application.HelpFile + '?q=' + PChar(Data));
    // Открытие темы по номеру
    HELP_CONTEXT, HELP_CONTEXTPOPUP:
      OpenURL(Application.HelpFile + '/article' + IntToStr(Data) + '.html');
    // Расширенные команды:
    HELP_COMMAND:
    begin
      DataStr := PChar(Data);

      // Открытие индекса
      if StartsStr('SEARCH(', DataStr) then
        OpenURL(Application.HelpFile)
      else
      // Открытие поиска
      if StartsStr('FIND(', DataStr) then
        OpenURL(Application.HelpFile)
      else
      // Переход к теме по её имени
      if StartsStr('JI(', DataStr) then
      begin
        // Обрезали 'JI('
        DataStr := Trim(Copy(DataStr, 4, MaxInt));
        // Обрезали ')'
        SetLength(DataStr, Length(DataStr) - 1);

        OpenURL(Application.HelpFile + '/' + DataStr + '.html');
      end
      else
      // Прочие команды - считаем поиском по ключевому слову
        Result := ApplicationHelp(HELP_KEY, Integer(DataStr), CallHelp);
    end;
    // HELP_QUIT: - здесь можно закрыть окно браузера
    // ... <- тут прочие команды, если надо
  else
    // Все прочие команды обрабатывать не умеем - указываем, что завершились неудачно
    Result := False;
  end;

  // Мы сделали всю работу, делать больше нечего
  CallHelp := False;
end;
Обычно ключевыми командами являются HELP_KEY (а также её обёртка через HELP_COMMAND), HELP_CONTEXT/HELP_CONTEXTPOPUP, HELP_COMMAND с командой 'JI', а также какая-то команда "открытия справки вообще" (например, HELP_FINDER).

Событие Application.OnHelp вызывается при выполнении любой команды со справкой в вашей Delphi программе, позволяя вам отреагировать на неё и открыть web-страничку.

Только не забудьте установить свойство HelpFile! Иначе Delphi программа будет считать, что у вас нет справки. Вы можете установить в это свойство URL справочной системы (как это предполагается в примере выше). К примеру, для примера выше вы можете установить свойство в 'http://www.google.com/search'. Только понятно, что открытие темы по контекстному номеру или имени работать не будет. Зато открытие темы по ключевому слову приведёт к поиску этого слова в Google.

Сравнение возможностей форматов

Как я говорил выше, поддерживается ли та или иная команда - зависит от выбранного формата и версии Delphi. Я просуммирую основные возможности, которые мы рассматривали, на примере Delphi 7 и Delphi XE. Для наглядности, я добавил колонку с web-справкой, где + и - стоят по примеру чуть выше. Вы должны понимать что это только пример и ваша конкретная реализация может иметь как больше возможностей, так и меньше.

Я думаю, что табличка достаточно понятна, за исключением колонки D7/DXE - это я так обозначил стороннее (не штатное) решение с подключением модуля HTMLHelpViewerEx.

Возможность.HLP.CHMWeb
Команда или методОписаниеDelphi 7Delphi XEDelphi 7Delphi XED7/DXEПример выше
HelpJumpТема по ID + + - + + +
HelpContextТема по контексту + + - + + +
HelpKeywordТема по ключевому слову 1 1 - + + +
HELP_CONTENTSТема по умолчанию + + - + + +/-
HELP_FINDERОткрыть окно справки + + - - + +/-
HELP_TABСодержание + + - - + +/-
SEARCH()Индекс + + - - + +/-
FIND()Поиск + + - - + +/-
HELP_QUITВыход + - - + + -

+ означает поддержку возможности, - означает отсутствие поддержки, +/- означает, что сама команда работает, но её результат ничем не отличается от обычного открытия справки, а цифра - частичную поддержку (баг) со ссылкой на обходной путь (код ниже).

Исправление/обходной путь для 1:
procedure TForm1.btByKeywordClick(Sender: TObject);
var
  SaveOld: String;
  OldDir: String;
begin
  SaveOld := Application.HelpFile;
  OldDir := GetCurrentDir;
  try
    ChDir(ExtractFilePath(Application.HelpFile));
    Application.HelpFile := ExtractFileName(Application.HelpFile);

    Application.HelpKeyword('example');

  finally
    Application.HelpFile := SaveOld;
    SetCurrentDir(OldDir);
  end;
end;

to topВажное примечание: модальные окна

К сожалению, в Delphi имеется довольно неприятная особенность. Вспомните, как Delphi реализует модальные окна: она перебирает все окна и отключает (disable) их, после чего показывает (якобы модальное) окно обычным образом и делает локальный цикл выборки сообщений. Отключенные окна не реагируют на ввод пользователя, поэтому создаётся эффект модальности.

Проблема состоит в том, что среди отключаемых окон может находится и окно справки. Иными словами, если вы показываете какое-либо окно модально - то оно будет модально и по отношению к уже открытому окну справки. В результате вы просто не сможете работать с окном справки, визуально оно как бы "висит".

Мне не приходит в голову удачного решения этой проблемы, кроме как не использовать модальные окна или закрывать окно справки перед показом модального окна. На StackOverflow есть решение, но оно, на мой взгляд, не корректно: предлагается не вызывать HH_INITIALIZE, но этого строго требует документация MSDN. Вы можете попробовать закомментировать строчки с HH_INITIALIZE/HH_UNINITIALIZE, но делаете вы это на свой страх и риск.

to topЗаключение

Надеюсь, что эта статья поможет вам с интеграцией справки в ваши программы.

В этом руководстве были рассмотрены стандартные возможности использования справочных систем в Delphi, универсальные для любых установленных систем. Однако возможности конкретного формата справки этим не исчерпываются. К примеру, вы можете позиционировать окна справки (ну, например, так, чтобы окно справки всегда было сбоку вашего главного окна), вы можете получать обратную связь от окна справки (к примеру, нажатие на ссылку или кнопку в окне справки приводит к действию в вашей программе) и делать множество других вещей, недоступных через общий механизм в Delphi. Все эти расширенные возможности реализуются обращением к справочной системе напрямую, минуя обёртку Delphi. См. ссылки ниже для дополнительной информации. К примеру, для формата .CHM вас будет интересовать SDK и руководство по продвинутому использованию .CHM.

Если у вас всё ещё есть вопрос, а как же создавать сами файлы справки, то я вам рекомендую воспользоваться программой Help & Manual - это лучшее решение для создания справки в форматах HLP, CHM, HXS, PDF, HTML, RTF и E-book.

Вы можете создавать файлы справки в HLP и CHM и вручную, используя только бесплатные компиляторы (Microsoft Help Workshop и HTML Help Workshop соответственно).

См. также:
  1. Создание файлов справки:
  2. Использование файлов справки:

26 комментариев :

  1. В свете последних версий все же стоило упомянуть ICustomHelpViewer. Что позволяет реализовывать какой угодно формат справки, хоть свой собственный. У нас например это зип архив с html и картинками который показывается собственными средствами.

    ОтветитьУдалить
  2. Просто статья говорит о справке с клиентской стороны. О том, как можно использовать в своей программе справку.

    ICustomHelpViewer (появившийся, кстати, аж в Delphi 6, а вовсе не в последних версиях) предназначен для разработчика справочной системы, а не её клиента/потребителя.

    Если кому-то нужна в его программе реализация не стандартного формата справки, то ему вовсе не нужно для этого разрабатывать свою справочную систему. Для этого достаточно просто назначить обработчик события Application.OnHelp - вот и всё.

    Это существенно проще, чем писать свою реализацию ICustomHelpViewer.

    ОтветитьУдалить
  3. Разбирался как-то с этим CHM, довольно неплохая штука. Есть правда парочка косяков с путями начинающимися с # и еще кое-какие мелочи, но на сегодняшний момент ничего лучше CHM не встречал, возможно что-то и придумали уже. Но в итоге это все равно не потребовалось, т.к. на работе сказали "а кому оно нафиг надо, есть дока на PDF". Так что в раельной жизни чаще побеждает принцип "не воняет - не трогай". По поводу программы HTM2CHM - из плюсов только бесплатность и относительная легкость, но из-за нее же и нельзя сделать что-то сложное. Например полнотекстовый поиск, вывод страниц для ветвей, указать стартовую страницу (хотя это может я в ней не смог разобраться?!?). За ссылку на http://www.intbook.info/C/Glava_08/Index0.htm премного благодарен! Долго искал внятный хелп для Microsoft Help Workshop, по-моему он единственный из бесплатных который может делать все фишки CHM, но сама программа какая-то громоздкая и неудобная.

    ОтветитьУдалить
  4. Все таки интересно насчет Д7 и .CHM
    С твоим решением минусы в табличке стают плюсами? Или их никак не связать.
    Просто надо уходить от справки в вордовском файле :)

    ОтветитьУдалить
  5. Не понял вопроса. Кто чем становится?

    В табличке сравнения уровня поддержки в колонке Delphi 7 показаны возможности штатных средств Delphi 7.

    В колонке Delphi XE показаны штатные возможности Delphi XE.

    В колонке D7/DXE, как я и пояснил выше, показаны возможности модуля HTMLHelpViewerEx.

    ОтветитьУдалить
  6. Я когда то протестировал много утилит для создания help’ов: Help & Manual - это действительно лучшее решение! Работать удобно, а затем одним движением позволяет сгенерить и HLP для программы и доку в формате PDF.

    ОтветитьУдалить
  7. Блин, не достаточно внимательно прочитал. Сори.

    ОтветитьУдалить
  8. Очень полезная статья! Спасибо!

    ОтветитьУдалить
  9. Большое спасибо за статью!
    Нашел косячок при использовании модуля HTMLHelpViewerExt - в Delphi7.
    Справка в формате chm.
    При закрытии вызванной контекстной справки (по F1 на выбранном элементе) - пропадают открытые модальные окна программы, активное становится главное окно, исправляется только повторным переходом на программу по Alt-Tab.

    ОтветитьУдалить
    Ответы
    1. Исправлено в новой версии.

      Также добавил примечание про модальные окна.

      Удалить
  10. У нас в проекте на прошлой работе тоже модальные окошки "проваливались". Непонятно почему это происходило, видимо это какой-то косяк Делфи (работали с 7-й версией), а может еще что. Обычно это происходило на больших нагрузках, а разобрались потом с ней или нет не помню. Скорей всего забили.

    ОтветитьУдалить
  11. Статья оказалась полезной.
    Столкнулся со следующей проблемой.
    Использую справку CHM в программе и в форме динамически подключаемой библиотеки DLL.
    Приходится прописывать модуль HTMLHelpViewer и в программе, и в модуле библиотеки.
    Вызываю форму из библиотеки и тут же ее закрываю - приложение глухо виснет! Это происходит только если не были вызваны функции Application.Help.... Пока обошелся "заплаткой": при входе в форму библиотеки вызываю фиктивную функцию
    Application.HelpCommand(-1, -1);
    и тогда уже "Отмена" не вешает приложение.
    В чем может быть дело?
    Подозреваю, что выполняемая секция finalization модуля HTMLHelpViewer, который загружается в динамически подключаемой библиотеке, разрушает или пытается разрушить что-то в основном приложении, чего не следует.

    ОтветитьУдалить
  12. А к Delphi 5 возможно прикрутить CHM-справку?

    К сожалению, предложенный вариант не работает, т.к. в Delphi 5 нет модулей WinHelpViewer, HelpIntfs, а также ошибка - неизвестный интерфейс IInterface.

    ОтветитьУдалить
    Ответы
    1. Обновил модуль, добавил поддержку ещё более старых IDE.

      Удалить
    2. Попробовал на D6.
      контекстная помощь работает на ура.
      Одна проблема - если вызвать помощь по F1, то то все нормально до выхода из программы.
      При выходе программа вылетает с исключением "program faulted with message 'access violation at 0xXXXXXXXX: read of address 0xXXXXXXXX. Process Stopped.
      Правда, происходит это только при запуске из-под IDE.
      В откомпилированной и отлинкованной программе все нормально.
      Спасибо.

      Удалить
    3. Место возбуждения и стек вызова (View / Debug Windows / Call Stack) не приведёте? У меня на D6 / Win10 не воспроизводится.

      Удалить
    4. Там очень коротко под XP:
      @Halt0

      При пошаговом исполнении вылет происходит на последнем End. в программе, который следует непосредственно после Application.Run.
      Если на этом End. нажать F7 то попадаю в эту самую _Halt0 (в юните System), где и происходит вылет на строчке ExitProcess(ExitCode);
      Но глубже не лез.
      В целом, это никак не сказывается в откомпиленной программе, толь под IDE в режиме отладки.

      Удалить
  13. Спасибо за помощь. Информация очень помогла при разработки проекта в Д7.

    ОтветитьУдалить
  14. Прошу прощения, что поднимаю древнюю тему, но… поиск по сайту не дал ответ на мой вопрос.
    Александр, на каких условиях (в рамках какой лицензии) вы предоставляете Ваше решение (HTMLHelpViewerEx)? Можно ли его использовать в проприетарных продуктах и какие при этом надо выполнить условия?

    ОтветитьУдалить
  15. А нету у меня никаких условий. Всё моё в моём блоге можно использовать как угодно.

    ОтветитьУдалить
  16. > А нету у меня никаких условий. Всё моё в моём блоге можно использовать как угодно.
    Александр, спасибо за ответ (хоть в чём-то у нас взгляды сходятся :) ). Но, видете ли, в случае проверки кода соответствующими организациями, я не смогу сослаться на эту запись в Вашем блоге. Увы.
    Вот если бы в модулю был приложен readme или, что лучше, в самом модуле было бы указано, что он распространяется на условиях модифицированной BSDL (или какой-нибудь другой пермиссивной лицензии, которая Вам более нравится)…
    Дело в том, что без такого указания закон считает, что Вы оставляете все права на код за собой и публикуете его только для ознакомления. Т.е. ни я, ни кто другой не можем любым способом использовать Ваш модуль в своих приложениях. Иначе, мы нарушаем Ваши права. И — увы — Вас даже не известят об этом, но воспользовавшемуся Вашим модулем придётся туго.

    ОтветитьУдалить
  17. Добрый день! Отличная статья, воспользовался вашим HTMLHelpViewerEx.
    Но появилась проблема - подскажите пожалуйста в чем дело, работаю на Delphi7.
    При подключении HTMLHelpViewerEx, приложение остается в процессах после его закрытия. Как только убираю HTMLHelpViewerEx из проекта - приложение из процессов уходит.
    Что мне нужно сделать?

    ОтветитьУдалить
  18. "LCL Lazarus. Разработка справочной системы." и ссылки на вэбдельфи мертвы

    ОтветитьУдалить
    Ответы
    1. Вообще-то работают. Может http://www.webhelpi.ru/ был на обслуживании?

      Но я нашёл две другие мёртвые ссылки, заменил. Спасибо.

      P.S. Также добавил немного новой информации по Help System 2.x.

      Удалить

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и (опционально) ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.

Примечание. Отправлять комментарии могут только участники этого блога.