Нудное вступление с 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 (все, что нужно для поиграться).
Комментарии (13)
SaNNy32
19.08.2016 19:22Жесть.
Если серьезно, то это абсолютно не нужно городить свой велосипед, когда есть нормальные решения, на подобие LimeReport или QtRPT.
AlexisVaBel
19.08.2016 21:13На тот момент limereport был сырой и для решения задачи не годился.данные с базы firebird.и да сегодня Мы смотрим на lime.а по поводу жести-не соглашусь статью оформлял дольше чем этот код
Iceg
19.08.2016 20:40А как та скада зовётся?
AlexisVaBel
19.08.2016 21:09У нас продукты noname делаем для собственного использования в автоматике(vsq внутри так ещё и зовем).недавно в ступор встали, что продукт надо бы именовать-требование налоговиков к производству
MCoder
19.08.2016 21:10Делал что-то подобное, но просто тупо вставлял html шаблон данные и грузил в textedit.
А вообще есть вот что: http://limereport.ru/ru/index.php
QuAzI
19.08.2016 21:10В своё время пользовал NCReports для Qt и работало вполне хорошо, плюс всякие фичи типа дизайнера и прочих плюшек нужных для репортов. На тот момент у них были какие-то ограничения на тему сколько страниц печатается без подписи о том что это триалка. Кажется 10
Magister7
20.08.2016 01:26Когда-то давно вот такое видел: https://github.com/trdm/unnstudioreport
Правда забросили уже, но вроде до более-менее рабочего состояния довели.
Сам не пробовал, просто вспомнилось.
heleo
22.08.2016 11:31В силу специфики требований к отчётам делаем на odf путём создания отчётов-шаблонов с метками по которым в итоге происходит вставка данных. В принципе ничего сложного в генерации не вижу, хотя делать полностью отчёт «с нуля» под odt та ещё проблема. Зато качество разметки документа закрывает все претензии к строгости оформления.
Harrix
Эх, можно было бы еще этот QTextDocument в docx или хотя бы rft сохранять и цены бы не было. А так в лучшем случае приходится как html страницу сохранять переименованную как .doc. Либо использовать odt, да не любят обычные пользователи данный формат и Open Office.
leremin
Есть же аналоги DocumentFormat.OpenXml, который под .NET. И не один.
evilandfox
Под win легко сгенерировать docx через COM объект QAxObject