Пользовательский интерфейс в Qt

QPrinter и QPrintDialog: базовая печать документа

Введение

Печать — одна из тех функций, которая редко находится в центре внимания при проектировании приложения, но её отсутствие или плохая реализация быстро вызывает недовольство пользователей, особенно в бизнес-приложениях (отчёты, накладные, счета). QPrinter — основной класс Qt для печати, представляющий абстракцию над физическим или виртуальным принтером, а QPrintDialog — стандартный системный диалог выбора принтера и настройки параметров печати. В статье разберём базовый сценарий печати простого документа.

Концепция

QPrinter наследует QPagedPaintDevice, что позволяет рисовать на нём через обычный QPainter, как на любом другом устройстве вывода (виджете, изображении) — печать на физический принтер концептуально не отличается от рисования на экране, разница лишь в конечном устройстве, куда направляются команды отрисовки. QPrintDialog оборачивает QPrinter, предоставляя пользователю системный интерфейс выбора принтера, количества копий, диапазона страниц и других параметров — после подтверждения диалога эти настройки автоматически применяются к переданному объекту QPrinter.

Пример кода: QPrinter и QPrintDialog

#include <QPrinter>
#include <QPrintDialog>
#include <QPainter>
#include <QApplication>
#include <QPushButton>

void printSimpleDocument(QWidget *parentWidget)
{
    QPrinter printer(QPrinter::HighResolution);
    printer.setPageSize(QPageSize(QPageSize::A4));
    printer.setOrientation(QPageLayout::Portrait);

    QPrintDialog dialog(&printer, parentWidget);
    dialog.setWindowTitle("Печать документа");

    if (dialog.exec() != QDialog::Accepted) {
        return; // пользователь отменил печать
    }

    QPainter painter;
    if (!painter.begin(&printer)) {
        qWarning() << "Не удалось начать печать";
        return;
    }

    const QRect pageRect = printer.pageRect(QPrinter::DevicePixel).toRect();

    QFont titleFont("Arial", 18, QFont::Bold);
    painter.setFont(titleFont);
    painter.drawText(pageRect, Qt::AlignHCenter | Qt::AlignTop, "Счёт № 2024-105");

    QFont bodyFont("Arial", 11);
    painter.setFont(bodyFont);
    const QRect bodyRect = pageRect.adjusted(0, 100, 0, 0);
    painter.drawText(bodyRect, Qt::AlignLeft | Qt::TextWordWrap,
        "Дата: 23.06.2026\n"
        "Клиент: ООО \"Ромашка\"\n"
        "Сумма к оплате: 15 400 руб.\n\n"
        "Спасибо за сотрудничество!");

    painter.end(); // фактическая отправка задания на печать происходит здесь
}

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QPushButton button("Распечатать документ");
    QObject::connect(&button, &QPushButton::clicked, [&button]() {
        printSimpleDocument(&button);
    });
    button.show();

    return app.exec();
}

Пояснения к коду

QPrinter::HighResolution задаёт режим разрешения, оптимизированный для печати на реальном принтере, в отличие от QPrinter::ScreenResolution, более уместного для предпросмотра на экране — выбор режима влияет на то, как Qt масштабирует координаты при последующей отрисовке. После того как QPrintDialog::exec() вернул QDialog::Accepted, объект printer уже содержит все выбранные пользователем настройки (принтер, ориентация, количество копий), и можно сразу приступать к рисованию.

painter.begin(&printer) переключает QPainter в режим отрисовки на принтер — все последующие вызовы drawText(), drawRect() и так далее формируют содержимое страницы так же, как при рисовании на обычном виджете. Критичный момент — painter.end(): именно этот вызов завершает текущую страницу и фактически инициирует отправку сформированного задания на спулер печати операционной системы.

Подводные камни

  • Использование QPrinter::DevicePixel как единицы измерения без понимания разницы с QPrinter::Point/QPrinter::Millimeter. Координаты, вычисленные в одной системе единиц, не подходят для других без явного пересчёта — смешивание единиц при работе с pageRect() и собственными вычисленными отступами — частый источник того, что содержимое печатается с неверным масштабом или смещением относительно ожидаемого расположения на странице.
  • Отсутствие проверки результата painter.begin(&printer). Если принтер недоступен (отключён, не отвечает, выбран несуществующий виртуальный принтер) или нет прав на запись в очередь печати, begin() вернёт false — продолжение отрисовки на не начавшемся успешно QPainter не вызовет краш напрямую, но результат печати будет неопределённым или печать просто не произойдёт без вывода диагностики, если не проверять это значение явно.
  • Текст, выходящий за границы pageRect() без явной разбивки на страницы. В примере весь текст помещается на одну страницу, но при печати документа произвольной длины без логики постраничной разбивки (разбираемой в статье 195) содержимое, не уместившееся на первой странице, будет просто обрезано, а не перенесено на следующую — QPainter не выполняет это автоматически.
  • Печать в headless/серверном окружении без графического сервера и установленных принтеров. На Linux-серверах без CUPS или иной системы печати QPrintDialog::exec() может вести себя непредсказуемо или не находить ни одного принтера вовсе — для серверных сценариев генерации документов без интерактивного выбора принтера лучше подходит прямая генерация PDF через QPdfWriter (статья 191), не требующая реального принтера или диалога выбора.