Итак, продолжаем изучать процесс создания справки для приложений, разработанных с использованием 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. В обработчиках событий меню, мы проверяем, правильно ли отработал вызов справки и в случае успешного открытия нужной страницы, делаем активным вкладку с окном справки.
После всех изменений, главное окно нашего приложения выглядит так:
Как уже было сказано ранее, существует еще возможность отображения справки с помощью утилиты 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 <wx/wx.h>
#include <wx/textdlg.h>
#include <wx/aboutdlg.h>
#include <wx/cshelp.h>
#include <wx/html/helpctrl.h>
#include <wx/html/helpwnd.h>
#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 <wx/tipdlg.h>
#include <wx/config.h>
...
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() и _() внутри текстового файла. Так, строки, заключенные внутрь макроса _() будут автоматически переведены при смене языка.
Ну вот, все изменения произведены. Смотрим, что у нас получилось.

