文章目录
前言
因项目需要,需要自行手绘pdf报表,网上已经有一些相关的方案实现了,但是没有一条龙服务,包括表头,表格,页眉,页脚,曲线图等,在这里我实现了以后,记录一下。(因为项目保密需要,没有图,反正肯定是能用的)
另外,由于大家情况各不相同,我不可能把我代码一股脑弄上来,不合理,所以就挑一些功能项摘出来介绍,大家灵活应用参考。
内容有点乱,没有做优化整理,不喜勿喷,右上角有个x,对,浏览器右上角那个,可以点。
友好意见,欢迎交流
一、初始化创建文件位置
这一步先做个弹窗,用于存放我们的pdf
//通过dialog来保存PDF路径
QString MyDialog::pdfSaveAs(QString fileName)
{
QString file = QFileDialog::getSaveFileName(
this, //父组件
"另存为", //标题
QCoreApplication::applicationDirPath() + QString::fromLocal8Bit("/data/Report/%1").arg(fileName + ".pdf"),
//设置路径, .表示当前路径,./表示更目录
QString::fromLocal8Bit("PDF文件(*.pdf)"), //过滤器,保存的类型
Q_NULLPTR,
QFileDialog::ShowDirsOnly);
return file;
}
调用办法
QString file_path = pdfSaveAs(QString::fromLocal8Bit("报告%1").arg(QDateTime::currentDateTime().toString("_yy_MM_dd_hh_mm")) + ".pdf"));
二、绘制表格
1.绘制表格函数(仅供参考,你们肯定写得更好)
//y:表单的Y坐标
//horzBorder:水平外边距,写0表示没有边距
//row,column:行数和列数
//unitHeight:单元高度
//font:字体大小
//list:要写入的单元数据
void MyDialog::pdfDrawForm(QPainter* paint, int y, int horzBorder, int row, int column, int unitHeight, QFont& font, QStringList& list)
{
paint->setFont(font);
paint->setPen(QPen(QBrush(QColor(0, 0, 0)), 2));
int Width = paint->viewport().width() - (horzBorder << 1);
int unitWidth = Width / column;
for (int i = 0; i < row; i++)
{
int x = horzBorder;
for (int j = 0; j < column; j++)
{
paint->drawText(QRect(x, y, unitWidth, unitHeight), Qt::AlignCenter, list.at(i * column + j));
paint->drawRect(QRect(x, y, unitWidth, unitHeight));
x += unitWidth;
}
y += unitHeight;
paint->drawLine(0, y, Width, y);
y++;
}
}
2.基于QTableWidget绘制表格函数
//y:表单的Y坐标
//horzBorder:水平外边距,写0表示没有边距
//row,column:行数和列数
//unitHeight:单元高度
//lineweight:表格线粗
//font:字体大小
//table_get:要写入的表格
//返回值:返回最终的高度
int MyDialog::pdfDrawForm_from_table(QPainter* paint, int y, int horzBorder, int unitHeight, int lineweight, QFont& font, QTableWidget* table_get, QString unit)
{
paint->setFont(font);
paint->setPen(QPen(QBrush(QColor(0, 0, 0)), lineweight));
const int Width = paint->viewport().width() - (horzBorder << 1);
const int row = table_get->rowCount() + 1;
const int column = table_get->columnCount() + 1;
const int unitWidth = Width / column;
int x_head_up = horzBorder;
font.setBold(true);
paint->setFont(font);
paint->drawText(QRect(x_head_up, y, unitWidth, unitHeight), Qt::AlignCenter, QString::fromLocal8Bit("你想要放在左上角的信息").arg(unit));
paint->drawRect(QRect(x_head_up, y, unitWidth, unitHeight));
x_head_up += unitWidth;
for (int j = 1; j < column; j++)
{
paint->drawText(QRect(x_head_up, y, unitWidth, unitHeight), Qt::AlignCenter, table_get->horizontalHeaderItem(j - 1)->text());
paint->drawRect(QRect(x_head_up, y, unitWidth, unitHeight));
x_head_up += unitWidth;
}
y += unitHeight;
for (int i = 1; i < row; i++)
{
int x = horzBorder;
font.setBold(true);
paint->setFont(font);
paint->drawText(QRect(x, y, unitWidth, unitHeight), Qt::AlignCenter, table_get->verticalHeaderItem(i - 1)->text());
paint->drawRect(QRect(x, y, unitWidth, unitHeight));
x += unitWidth;
font.setBold(false);
paint->setFont(font);
for (int j = 1; j < column; j++)
{
paint->drawText(QRect(x, y, unitWidth, unitHeight), Qt::AlignVCenter | Qt::AlignRight, table_get->item(i - 1, j - 1)->text());
paint->drawRect(QRect(x, y, unitWidth, unitHeight));
x += unitWidth;
}
y += unitHeight;
}
return y;
}
三、pdf换页逻辑器
1.判断当前y值是否超出了某一界限,若超出了,则pdf翻页,y设定为10 否则y直接改为设定值(勉强可以用,希望各位大神有更好的解决办法)
这个破函数是我自己造的,用来更新y的值,可以比较方便的判断是否需要换页,并更新y值
//写一个函数,判断当前y值是否超出了某一界限,若超出了,则pdf翻页,y设定为10 否则y直接改为设定值
void MyDialog::setYvalue_with_if(int& y_now, int y_target, int set_limit, int& page_now, QPdfWriter* get_writer, QPainter* paint, QImage get_logo_img_tmp, QString file_path, bool show_logo_flag)
{
if (y_target > set_limit)
{
get_writer->newPage();
page_now++;
int nPDFWidth = paint->viewport().width();
int nPDFHeight = paint->viewport().height();
const int nPDFWidth_1_4 = nPDFWidth >> 2;
const int top_gap = 140;
const int bottom_gap = 25;
//绘制页脚
paint->setPen(QPen(QBrush(QColor(0, 0, 0)), 5));
paint->drawLine(0, nPDFHeight - bottom_gap, nPDFWidth, nPDFHeight - bottom_gap);
QFont font_tmp = QFont(QString::fromLocal8Bit("黑体"), 26, QFont::Normal);
font_tmp.setPixelSize(25);
paint->setFont(font_tmp);
paint->drawText(QRect(0, nPDFHeight - bottom_gap, nPDFWidth, bottom_gap), Qt::AlignLeft,
QString::fromLocal8Bit("文件名: %1").arg(file_path));
QImage show_logo = get_logo_img_tmp.scaled(QSize(top_gap * 2, top_gap), Qt::KeepAspectRatio, Qt::SmoothTransformation);
if (show_logo_flag)
{
paint->drawImage(QPoint(0, 0), show_logo);
}
paint->drawText(QRect(nPDFWidth - 200, nPDFHeight - 25, bottom_gap, 25), Qt::AlignRight,
QString::fromLocal8Bit("页码:%1").arg(QString::number(page_now)));
y_now = top_gap;
paint->setPen(QPen(QBrush(QColor(0, 0, 0)), 5));
paint->drawLine(0, y_now, nPDFWidth, y_now);
y_now = top_gap + 5;
}
else
{
y_now = y_target;
}
}
四、第二类标题生成单元(可举一反三,我就不反了)
1.一个指定位置,背景之类的标题生成器,勉强可用
//第二类标题生成单元
void MyDialog::pdf_title_2_set(QString title_text, int title_width, int title_height, int first_background, QFont font_title, QPdfWriter* get_writer, QPainter* paint, int& y, int set_limit, int& page_now, QImage get_logo_img_tmp, QString file_path, bool show_logo_flag)
{
if (y + title_height > set_limit)
{
setYvalue_with_if(y, y + title_height, set_limit, page_now, get_writer, paint, get_logo_img_tmp, file_path);
}
paint->fillRect(QRect(0, y, title_width, title_height), QGradient::Preset(first_background));
paint->setFont(font_title);
paint->drawText(QRect(10, y, title_width, title_height), Qt::AlignVCenter | Qt::AlignLeft, title_text);
setYvalue_with_if(y, y + title_height, set_limit, page_now, get_writer, paint, get_logo_img_tmp, file_path);
paint->setPen(QPen(QBrush(QColor(0, 0, 0)), 3));
paint->drawLine(0, y, title_width, y);
setYvalue_with_if(y, y + 5, set_limit, page_now, get_writer, paint, get_logo_img_tmp, file_path);
}
五、根据路径创建pdf文件
1.记录路径创建pdf生成器
其实到这一步,会的人就都会了,反正变成QPainter了,剩下的不是随便你们搞么,可发挥的地方就多了。
代码如下:
//判断路径是否为空
if (file_path.isEmpty())
return;
//初始化pdf文件
QFile pdfFile(file_path);
pdfFile.open(QIODevice::WriteOnly);
//初始化pdf信息
QPdfWriter* pWriter = new QPdfWriter(&pdfFile);
pWriter->setPageSize(QPagedPaintDevice::A4);
pWriter->setResolution(300); //设置dpi 每个平方英寸像素为300
pWriter->setPageMargins(QMarginsF(30, 30, 30, 30));//上下左右间隔,这个就不废话了
//初始化绘制层
QPainter* pPainter = new QPainter(pWriter);
//中间填写你要绘制的东西
//绘制完毕
delete pPainter;
delete pWriter;
pdfFile.close();
2.初始化字体样式
代码如下:
//初始化字体样式
QFont font[6] = { QFont(QString::fromLocal8Bit("黑体"),26,QFont::Bold),
QFont(QString::fromLocal8Bit("黑体"),26,QFont::Normal),
QFont(QString::fromLocal8Bit("黑体"),26,QFont::Bold),
QFont(QString::fromLocal8Bit("黑体"),26,QFont::Normal),
QFont(QString::fromLocal8Bit("黑体"),26,QFont::Normal),
QFont(QString::fromLocal8Bit("黑体"),26,QFont::Normal)
};
font[0].setPixelSize(86);
font[1].setPixelSize(55);
font[2].setPixelSize(35);
font[3].setPixelSize(42);
font[4].setPixelSize(54);
font[5].setPixelSize(25);
3.绘制pdf内容(初始化)
代码如下:
//Painter PDF
qDebug() << pPainter->viewport();
int nPDFWidth = pPainter->viewport().width();
int nPDFHeight = pPainter->viewport().height();
const int nPDFWidth_1_4 = nPDFWidth >> 2;
const int nPDFWidth_1_8 = nPDFWidth >> 3;
int page_now = 1;
const int top_gap = 140;
const int bottom_gap = 25;
const int title_1_height = 100;//标题一高度
const int title_2_height = 65;//标题二高度
const int chart_height = 880;
const int unitHeight = 39;//表格单行高度
const int line_head = 50;//文字缩进
int y = 0;//比较重要,贯穿始终,指向当前绘制的高度
4.绘制页脚(仅供参考)
代码如下:
//绘制页脚
pPainter->setPen(QPen(QBrush(QColor(0, 0, 0)), 5));
pPainter->drawLine(0, nPDFHeight - bottom_gap, nPDFWidth, nPDFHeight - bottom_gap);
pPainter->setFont(font[5]);
pPainter->drawText(QRect(0, nPDFHeight - bottom_gap, nPDFWidth, bottom_gap), Qt::AlignLeft,
QString::fromLocal8Bit("文件名: %1").arg(file_path));
pPainter->drawText(QRect(nPDFWidth - 200, nPDFHeight - 25, bottom_gap, 25), Qt::AlignRight,
QString::fromLocal8Bit("页码:%1").arg(QString::number(page_now)));
5.绘制logo图(仅供参考)
代码如下:
//左上角绘制logo图
QImage show_logo = get_logo_img_tmp.scaled(QSize(top_gap * 2, top_gap), Qt::KeepAspectRatio, Qt::SmoothTransformation);
if (show_logo_flag)
{
pPainter->drawImage(QPoint(0, 0), show_logo);
}
6.绘制大标题
代码如下:
//在10%的头部居中显示
//0.主标题信息展示
pPainter->fillRect(QRect(0, y, nPDFWidth, title_1_height), QGradient::Preset(title_background));
pPainter->setFont(font[0]);
pPainter->drawText(QRect(0, y, nPDFWidth, title_1_height), Qt::AlignCenter,
QString::fromLocal8Bit("什么什么检测报告"));
y += 140;
pPainter->setPen(QPen(QBrush(QColor(0, 0, 0)), 5));
pPainter->drawLine(0, y, nPDFWidth, y);
pPainter->drawLine(0, y + 18, nPDFWidth, y + 18);
setYvalue_with_if(y, y + 20, nPDFHeight - bottom_gap, page_now, pWriter, pPainter, get_logo_img_tmp, file_path, show_logo_flag);
7.强制跳转到最后一页
代码如下:
//强行跳转到最后一页
setYvalue_with_if(y, y + nPDFHeight, nPDFHeight - bottom_gap, page_now, pWriter, pPainter, get_logo_img_tmp, file_path, show_logo_flag);
六.导入曲线图
1.QChart
可以通过QChartView把已经画好的QChart拉出来,然后变个尺寸扔进去,然后记得把尺寸变回来,清空一下之前放QChartView的地方(清空方法友情赠送,不谢),再把这个扔回去就行。这个方法如果用opengl的话容易出问题,建议不要弄,如果弄的话,记得重新把opengl打开(如果可以的话)。
基本原理就是把窗体扣下来变成QPixmap然后画到pdf里。你们可以举一反三一下。
代码如下:
QChartView* chartView_tmp = new QChartView(&chart_A);
chartView_tmp->setRenderHint(QPainter::Antialiasing);
float old_w = chartView_tmp->width();
float old_h = chartView_tmp->height();
float show_w = nPDFWidth - (imageBorder << 1);
float show_h = chart_height;
chartView_tmp->resize(QSize(show_w, show_h));
QPixmap pixmap = QPixmap::grabWidget(chartView_tmp, chartView_tmp->rect());
chartView_tmp->resize(QSize(old_w, old_h));
if (y + show_h > nPDFHeight - bottom_gap)
{
setYvalue_with_if(y, y + show_h, nPDFHeight - bottom_gap, page_now, pWriter, pPainter, get_logo_img_tmp, file_path, show_logo_flag);
}
pPainter->drawPixmap(imageBorder, y, pixmap);
//清空verticalLayout_widget布局内的所有元素
QLayoutItem* child;
while ((child = ui.verticalLayout_widget->takeAt(0)) != 0)
{
//setParent为NULL,防止删除之后界面不消失
if (child->widget())
{
child->widget()->setParent(NULL);
}
delete child;
}
ui.verticalLayout_widget->addWidget(chartView_tmp);
2.QCustomPlot
代码如下:
QCustomPlot* show_plot = ui.mycustomplot;
float show_w = nPDFWidth - (imageBorder << 1);
float show_h = chart_height;
QPixmap pixmap = show_plot->toPixmap(show_w, show_h);
所以说qcustomplot是真好用
七.通过其它PDF阅读器来打开PDF
代码如下(示例):
//通过其它PDF阅读器来打开PDF
QDesktopServices::openUrl(QUrl::fromLocalFile(file_path));
该处使用的url网络请求的数据。
总结
以上就是大概的内容了,基本上都有了。大家可以基于这个举一反三,最好有大神帮我封装一下,我以后就可以拿来用了(想得美)。或者大家有更好的插件、方案之类的,欢迎分享给我,我就把这个扔了。