Categories: wxWidgets

Кросс-платформенная справочная система для кросс-платформенных приложений – Часть I

Сегодня я хочу рассказать об одной из интересных функциональных возможностей wxWidgets, а именно, о работе со справочной системой. Библиотека wxWidgets позволяет организовать работу со справочной системой, прилагая минимум усилий. Обеспечение работы со справкой с помощью wxWidgets занимает куда меньше времени, чем написание самой справки.
Также интересным является тот факт, что wxWidgets использует распостраненный формат описания структуры справочной системы, а именно те файлы, которые можно сгенерировать с помощью утилиты HTML Help Workshop. К слову сказать, это достаточно распространенная практика… так, например, инструментарий документирования исходного кода Doxygen также позволяет создавать файлы проектов HTML Help Workshop, с помощью которых можно сгенерировать справку в формате .chm
wxWidgets позволяет организовать работу со справочной системой для приложений, работающих под управлением любой из поддерживаемых операционных систем. А о том, как это всё работает, рассказано ниже.

Для начала создадим простенькое приложение:

HelpTest.cpp

#include <wx/wx.h>

class MyFrame : public wxFrame
{
 void CreateControls();
public:
 MyFrame();
 bool Create(wxWindow * parent);

 DECLARE_EVENT_TABLE()
 void OnExit(wxCommandEvent & event);
};

BEGIN_EVENT_TABLE(MyFrame, wxFrame)
EVT_MENU(wxID_EXIT, MyFrame::OnExit)
END_EVENT_TABLE()

MyFrame::MyFrame()
{
 Create(NULL);
}

bool MyFrame::Create(wxWindow * parent)
{
 bool res = wxFrame::Create(parent, wxID_ANY, wxT("Help System Test"));
 if(res)
 {
  CreateControls();
 }
 return res;
}

void MyFrame::CreateControls()
{
 wxMenuBar * menuBar = new wxMenuBar;
 SetMenuBar(menuBar);

 wxMenu * fileMenu = new wxMenu;
 fileMenu->Append(wxID_OPEN, _("Open\tCtrl+O"));
 fileMenu->Append(wxID_SAVE, _("Save\tCtrl+S"));
 fileMenu->AppendSeparator();
 fileMenu->Append(wxID_EXIT, _("Exit\tAlt+F4"));

 menuBar->Append(fileMenu, _("File"));

 wxBoxSizer * sizer = new wxBoxSizer(wxVERTICAL);
 SetSizer(sizer);

 CreateStatusBar();
}

void MyFrame::OnExit(wxCommandEvent & event)
{
 Close();
}

class MyApp : public wxApp
{
 virtual bool OnInit()
 {
  MyFrame * frame = new MyFrame;
  SetTopWindow(frame);
  frame->Centre();
  frame->Show();
  return true;
 }
};

IMPLEMENT_APP(MyApp);

Начнем с самого простого. А проще всего реализовать контекстную справку для диалоговых окон. Для этого внесем некоторые изменения в наш код:

HelpTest.cpp

...
#include <wx/cshelp.h>
#include <wx/html/helpctrl.h>

class MyDialog : public wxDialog
{
public:
 MyDialog(wxWindow * parent)
  : wxDialog(parent, wxID_ANY, _("Modal dialog"))
 {
  SetExtraStyle(wxDIALOG_EX_CONTEXTHELP);
  SetSizer(new wxBoxSizer(wxVERTICAL));
  wxTextCtrl * text = new wxTextCtrl(this, wxNewId(), wxT("Some text"),
   wxDefaultPosition, wxSize(300, 100), wxTE_MULTILINE);
  text->SetHelpText(_("You can type some tex here"));

  wxBoxSizer * buttonSizer = new wxBoxSizer(wxHORIZONTAL);
  wxButton* btnOK = new wxButton(this, wxID_OK, _T("&OK"));
  btnOK->SetHelpText(_("The OK button confirms the dialog choices."));

  wxButton* btnCancel = new wxButton(this, wxID_CANCEL, _T("&Cancel"));
  btnCancel->SetHelpText(_("The Cancel button cancels the dialog."));

  buttonSizer->Add(btnOK, 0, wxALIGN_CENTER | wxALL, 5);
  buttonSizer->Add(btnCancel, 0, wxALIGN_CENTER | wxALL, 5);

  // Add explicit context-sensitive help button for non-MSW
#ifndef __WXMSW__
  buttonSizer->Add(new wxContextHelpButton(this), 0, wxALIGN_CENTER|wxALL, 5);
#endif
  GetSizer()->Add(text, 1, wxEXPAND|wxALL, 5);
  GetSizer()->Add(buttonSizer, 0, wxALIGN_RIGHT, 5);

  GetSizer()->Fit(this);
  Centre();
 }
};

class MyFrame : public wxFrame
{
 ...
 DECLARE_EVENT_TABLE()
 void OnDialogContextHelp(wxCommandEvent & event);
 ...
};

enum
{
 ID_DIALOG_CONTEXT_HELP = 10001
};

BEGIN_EVENT_TABLE(MyFrame, wxFrame)
...
EVT_MENU(ID_DIALOG_CONTEXT_HELP, MyFrame::OnDialogContextHelp)
END_EVENT_TABLE()
...
void MyFrame::CreateControls()
{
 wxMenuBar * menuBar = new wxMenuBar;
 SetMenuBar(menuBar);
 ...
 wxMenu * helpMenu = new wxMenu;
 helpMenu->Append(ID_DIALOG_CONTEXT_HELP, _("Dialog context help"));
 ...
 menuBar->Append(fileMenu, _("File"));
 ...
}
...
void MyFrame::OnDialogContextHelp(wxCommandEvent & event)
{
 MyDialog dlg(this);
 dlg.ShowModal();
}
...
class MyApp : public wxApp
{
 virtual bool OnInit()
 {
  wxHelpControllerHelpProvider* provider = new wxHelpControllerHelpProvider;
  wxHelpProvider::Set(provider);
  ...
  return true;
 }
};

Как видно, мы создали класс диалогового окна, производного от wxDialog и поместили на диалоговое окно текстовое поле и кнопки.
Для обеспечения поддержки контекстной справки для элементов диалога, нам необходимо установить нашему диалоговому окну стиль wxDIALOG_EX_CONTEXTHELP. Этот стиль работает только для ОС семейства Windows, для всех остальных операционных систем нам необходимо создавать кнопку для вызова контекстной справки. Для этой цели используем класс wxContextHelpButton. Далее необходимо, также, установить тектст справочного сообщения для каждого элемента управления с помощью wxWindow::SetHelpText.
Поддержка контекстной справки обеспечивается в wxWidgets посредством использования класса wxHelpControllerHelpProvider. Для того, чтобы всё заработало, мы при инициализации приложения создаем новый провайдер контекстной справки и делаем его активным при помощи метода wxHelpProvider::Set.
Теперь собираем наш пример и смотрим, что получилось.

Отлично. Оно даже заработало.
Теперь можно приступить к более сложной части
Создадим справку для нашего приложения в формате HTML.

Для создания CHM-файла будем использовать HTML Help Workshop

Создадим новый проект

Назовем его HelpTest

После этого нам необходимо указать, что проект создается на основе уже существующих файлов

Теперь нам необходимо добавить в проект уже созданные HTML-файлы

Вот так должно выглядеть окно настроек проекта

Теперь нам необходимо создать структуру проекта справки. Переходим на вкладку “Contents” и указываем, что необходимо создать новый файл с описанием структуры проекта

Теперь создаем иерархию справочной системы. Для этого пользуемся кнопками на панели инструментов слева. Для добавления страницы справки используем кнопку “Insert a page”.
В диалоговом окне “Table of Contents Entry” необходимо указать отображаемое название страницы и указать соответствующий HTML-файл, нажав кнопку “Add…”.

В диалоговом окне “Path or URL” нужно выбрать файл из списка и нажать OK

Для создания группы пользуемся кнопкой “Insert a heading”

После того, как мы добавили все необходимые страницы, окно структуры проекта должно выглядеть примерно вот так:

Теперь нам необходимо создать индекс справки. Для этого переходим на вкладку Index и указываем, что нужно создать новый файл индекса.

Для добавления нового ключевого слова пользуемся кнопкой “Insert a keyword”

В окне “Index Entry” необходимо указать ключевое слово и выбрать соответствующий HTML-файл, нажав кнопку “Add…”

В окне “Path or URL” не обходимо выбрать файл из списка и нажать “OK”

После того, как мы добавили все ключевые слова, вкладка индекса должна иметь примерно вот такой вид.

На вкладке “Project” нажимеам кнопку “Project Options”, указываем название проекта и выбираем файл, отображаемый по умолчанию при открытии окна справки.

Теперь нам нужно собрать CHM-файл. Для этого жмем “Compile HTML file” и ждем завершения компиляции.

После того, как все успешно собралось, в журнале компиляции должна появиться статистика по проекту: количество тем, внешних и внутренних ссылок, рисунков.

Теперь можно запустить наш CHM-файл и посмотреть, что получилось.

После того, как мы сохранили наш проект справки, в каталоге с файлами должны появиться .hpp, .hhk, .hhc и .chm файлы

Отлично. CHM-файл у нас готов, теперь можно приступать к внесению изменений в исходный код приложения. Но сначала немного теории.

Для создания справочной системы в wxWidgets используется концепция контроллеров справки, которые позволяют загружать файлы справки и отображать их содержимое.

  • wxWinHelpController – используется для работы с файлами справки в формате .hlp Этот формат справки считается устаревшим, поэтому данный контроллер спавки желательно не использовать
  • wxCHMHelpController – для работы с файлами справки в формате .chm Этот контроллер справки доступен для использования только под управлением ОС Windows.
  • code>wxWinceHelpController – для работы с файлами справки для Windows CE (формат .htm)
  • wxHtmlHelpController – доступен для всех платформ. Использует файлы .zip

Все контроллеры справки кроме wxHtmlHelpController являются классами-обёртками над “родной” справочной системой для конкретной ОС и используют внешние средства просмотра файлов справки. В случае же wxHtmlHelpController, просмотр справки осуществляется внутри процесса, и все окна справки будут закрыты по завершению приложения их вызвавшего. Если есть необходимость использовать внешний просмотрищик, то нужно воспользоваться утилитой HelpView ( utils/src/helpview ) и классом wxRemoteHtmlHelpController, описанным в файлах remhelp.h и remhelp.cpp, который позволяет управлять утилитой HelpView.

Итак, давайте посмотрим, как это всё работает на примере wxCHMHelpController (предварительно скопировав созданный ранее файл HelpTest.chm в папку с программой).

HelpTest.cpp

#include <wx/wx.h>
#include <wx/textdlg.h>
#include <wx/aboutdlg.h>
#include <wx/cshelp.h>
#include <wx/html/helpctrl.h>

class MyDialog : public wxDialog
{
public:
 MyDialog(wxWindow * parent)
  : wxDialog(parent, wxID_ANY, _("Modal dialog"))
 {
  SetExtraStyle(wxDIALOG_EX_CONTEXTHELP);
  SetSizer(new wxBoxSizer(wxVERTICAL));
  wxTextCtrl * text = new wxTextCtrl(this, wxNewId(), wxT("Some text"),
   wxDefaultPosition, wxSize(300, 100), wxTE_MULTILINE);
  text->SetHelpText(_("You can type some tex here"));

  wxBoxSizer * buttonSizer = new wxBoxSizer(wxHORIZONTAL);
  wxButton* btnOK = new wxButton(this, wxID_OK, _T("&OK"));
  btnOK->SetHelpText(_("The OK button confirms the dialog choices."));

  wxButton* btnCancel = new wxButton(this, wxID_CANCEL, _T("&Cancel"));
  btnCancel->SetHelpText(_("The Cancel button cancels the dialog."));

  buttonSizer->Add(btnOK, 0, wxALIGN_CENTER | wxALL, 5);
  buttonSizer->Add(btnCancel, 0, wxALIGN_CENTER | wxALL, 5);

#ifndef __WXMSW__
  buttonSizer->Add(new wxContextHelpButton(this), 0, wxALIGN_CENTER | wxALL, 5);
#endif
  GetSizer()->Add(text, 1, wxEXPAND|wxALL, 5);
  GetSizer()->Add(buttonSizer, 0, wxALIGN_RIGHT, 5);

  GetSizer()->Fit(this);
  Centre();
 }
};

class MyFrame : public wxFrame
{
 void CreateControls();

#if wxUSE_MS_HTML_HELP && !defined(__WXUNIVERSAL__)
 wxCHMHelpController     m_msHtmlHelp;
#endif

public:
 MyFrame();
 bool Create(wxWindow * parent);

 wxHelpController * GetHelpController()
 {
  return &m_msHtmlHelp;
 }

 DECLARE_EVENT_TABLE()
 void OnDialogContextHelp(wxCommandEvent & event);
 void OnAbout(wxCommandEvent & event);
 void OnExit(wxCommandEvent & event);

 void OnShowHelpContents(wxCommandEvent & event);
 void OnShowSection_FileMenu(wxCommandEvent & event);
 void OnKeywordSearch(wxCommandEvent & event);
};

enum
{
 ID_DIALOG_CONTEXT_HELP = 10001,
 ID_SHOW_SECTION_FILEMENU,
 ID_KEYWORD_SEARCH
};

BEGIN_EVENT_TABLE(MyFrame, wxFrame)
EVT_MENU(wxID_EXIT, MyFrame::OnExit)
EVT_MENU(wxID_ABOUT, MyFrame::OnAbout)
EVT_MENU(wxID_HELP_CONTENTS, MyFrame::OnShowHelpContents)
EVT_MENU(ID_SHOW_SECTION_FILEMENU, MyFrame::OnShowSection_FileMenu)
EVT_MENU(ID_DIALOG_CONTEXT_HELP, MyFrame::OnDialogContextHelp)
EVT_MENU(ID_KEYWORD_SEARCH, MyFrame::OnKeywordSearch)
END_EVENT_TABLE()

MyFrame::MyFrame()
{
 Create(NULL);
}

bool MyFrame::Create(wxWindow * parent)
{
 bool res = wxFrame::Create(parent, wxID_ANY, wxT("Help System Test"));
 if(res)
 {
  CreateControls();
 }
 return res;
}

void MyFrame::CreateControls()
{
 wxMenuBar * menuBar = new wxMenuBar;
 SetMenuBar(menuBar);

 wxMenu * fileMenu = new wxMenu;
 fileMenu->Append(wxID_OPEN, _("Open\tCtrl+O"));
 fileMenu->Append(wxID_SAVE, _("Save\tCtrl+S"));
 fileMenu->AppendSeparator();
 fileMenu->Append(wxID_EXIT, _("Exit\tAlt+F4"));

 wxMenu * helpMenu = new wxMenu;
 helpMenu->Append(ID_DIALOG_CONTEXT_HELP, _("Dialog context help"));
 helpMenu->Append(wxID_HELP_CONTENTS, _("Contents"));
 helpMenu->Append(ID_SHOW_SECTION_FILEMENU, _("Show help on \"File\" menu"));
 helpMenu->Append(ID_KEYWORD_SEARCH, _("Keyword search"));
 helpMenu->AppendSeparator();
 helpMenu->Append(wxID_ABOUT, _("About..."));

 menuBar->Append(fileMenu, _("File"));
 menuBar->Append(helpMenu, _("Help"));

 wxBoxSizer * sizer = new wxBoxSizer(wxVERTICAL);
 SetSizer(sizer);

 CreateStatusBar();
}

void MyFrame::OnShowHelpContents(wxCommandEvent & event)
{
 m_msHtmlHelp.DisplayContents();
}

void MyFrame::OnShowSection_FileMenu(wxCommandEvent & event)
{
 m_msHtmlHelp.DisplaySection(wxT("File"));
}

void MyFrame::OnKeywordSearch(wxCommandEvent & event)
{
 wxString keyword = wxGetTextFromUser(_("Keyword Search"));
 if(!keyword.IsEmpty())
 {
  m_msHtmlHelp.KeywordSearch(keyword);
 }
}

void MyFrame::OnDialogContextHelp(wxCommandEvent & event)
{
 MyDialog dlg(this);
 dlg.ShowModal();
}

void MyFrame::OnAbout(wxCommandEvent & event)
{
 wxAboutDialogInfo info;
 info.SetName(_("HelpTest"));
 info.SetVersion(_("0.0.1"));
 info.SetDescription(_("This program does something great."));
 info.SetCopyright(_T("(C) 2007 Volodymir (T-Rex) Tryapichko https://wxwidgets.info"));
 wxAboutBox(info);
}

void MyFrame::OnExit(wxCommandEvent & event)
{
 Close();
}

class MyApp : public wxApp
{
 virtual bool OnInit()
 {
  wxHelpControllerHelpProvider* provider = new wxHelpControllerHelpProvider;
  wxHelpProvider::Set(provider);

  MyFrame * frame = new MyFrame;

#if wxUSE_MS_HTML_HELP && !defined(__WXUNIVERSAL__)
  provider->SetHelpController(frame->GetHelpController());
  if( !frame->GetHelpController()->Initialize(_T("HelpTest")) )
  {
   wxLogError(wxT("Cannot initialize the MS HTML Help system."));
  }
#endif
  SetTopWindow(frame);
  frame->Centre();
  frame->Show();
  return true;
 }
};

IMPLEMENT_APP(MyApp);

Что же делает наш пример:

  • Help -> Contents – вызывает окно справки и отображает список тем
  • Help -> Show help on “File” menu – вызывает окно справки и отображает тему "File"
  • Help -> Keyword search – позволяет ввести ключевое слово или фразу, а затем вызывает окно справки и выполняет поиск по введенной фразе.

Отображение списка тем производится с помощью метода wxHelpController::DisplayContents.
Ваше приложение может, также, иметь другие важные страницы справки. Для отображения конкретной страницы справки используется метод wxHelpController::DisplaySection, который может принимать в качестве параметра название страницы, числовой идентификатор страницы или имя HTML-файла.
Для выполнения поиска по ключевому слову используется метод wxHelpController::KeywordSearch, который принимает в качестве параметра ключевое слово или фразу.

Теперь можно попробовать создать кросс-платформенный вариант справочной системы. Для этого нам не обходимо все HTML-файлы, рисунки, и файлы созданные HTML Help Workshop запаковать в ZIP-архив и перенести этот архив в папку с программой, а также внести некоторые изменения в исходный код нашого приложения, которое теперь будет использовать класс wxHtmlHelpController и пересобрать его.

HelpTest.cpp

#include <wx/wx.h>
#include <wx/textdlg.h>
#include <wx/aboutdlg.h>
#include <wx/cshelp.h>
#include <wx/html/helpctrl.h>

#include "wx/filesys.h"
#include "wx/fs_zip.h"

class MyDialog : public wxDialog
{
public:
 MyDialog(wxWindow * parent)
  : wxDialog(parent, wxID_ANY, _("Modal dialog"))
 {
  SetExtraStyle(wxDIALOG_EX_CONTEXTHELP);
  SetSizer(new wxBoxSizer(wxVERTICAL));
  wxTextCtrl * text = new wxTextCtrl(this, wxNewId(), wxT("Some text"),
   wxDefaultPosition, wxSize(300, 100), wxTE_MULTILINE);
  text->SetHelpText(_("You can type some tex here"));

  wxBoxSizer * buttonSizer = new wxBoxSizer(wxHORIZONTAL);
  wxButton* btnOK = new wxButton(this, wxID_OK, _T("&OK"));
  btnOK->SetHelpText(_("The OK button confirms the dialog choices."));

  wxButton* btnCancel = new wxButton(this, wxID_CANCEL, _T("&Cancel"));
  btnCancel->SetHelpText(_("The Cancel button cancels the dialog."));

  buttonSizer->Add(btnOK, 0, wxALIGN_CENTER | wxALL, 5);
  buttonSizer->Add(btnCancel, 0, wxALIGN_CENTER | wxALL, 5);

#ifndef __WXMSW__
  buttonSizer->Add(new wxContextHelpButton(this), 0, wxALIGN_CENTER | wxALL, 5);
#endif
  GetSizer()->Add(text, 1, wxEXPAND|wxALL, 5);
  GetSizer()->Add(buttonSizer, 0, wxALIGN_RIGHT, 5);

  GetSizer()->Fit(this);
  Centre();
 }
};

class MyFrame : public wxFrame
{
 wxHtmlHelpController     m_msHtmlHelp;
 void CreateControls();
public:
 ...
 wxHtmlHelpController * GetHelpController()
 {
  return &m_msHtmlHelp;
 }
 ...
};
...
class MyApp : public wxApp
{
 virtual bool OnInit()
 {
  wxFileSystem::AddHandler(new wxZipFSHandler);

  wxHelpControllerHelpProvider* provider = new wxHelpControllerHelpProvider;
  wxHelpProvider::Set(provider);

  MyFrame * frame = new MyFrame;

  provider->SetHelpController(frame->GetHelpController());
  if( !frame->GetHelpController()->AddBook(wxFileName(wxT("HelpTest.zip"))))
  {
   wxLogError(wxT("Cannot initialize the Help system."));
  }
  SetTopWindow(frame);
  frame->Centre();
  frame->Show();
  return true;
 }
};

IMPLEMENT_APP(MyApp);

После всех проделанных изменений, окно справки под управлением ОС Windows будет выглядеть подобным образом:

А вот так оно будет выглядеть при работе в ОС Linux:

Ну вот, пока всё. Продолжение темы будет позже ;)

Скачать исходный код примера к статье

T-Rex

Share
Published by
T-Rex

Recent Posts

Разработка кроссплатформенных модульных приложений на C++ с библиотекой wxWidgets

Введение Уже долгое время не пишу статьи о разработке, хотя сам процесс написания мне очень…

11 years ago

wxWidgets App With Plugins (Windows/Linux/Mac) – Sample Source Code

I can see that there is still a lot of topics at wxWidgets forums related…

11 years ago

wxToolBox is Now Open-Source!

I've just published the source code of wxToolBox component and a couple of sample apps at…

11 years ago

Microsoft Kinect Helper Library and Sample for wxWidgets

Microsoft released their Kinect SDK several days ago. So, for those wxWidgets developers who are…

13 years ago

wxJSON 1.1.0 Released

JSON (JavaScript Object Notation) is a lightweight data-interchange format. It is easy for humans to…

15 years ago

wxRuby. Оно даже работает!

Вдохновленнный читаемой нынче книгой My Job Went to India: 52 Ways to Save Your Job…

15 years ago