Нудное вступление с 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 (все, что нужно для поиграться).
Поделиться с друзьями
-->

Комментарии (13)


  1. Harrix
    19.08.2016 17:27

    Эх, можно было бы еще этот QTextDocument в docx или хотя бы rft сохранять и цены бы не было. А так в лучшем случае приходится как html страницу сохранять переименованную как .doc. Либо использовать odt, да не любят обычные пользователи данный формат и Open Office.


    1. leremin
      19.08.2016 18:04

      Есть же аналоги DocumentFormat.OpenXml, который под .NET. И не один.


    1. evilandfox
      19.08.2016 19:30

      Под win легко сгенерировать docx через COM объект QAxObject


  1. SaNNy32
    19.08.2016 19:22

    Жесть.
    Если серьезно, то это абсолютно не нужно городить свой велосипед, когда есть нормальные решения, на подобие LimeReport или QtRPT.


    1. AlexisVaBel
      19.08.2016 21:13

      На тот момент limereport был сырой и для решения задачи не годился.данные с базы firebird.и да сегодня Мы смотрим на lime.а по поводу жести-не соглашусь статью оформлял дольше чем этот код


  1. Iceg
    19.08.2016 20:40

    А как та скада зовётся?


    1. AlexisVaBel
      19.08.2016 21:09

      У нас продукты noname делаем для собственного использования в автоматике(vsq внутри так ещё и зовем).недавно в ступор встали, что продукт надо бы именовать-требование налоговиков к производству


  1. MCoder
    19.08.2016 21:10

    Делал что-то подобное, но просто тупо вставлял html шаблон данные и грузил в textedit.
    А вообще есть вот что: http://limereport.ru/ru/index.php


  1. QuAzI
    19.08.2016 21:10

    В своё время пользовал NCReports для Qt и работало вполне хорошо, плюс всякие фичи типа дизайнера и прочих плюшек нужных для репортов. На тот момент у них были какие-то ограничения на тему сколько страниц печатается без подписи о том что это триалка. Кажется 10


  1. Magister7
    20.08.2016 01:26

    Когда-то давно вот такое видел: https://github.com/trdm/unnstudioreport
    Правда забросили уже, но вроде до более-менее рабочего состояния довели.
    Сам не пробовал, просто вспомнилось.


  1. heleo
    22.08.2016 11:31

    В силу специфики требований к отчётам делаем на odf путём создания отчётов-шаблонов с метками по которым в итоге происходит вставка данных. В принципе ничего сложного в генерации не вижу, хотя делать полностью отчёт «с нуля» под odt та ещё проблема. Зато качество разметки документа закрывает все претензии к строгости оформления.


    1. AlexisVaBel
      23.08.2016 05:49

      имеется ввиду просто обход элементов шаблона со вставкой данных?


      1. heleo
        23.08.2016 22:33

        Да, по факту так и сделано.