Введение
Скомпилированный .qch-файл сам по себе бесполезен без программного компонента, который умеет его читать и предоставлять пользователю — для этого Qt предоставляет класс QHelpEngine (и его облегчённый вариант без UI-зависимостей, QHelpEngineCore). В статье разберём, как зарегистрировать справочник в приложении, выполнить поиск по ключевому слову и получить содержимое конкретной страницы для отображения.
Концепция
QHelpEngine оборачивает работу с .qch/.qhc файлами и предоставляет несколько ключевых API: QHelpContentModel (модель дерева TOC, готовая для использования с QTreeView), QHelpIndexModel/QHelpSearchEngine (поиск по ключевым словам и полнотекстовый поиск соответственно), и fileData()/findFile() для получения «сырого» содержимого конкретной страницы по её URL внутри справочника (схема qthelp://namespace/virtualFolder/path). Сам движок не занимается отображением HTML — это явно делегируется отдельному виджету (QTextBrowser или QWebEngineView), что мы детально разберём в следующей статье цикла.
Пример кода: Встраивание QHelpEngine в своё приложение
#pragma once
#include <QHelpEngine>
#include <QObject>
#include <QStringList>
class HelpSystem : public QObject
{
Q_OBJECT
public:
explicit HelpSystem(const QString &collectionFilePath, QObject *parent = nullptr)
: QObject(parent), m_engine(collectionFilePath, this)
{
if (!m_engine.setupData()) {
qWarning() << "Ошибка инициализации справки:" << m_engine.error();
}
}
QHelpEngine *engine() { return &m_engine; }
// Поиск страницы по ключевому слову через индекс
QUrl findPageByKeyword(const QString &keyword)
{
const QMap<QString, QUrl> links = m_engine.linksForIdentifier(keyword);
if (!links.isEmpty()) {
return links.constBegin().value();
}
return {};
}
// Полнотекстовый поиск по содержимому всей справки
void searchFullText(const QString &query)
{
auto *searchEngine = m_engine.searchEngine();
connect(searchEngine, &QHelpSearchEngine::searchingFinished, this, [this, searchEngine]() {
const auto results = searchEngine->searchResults();
qDebug() << "Найдено результатов:" << results.size();
for (const auto &result : results) {
qDebug() << " -" << result.documentTitle() << result.url();
}
});
searchEngine->search(query);
}
QByteArray getPageContent(const QUrl &url)
{
return m_engine.fileData(url);
}
private:
QHelpEngine m_engine;
};
// Использование при старте приложения
#include "helpsystem.h"
#include <QCoreApplication>
#include <QStandardPaths>
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
const QString collectionPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)
+ "/myapp_help.qhc";
HelpSystem help(collectionPath);
// Регистрация .qch-файла, поставляемого вместе с приложением, при первом запуске
if (!help.engine()->registeredDocumentations().contains("com.mycompany.myapp.1.0")) {
help.engine()->registerDocumentation(":/docs/myapp.qch");
}
const QUrl page = help.findPageByKeyword("установка");
qDebug() << "Страница по ключевому слову 'установка':" << page;
help.searchFullText("прокси-сервер");
return app.exec();
}
Пояснения к коду
Конструктор QHelpEngine(collectionFilePath) принимает путь к файлу коллекции (.qhc), который будет создан автоматически при первом запуске, если ещё не существует — это файл, в котором Qt Help Framework хранит метаданные о зарегистрированных справочниках именно для этого приложения, поэтому он создаётся в стандартной директории данных приложения (QStandardPaths::AppDataLocation), а не рядом с самим .qch.
registerDocumentation(":/docs/myapp.qch") подключает скомпилированный .qch (в примере — встроенный в ресурсы приложения через .qrc) к коллекции; проверка registeredDocumentations().contains(namespace) предотвращает повторную регистрацию при каждом запуске приложения, поскольку регистрация — операция, модифицирующая файл коллекции на диске, и не должна выполняться избыточно. linksForIdentifier() — прямой доступ к индексу ключевых слов (тому самому, что описывался в .qhp через <keywords>), возвращающий все страницы, связанные с данным идентификатором.
Подводные камни
- Повторная регистрация одного и того же .qch без проверки на каждом запуске приложения не вызывает явной ошибки, но создаёт ненужную нагрузку на файл коллекции и потенциально может приводить к рассинхронизации, если в это время .qch обновился (новая версия приложения) — namespace из старой версии может конфликтовать с уже зарегистрированным.
- Чтение
.qchнапрямую из ресурсов Qt (:/docs/myapp.qch) без предварительного копирования на диск, если используется API, требующий реального пути в файловой системе (а не виртуального ресурсного пути) —QHelpEngineподдерживает путь в формате ресурсов Qt для регистрации документации, но это стоит явно проверять для конкретной используемой версии Qt, поскольку поведение могло отличаться в более старых релизах. - Игнорирование
m_engine.error()после неудачногоsetupData()оставляет разработчика без понимания, почему справка не работает — частые причины: повреждённый или отсутствующий файл коллекции, недостаточные права на запись в директориюAppDataLocation. - Полнотекстовый поиск, выполняемый синхронно в блокирующем стиле в UI-потоке.
QHelpSearchEngine::search()асинхронен и испускаетsearchingFinished, как показано в примере, но если результат ожидается через блокирующий цикл или неверно интегрирован с UI, поиск по большой справке может ощущаться как «подвисание» интерфейса — правильная интеграция всегда через сигнал, а не ожидание результата сразу после вызоваsearch().