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

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

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

HelpTest.cpp

#include 

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 
#include 

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.
Теперь собираем наш пример и смотрим, что получилось.

Cross Platform Help System for Cross Platform Software in wxWidgets

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

Cross Platform Help System for Cross Platform Software in wxWidgets

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

Cross Platform Help System for Cross Platform Software in wxWidgets

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

Cross Platform Help System for Cross Platform Software in wxWidgets

Назовем его HelpTest

Cross Platform Help System for Cross Platform Software in wxWidgets

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

Cross Platform Help System for Cross Platform Software in wxWidgets

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

Cross Platform Help System for Cross Platform Software in wxWidgets

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

Cross Platform Help System for Cross Platform Software in wxWidgets

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

Cross Platform Help System for Cross Platform Software in wxWidgets

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

Cross Platform Help System for Cross Platform Software in wxWidgets

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

Cross Platform Help System for Cross Platform Software in wxWidgets

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

Cross Platform Help System for Cross Platform Software in wxWidgets

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

Cross Platform Help System for Cross Platform Software in wxWidgets

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

Cross Platform Help System for Cross Platform Software in wxWidgets

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

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

Cross Platform Help System for Cross Platform Software in wxWidgets

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

Cross Platform Help System for Cross Platform Software in wxWidgets

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

Cross Platform Help System for Cross Platform Software in wxWidgets

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

Cross Platform Help System for Cross Platform Software in wxWidgets

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

Cross Platform Help System for Cross Platform Software in wxWidgets

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

Cross Platform Help System for Cross Platform Software in wxWidgets

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

Cross Platform Help System for Cross Platform Software in wxWidgets

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

Cross Platform Help System for Cross Platform Software in wxWidgets

Отлично. 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 
#include 
#include 
#include 
#include 

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 
#include 
#include 
#include 
#include 

#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 будет выглядеть подобным образом:

Cross Platform Help System for Cross Platform Software in wxWidgets

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

Cross Platform Help System for Cross Platform Software in wxWidgets

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

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

Leave a Reply

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

Please leave these two fields as-is: