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