Pull to refresh

Qt: Вывод отчета стандартными средствами (или живем без генераторов отчета)

Reading time4 min
Views16K

Нудное вступление с 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 (все, что нужно для поиграться).
Tags:
Hubs:
Total votes 15: ↑12 and ↓3+9
Comments13

Articles