Нудное вступление с Qt 4.8
Недавно коллега по работе спросил об опыте использования построения отчетов под Qt (начинаем потихоньку внедрять SCADA, написанную на Qt) — в силу поставленной задачи очень нужная вещь. Генераторами никто не пользовался (на данной платформе), но отчеты
Покопавшись в проектах, нашел приложение с отчетами, виджетами для предпросмотра (QLabel, QTableView....). Вид отчета «preview»:

Окно приложения ниже. Под 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("Партия");
.....Получаем что-то подобное:

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