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

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

Возьмем пример из статьи “Кросс-платформенная справка для кросс-платформенных приложений – Часть 1” и внесем некоторые изменения:

HelpTest.cpp

class MyFrame : public wxFrame
{		
	wxHtmlHelpController     m_msHtmlHelp;
	wxHtmlHelpWindow * m_HtmlHelpWindow;
	wxNotebook * m_NavigationNotebook;
	...
public:	
	...
};
...
MyFrame::MyFrame()
: m_msHtmlHelp(wxHF_EMBEDDED|wxHF_DEFAULT_STYLE)
{
	Create(NULL);
}
...
void MyFrame::CreateControls()
{
	...
	wxBoxSizer * sizer = new wxBoxSizer(wxVERTICAL);
	SetSizer(sizer);

	m_NavigationNotebook = new wxNotebook(this, wxID_ANY);

	m_HtmlHelpWindow = new wxHtmlHelpWindow(m_NavigationNotebook, wxID_ANY);
	wxPanel * panel = new wxPanel(m_NavigationNotebook, wxID_ANY);
	m_NavigationNotebook->AddPage(panel, _("Workspace"), true);
	m_NavigationNotebook->AddPage(m_HtmlHelpWindow, _("Help"), false);

	m_msHtmlHelp.SetHelpWindow(m_HtmlHelpWindow);

	sizer->Add(m_NavigationNotebook, 1, wxEXPAND);

	CreateStatusBar();
}
...
void MyFrame::OnShowHelpContents(wxCommandEvent & event)
{
	if(m_msHtmlHelp.DisplayContents())
	{
		m_NavigationNotebook->SetSelection(1);
	}
}

void MyFrame::OnShowSection_FileMenu(wxCommandEvent & event)
{
	if(m_msHtmlHelp.DisplaySection(wxT("File")))
	{
		m_NavigationNotebook->SetSelection(1);
	}
}

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

Как видно, изменений совсем немного: мы создали компонент wxNotebook и поместили на него две вкладки. Первая вкладка – рабочая, а вторая – содержит встроенное окно справки. Для того, чтобы наш контроллер справки мог работать со встроенными окнами, мы установили для него стиль wxHF_EMBEDDED, затем создали объект wxHtmlHelpWindow и выполнили его привязку к нашему контроллеру справки посредством метода wxHtmlHelpController::SetHelpWindow. В обработчиках событий меню, мы проверяем, правильно ли отработал вызов справки и в случае успешного открытия нужной страницы, делаем активным вкладку с окном справки.
После всех изменений, главное окно нашего приложения выглядит так:

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

Как уже было сказано ранее, существует еще возможность отображения справки с помощью утилиты helpview (utils\helpview\src), но, оказалось, сделать это уже сложнее и тому есть несколько причин.
Причина первая. Как это ни странно, но исходные коды утилиты helpview немного устарели и для того чтобы ее собрать, необходимо “доработать напильником”.
Причина вторая. Исходный код класса wxRemoteHtmlHelpController завязан на исходном коде примера, да и к тому же, написан без поддержки Unicode, что меня лично очень огорчило, ибо уже достаточно давно не пользуюсь ANSI-сборкой wxWidgets.
Причина третья. Файлы проектов для утилиты helpview и тестового примера имеют UNIX-окончания строк, что делает невозможным их использование в Visual Studio без предварительных танцев с бубном.

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

  • Открыть файл проекта в броузере
  • Скопировать весь текст
  • Открыть файл проекта в блокноте
  • Вставить скопированный из броузера текст
  • Сохранить

Проблема с поддержкой Unicode решается использованием макросов wxT() и _() для всех строковых констант и использованием wxString::GetData() вместо wxString::c_str().

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

Исправленный исходный код класса wxRemoteHtmlHelpController, а также исходный код примера, Вы можете найти в конце статьи.

Для использования класса wxRemoteHtmlHelpController в своем приложении, необходимо включить в проект файлы remhelp.h и remhelp.cpp

Теперь внесем изменения в исходный код нашего примера:

HelpTest.cpp

#include 
#include 
#include 

#include 
#include 
#include 

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

#include "remhelp.h"

class MyApp : public wxApp
{
	wxRemoteHtmlHelpController * m_RemoteHelpController;
public:
	wxRemoteHtmlHelpController * GetRemoteHelpController()
	{
		return m_RemoteHelpController;
	}
	virtual bool OnInit();
	virtual int OnExit();
};

DECLARE_APP(MyApp);
IMPLEMENT_APP(MyApp);
...
class MyFrame : public wxFrame
{		
	...
	DECLARE_EVENT_TABLE()	
	...
	void OnShowHelpAboutRemote(wxCommandEvent & event);
};

enum
{
	ID_DIALOG_CONTEXT_HELP = 10001,
	ID_SHOW_SECTION_FILEMENU,
	ID_KEYWORD_SEARCH,
	ID_HELP_ABOUT_REMOTE
};

BEGIN_EVENT_TABLE(MyFrame, wxFrame)
...
EVT_MENU(ID_HELP_ABOUT_REMOTE, MyFrame::OnShowHelpAboutRemote)
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"));
	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..."));
	helpMenu->Append(ID_HELP_ABOUT_REMOTE, _("About... (Remote)"));

	menuBar->Append(fileMenu, _("File"));
	menuBar->Append(helpMenu, _("Help"));
...
}
...
void MyFrame::OnShowHelpAboutRemote(wxCommandEvent & event)
{
	wxGetApp().GetRemoteHelpController()->Display(wxT("about.html"));
}
...
bool MyApp::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."));
	}
	m_RemoteHelpController = new wxRemoteHtmlHelpController(this);
#if defined(__WXMSW__)
	wxString server = wxT("helpview.exe");
	wxString service = wxT("generic_helpservice");
#else
	wxString server = wxT("./helpview");
	wxString service = wxT("/tmp/wxWidgets-helpconnection");
#endif    
	m_RemoteHelpController->SetServer(server);
	m_RemoteHelpController->SetService(service);
	if(!m_RemoteHelpController->AddBook(wxT("HelpTest.zip")))
	{
		wxLogError(wxT("Cannot initialize the Remote Help system."));
	}
	SetTopWindow(frame);
	frame->Centre();
	frame->Show();
	return true;
}

int MyApp::OnExit()
{
	wxDELETE(m_RemoteHelpController);
	return wxApp::OnExit();
}

Теперь смотрим, какие же мы внесли изменения:
Мы добавили в класс приложения новую переменную типа wxRemoteHtmlHelpController. Произвели настройку соединения с утилитой helpview с помощью методов wxRemoteHtmlHelpController::SetServer и wxRemoteHtmlHelpController::SetService. Добавили новый пункт меню "About... (Remote)" и создали для него обработчик, в котором с помощью метода wxRemoteHtmlHelpController::Display отображаем страницу с правки с помощью утилиты helpview.

Еще одним популярным методом донесения справочной информации до конечного пользователя является использование диалога "Tip of the Day", с помощью которого можно, например, рассказать о ключевых особенностях программного продукта. Использование этого метода в wxWidgets также не занимает много усилий. С помощью функции wxShowTip, которая принимает в качестве параметров указатель на родительское окно и указатель на объект wxTipProvider, можно буквально несколькими строками кода организовать подобный функционал в своем приложении. Ну вот, пора снова править наш пример:

HelpTest.cpp

...
#include 
#include 
...
class MyApp : public wxApp
{
...
	void ShowTip(wxWindow * parent, bool bShowTip, wxConfigBase * config = NULL);
};
...
class MyFrame : public wxFrame
{		
	...
	void OnShowTip(wxCommandEvent & event);
};

enum
{
	ID_DIALOG_CONTEXT_HELP = 10001,
	ID_SHOW_SECTION_FILEMENU,
	ID_KEYWORD_SEARCH,
	ID_HELP_ABOUT_REMOTE,
	ID_SHOW_TIP
};

BEGIN_EVENT_TABLE(MyFrame, wxFrame)
...
EVT_MENU(ID_SHOW_TIP, MyFrame::OnShowTip)
END_EVENT_TABLE()
...
void MyFrame::CreateControls()
{
	...
	helpMenu->AppendSeparator();
	helpMenu->Append(ID_SHOW_TIP, _("Tip of the Day"));
	helpMenu->AppendSeparator();
	helpMenu->Append(wxID_ABOUT, _("About..."));
	...
}

void MyFrame::OnShowTip(wxCommandEvent & event)
{
	wxConfig * config = new wxConfig(wxGetApp().GetAppName());
	wxGetApp().ShowTip(this, false, config);
	wxDELETE(config);
}
...
bool MyApp::OnInit()
{
	...
	wxConfig * config = new wxConfig(GetAppName());
	if(config)
	{
		bool bShowTip(false);
		if(!config->Read(wxT("ShowTip"), &bShowTip))
		{
			bShowTip = true;
		}
		if(bShowTip)
		{			
			ShowTip(frame, bShowTip, config);			
		}
	}
	wxDELETE(config);
	return true;
}

void MyApp::ShowTip(wxWindow * parent, bool bShowTip, wxConfigBase * config)
{
	wxString tipPath = GetAppName()+wxT(".txt");
	wxTipProvider * provider = wxCreateFileTipProvider(tipPath, 0);
	bShowTip = wxShowTip(parent, provider, bShowTip);
	wxDELETE(provider);
	if(config)
	{
		config->Write(wxT("ShowTip"), bShowTip);
		config->Flush();
	}
}

Помимо модификации исходного кода приложения, нам необходимо создать текстовый файл, в котором будет храниться текст диалога "Tip of the Day". Каждая строка в этом текстовом файле соответствует странице справки для нашего диалога. Еще интересной возможностью является использование макросов wxT() и _() внутри текстового файла. Так, строки, заключенные внутрь макроса _() будут автоматически переведены при смене языка.

Ну вот, все изменения произведены. Смотрим, что у нас получилось.

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

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

Leave a Reply

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

Please leave these two fields as-is: