Итак, продолжаем изучать процесс создания справки для приложений, разработанных с использованием 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()
и _()
внутри текстового файла. Так, строки, заключенные внутрь макроса _()
будут автоматически переведены при смене языка.
Ну вот, все изменения произведены. Смотрим, что у нас получилось.