Введение
QHelpEngine сам по себе не отображает HTML — для полноценного встроенного справочного браузера нужен виджет отображения, подключённый к схеме qthelp://, по которой адресуются страницы внутри справочника. В статье построим минимальный, но полнофункциональный справочный браузер с навигацией по TOC, кнопками «вперёд»/»назад» и поиском, используя QTextBrowser как основу — он легче по весу, чем QWebEngineView, и достаточен для большинства справочных текстов без сложного JavaScript.
Концепция
QTextBrowser поддерживает навигацию по гиперссылкам «из коробки», но по умолчанию умеет загружать только файлы с диска или ресурсы Qt — для поддержки схемы qthelp://, по которой адресуются страницы внутри .qch, нужно унаследовать QTextBrowser и переопределить loadResource(), перенаправляя загрузку данных через QHelpEngine::fileData() вместо стандартной файловой загрузки. После этого обычные <a href="qthelp://..."> ссылки внутри HTML-контента справки начинают работать прозрачно через встроенный браузер.
Пример кода: Собственный справочный браузер
// helpbrowser.h
#pragma once
#include <QTextBrowser>
#include <QHelpEngine>
class HelpBrowser : public QTextBrowser
{
Q_OBJECT
public:
explicit HelpBrowser(QHelpEngine *helpEngine, QWidget *parent = nullptr)
: QTextBrowser(parent), m_helpEngine(helpEngine) {}
protected:
QVariant loadResource(int type, const QUrl &url) override
{
if (url.scheme() == "qthelp") {
return QVariant(m_helpEngine->fileData(url));
}
return QTextBrowser::loadResource(type, url);
}
private:
QHelpEngine *m_helpEngine;
};
// helpwindow.h — окно справки с TOC-навигацией и кнопками истории
#pragma once
#include <QMainWindow>
#include <QSplitter>
#include <QTreeView>
#include <QToolBar>
#include "helpbrowser.h"
class HelpWindow : public QMainWindow
{
Q_OBJECT
public:
explicit HelpWindow(QHelpEngine *helpEngine, QWidget *parent = nullptr)
: QMainWindow(parent), m_helpEngine(helpEngine)
{
setWindowTitle("Справка MyApp");
resize(900, 600);
m_browser = new HelpBrowser(helpEngine, this);
auto *contentTree = new QTreeView(this);
contentTree->setModel(helpEngine->contentModel());
contentTree->setHeaderHidden(true);
connect(contentTree, &QTreeView::clicked, this, &HelpWindow::onContentItemClicked);
auto *splitter = new QSplitter(this);
splitter->addWidget(contentTree);
splitter->addWidget(m_browser);
splitter->setStretchFactor(1, 1); // браузер занимает основное пространство
setCentralWidget(splitter);
auto *toolbar = addToolBar("Навигация");
toolbar->addAction("Назад", m_browser, &QTextBrowser::backward);
toolbar->addAction("Вперёд", m_browser, &QTextBrowser::forward);
toolbar->addAction("Домой", this, [this]() {
navigateTo(QUrl("qthelp://com.mycompany.myapp.1.0/doc/source/index.html"));
});
}
void navigateTo(const QUrl &url)
{
m_browser->setSource(url);
}
private slots:
void onContentItemClicked(const QModelIndex &index)
{
auto *model = static_cast<QHelpContentModel *>(m_helpEngine->contentModel());
QHelpContentItem *item = model->contentItemAt(index);
if (item) {
navigateTo(item->url());
}
}
private:
QHelpEngine *m_helpEngine;
HelpBrowser *m_browser;
};
Пояснения к коду
HelpBrowser::loadResource() — ключевой момент интеграции: при попытке QTextBrowser загрузить ресурс по URL со схемой qthelp://, вместо стандартной обработки (которая не умеет работать с этой схемой и вернула бы пустой результат) вызывается m_helpEngine->fileData(url), который извлекает содержимое страницы непосредственно из скомпилированного .qch — для любых других схем (обычные файлы, ресурсы Qt) поведение делегируется обратно базовой реализации через QTextBrowser::loadResource().
HelpWindow связывает QTreeView с моделью helpEngine->contentModel() — той самой, что Qt Help Framework строит автоматически на основе TOC из .qhp — клик по любому узлу дерева получает соответствующий QHelpContentItem и его URL, который передаётся в navigateTo(). Кнопки «Назад»/»Вперёд» на панели инструментов используют встроенную в QTextBrowser историю навигации (backward()/forward()), не требующую отдельной ручной реализации стека истории.
Подводные камни
QTextBrowserподдерживает ограниченное подмножество HTML/CSS — сложная вёрстка с современным CSS (flexbox, grid), JavaScript-интерактивность или сложные таблицы могут отображаться некорректно или не отображаться вовсе; если контент справки сложнее простого текста с изображениями и базовым форматированием, может потребоватьсяQWebEngineView(с дополнительной интеграцией черезQWebEngineUrlSchemeHandlerдля той же схемыqthelp://, что технически сложнее, чем переопределениеloadResource()).- Изображения и другие вложенные ресурсы внутри HTML-страниц справки также должны проходить через
loadResource(). Если страница ссылается на<img src="qthelp://.../images/logo.png">, этот запрос точно так же обрабатывается переопределённым методом — но стоит явно протестировать загрузку вложенных ресурсов, а не только основных HTML-страниц, поскольку тип ресурса (QTextDocument::ImageResourceи так далее) передаётся вtype, и иногда логика должна различать типы ресурсов, а не обрабатывать их одинаково. QHelpContentModelобновляется асинхронно послеregisterDocumentation(). Сразу после регистрации нового .qch модель может быть ещё не готова — для надёжного отображения дерева содержания стоит дождаться сигналаQHelpEngine::setupFinished()перед привязкой модели кQTreeView, а не предполагать её немедленную готовность.- Отсутствие обработки ситуации «страница не найдена» при битой ссылке внутри справки (например, после правки .qhp без обновления конкретной страницы) —
fileData()в этом случае может вернуть пустойQByteArray, иQTextBrowserпросто отобразит пустую страницу без какого-либо сообщения об ошибке, что затрудняет диагностику для конечного пользователя.