Нудное вступление с Qt 4.8


Недавно коллега по работе спросил об опыте использования построения отчетов под Qt (начинаем потихоньку внедрять SCADA, написанную на Qt) — в силу поставленной задачи очень нужная вещь. Генераторами никто не пользовался (на данной платформе), но отчеты мы я каким-то образом делали без использования FastReport и таскания лишних приложений.

Покопавшись в проектах, нашел приложение с отчетами, виджетами для предпросмотра (QLabel, QTableView....). Вид отчета «preview»:

image

Окно приложения ниже. Под Qt 5.x само приложение требует переработки, а вот отчеты работают:



Конструировались отчеты только и только до компиляции приложения — была задача сделать быстро (с xml — опыта работы почти не было).

Reporter Класс — генератор отчета


Почитав про форматирование в Qt родился класс Reporter (содержит в себе QTextDocument, QTextCursor и методы работы с ними). Вся работа внутри Reporter заключается в формировании QTextDocument, и далее распечатка его на принтере, либо отображение в QWidget.

Для шапки документа:

  • setDateDoc() — время
  • setCompanyDoc() — организация
  • setCaptionDoc() — название

Для контента:

  • setDataHeader(QStringList strLst) — шапка таблицы
  • addData(QStringList strLst) — данные

Работа осуществляется c QTextBlockFormat, либо QTextTableFormat, действующие лица из секции private

private:
    int             m_iCntTbls;
    int             m_iColCnt;
    QTextDocument   *const m_document;
    QTextCursor     m_cursor;

Нужен заголовок — не проблема

void Reporter::setCompanyDoc(QString str){
    // ставим позицию курсора вне чего-либо ранее редактированного
    m_cursor.movePosition(QTextCursor::End);
    if(m_iCntTbls>0){
        m_cursor.insertBlock();
        m_cursor.insertBlock();
        m_cursor.movePosition(QTextCursor::End);
    }
    // правим формат
    QTextBlockFormat blockFrm;

    blockFrm.setTopMargin(5);
    blockFrm.setBottomMargin(5);
    blockFrm.setAlignment(Qt::AlignLeft);
    blockFrm.setBackground(QBrush(QColor("lightGray")));
    // вставляем форматирование и текст к нему
    m_cursor.insertBlock(blockFrm);
    m_cursor.insertText(str);

}

Аналогично работаем с датой и заголовком таблицы.

Для создания самой таблицы, необходимо сначала вызвать метод setDataHeader(QStrignList strLst), число столбцов будет равно числу строк в списке:

void Reporter::setDataHeader(QStringList strLst){
    // ставим позицию курсора вне чего-либо ранее редактированного
    m_cursor.movePosition(QTextCursor::End);
    if(m_iCntTbls>0){
        m_cursor.insertBlock();
        m_cursor.insertBlock();
        m_cursor.movePosition(QTextCursor::End);
    }
    // зададим как будем рисовать рамку
    QBrush  borderBrush(Qt::SolidPattern);
    // проработаем формат таблицы
    QTextTableFormat tableFormat;
    tableFormat.setCellPadding(5);
    tableFormat.setCellSpacing(0);
    tableFormat.setHeaderRowCount(1);
    tableFormat.setBorderBrush(borderBrush);
    tableFormat.setBorderStyle(QTextFrameFormat::BorderStyle_Ridge);
    tableFormat.setBorder(1);
    tableFormat.setWidth(QTextLength(QTextLength::PercentageLength,100));    
    // вставим её и заполним шапку
    m_cursor.insertTable(1,strLst.count(),tableFormat);
    foreach(QString str, strLst){
        m_cursor.insertText(str);
        m_cursor.movePosition(QTextCursor::NextCell);
    }
    m_iCntTbls++;
    m_iColCnt=strLst.count();
}

Не забудем в конце сказать, что уже имеется как минимум 1 таблица и зафиксируем число колонок. А далее заполняем данными:

void Reporter::addData(QStringList strLst){
    // если данных меньше, то дополним пустыми
if(strLst.count()<m_iColCnt){
        int iAdd=m_iColCnt-strLst.count();
        while(iAdd>0){
            iAdd--;
            strLst<<"";
        }
    }else
  // данных больше - то ругаемся
    if(strLst.count()>m_iColCnt){
        QMessageBox::critical(0, tr("AddData"),
                              tr("Too many elements to paste wait %1 got %2").arg(m_iColCnt).arg(strLst.count()),
                              QMessageBox::Ok,QMessageBox::Ok
                              );
        return;
    }
    // заполняем
    QTextTable *tbl=m_cursor.currentTable();
    tbl->appendRows(1);
    m_cursor.movePosition(QTextCursor::PreviousRow);
    foreach(QString str, strLst){
        m_cursor.movePosition(QTextCursor::NextCell);
        m_cursor.insertText(str);
    }
}

Пример в действии:


Берем наш класс, создаем объект (m_reporter) и толкаем в него данные.

    m_reporter->setCompanyDoc(QString::fromLocal8Bit("НПФ Промавтоматика"));
    m_reporter->setCaptionDoc(QString::fromLocal8Bit(" Отчет №0"));
strLst<<QString::fromLocal8Bit("Рецепт         ")<<setStr("Продукт        ")<<setStr("Вес нужный")<<setStr("Вес набранный")
         <<setStr("Задача отпр.")<<setStr("Задача подтв.")<<setStr("Партия");
.....

Получаем что-то подобное:

image

(вывел на QDialog с помощью )

    QPainter painter(this);
    QRect rec(0,0,this->width(),this->height());
    m_reporter->getTextDoc()->drawContents(&painter,rec);

Для того чтобы распечатать, нужно вызвать метод Reporter::printDoc(QPrinter).

Резюме


Для быстрого решения конкретной задачи подходят стандартные средства Qt, из которых можно соорудить подходящие инструменты, однако для предоставления качественного продукта данный метод не применим.

Главным мину��ом является необходимость компиляции проекта, содержащего отчет при изменении формата документа, при добавлении новых отчетов и т.д. Всем спасибо, кто дочитал.

P.S. github.com/AlexisVaBel/QtReport.git (все, что нужно для поиграться).