Введение
Печать в корпоративной среде часто означает не локальный USB-принтер, а сетевой принтер, доступный через очередь печати сервера (CUPS на Linux/macOS, диспетчер очереди печати Windows). Кроме того, поведение системы печати существенно различается между Windows, macOS и Linux — то, что прозрачно работает на одной платформе, может потребовать дополнительной настройки на другой. В заключительной статье цикла разберём практические аспекты работы с сетевыми принтерами и кросс-платформенные нюансы печати в Qt.
Концепция
QPrinterInfo предоставляет программный доступ к списку доступных принтеров в системе (как локальных, так и сетевых, уже настроенных в ОС) без необходимости показывать пользователю интерактивный диалог выбора — это полезно для автоматизированных сценариев печати, где принтер для конкретной задачи определяется программно (например, «печатать накладные всегда на принтер склада», если такой настроен и доступен). Доступность конкретных сетевых принтеров, их статус (готов/занят/ошибка/нет бумаги) и поддерживаемые ими параметры (размеры бумаги, цветность) могут существенно различаться между платформами и зависят от того, как именно настроена печать на конкретной машине.
Пример кода: Печать на сетевые
#include <QPrinterInfo>
#include <QPrinter>
#include <QDebug>
void listAvailablePrinters()
{
const QList<QPrinterInfo> printers = QPrinterInfo::availablePrinters();
qDebug() << "Найдено принтеров:" << printers.size();
for (const QPrinterInfo &info : printers) {
qDebug() << "—" << info.printerName()
<< "| по умолчанию:" << info.isDefault()
<< "| состояние:" << static_cast<int>(info.state())
<< "| доступные размеры:" << info.supportedPageSizes().size();
}
}
bool printToSpecificNetworkPrinter(const QString &printerName, const QString &documentText)
{
const QPrinterInfo printerInfo = QPrinterInfo::printerInfo(printerName);
if (printerInfo.isNull()) {
qWarning() << "Принтер не найден:" << printerName;
return false;
}
if (printerInfo.state() == QPrinter::Error) {
qWarning() << "Принтер" << printerName << "находится в состоянии ошибки";
return false;
}
QPrinter printer(printerInfo);
printer.setPageSize(QPageSize(QPageSize::A4));
QPainter painter;
if (!painter.begin(&printer)) {
qWarning() << "Не удалось начать печать на" << printerName;
return false;
}
painter.drawText(QRectF(50, 50, printer.width() - 100, printer.height() - 100),
Qt::TextWordWrap, documentText);
painter.end();
return true;
}
void printToDefaultOrFallback(const QString &preferredPrinterName, const QString &documentText)
{
if (printToSpecificNetworkPrinter(preferredPrinterName, documentText)) {
return;
}
qWarning() << "Предпочитаемый принтер недоступен, используется принтер по умолчанию";
const QPrinterInfo defaultPrinter = QPrinterInfo::defaultPrinter();
if (!defaultPrinter.isNull()) {
printToSpecificNetworkPrinter(defaultPrinter.printerName(), documentText);
} else {
qWarning() << "В системе не настроено ни одного принтера";
}
}
Пояснения к коду
QPrinterInfo::availablePrinters() возвращает список всех принтеров, зарегистрированных в системе на момент вызова — для сетевых принтеров это означает, что они должны быть предварительно настроены через системные средства ОС (Панель управления Windows, Настройки принтеров и сканеров macOS, CUPS на Linux); Qt сам не реализует протоколы сетевого обнаружения принтеров (например, IPP), а полагается на уже сделанную системную интеграцию.
printToSpecificNetworkPrinter() показывает программный выбор конкретного принтера по имени без интерактивного диалога — конструктор QPrinter(printerInfo) связывает объект QPrinter с конкретным выбранным принтером напрямую. printToDefaultOrFallback() демонстрирует разумный паттерн отказоустойчивости: при недоступности предпочитаемого (например, специфичного для конкретного отдела) принтера приложение автоматически переключается на принтер по умолчанию вместо полного отказа от печати.
Подводные камни
- Имена принтеров, возвращаемые
printerName(), могут существенно различаться между платформами для того, что пользователь воспринимает как «один и тот же» сетевой принтер. Windows может показывать дружественное имя, настроенное администратором, тогда как CUPS на Linux часто использует более техническое, нормализованное имя устройства (без пробелов, с подчёркиваниями) — код, который хранит «захардкоженное» имя принтера для автоматизированного сценария, нужно настраивать отдельно для каждой целевой платформы, на которой будет развёрнуто приложение. - Состояние принтера (
state()), сообщаемое драйвером, не всегда отражает реальную физическую готовность принтера к печати. Некоторые сетевые принтеры или драйверы сообщают состояние «готов» даже когда физически не хватает бумаги или возникла застрявшая страница — состояние через API Qt полезно для предварительной грубой проверки, но не заменяет полноценную обработку реальных ошибок печати, которые могут проявиться уже после отправки задания в очередь, когдаpainter.end()уже был вызван успешно. - Различная поддержка размеров и типов бумаги между платформами и конкретными моделями принтеров.
supportedPageSizes()возвращает разный набор для разных принтеров — попытка установить черезsetPageSize()размер, не поддерживаемый конкретным выбранным принтером, может привести либо к автоматическому выбору ближайшего поддерживаемого размера драйвером (без явного предупреждения приложению), либо к ошибке печати, в зависимости от платформы и конкретного драйвера принтера. - Сетевые принтеры могут быть временно недоступны из-за проблем с сетью, а не из-за проблем с самим принтером. Код, написанный с предположением, что недоступность принтера всегда означает аппаратную проблему устройства, не учитывает класс сетевых сбоев (принтер выключен, проблема с DNS/маршрутизацией к сетевому адресу принтера, временная перегрузка сервера печати) — для надёжных автоматизированных сценариев печати (например, по расписанию на сервере) стоит реализовать повторные попытки с задержкой, аналогично паттерну устойчивых HTTP-запросов, разобранному в основном цикле статей про сеть.