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

Печать на сетевые принтеры и кросс-платформенные различия

Введение

Печать в корпоративной среде часто означает не локальный 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-запросов, разобранному в основном цикле статей про сеть.