Различные статьи по Qt и c++

Встраивание QHelpEngine в своё приложение: поиск и просмотр справки

Введение

Скомпилированный .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().