Q打印表格内容类

最强大功能打印 代码有详实解疑 参数保姆级的注解 适合初学 学习类的使用

cpp 复制代码
.h
#ifndef TABLEPRINTER_H
#define TABLEPRINTER_H

#include <QObject>
#include <QTableView>
#include <QPrinter>
#include <QPainter>
#include <QAbstractItemModel>
#include <QFont>
#include <QVector>
#include <QRect>


class TablePrinter
{
public:
    // 在文件开头添加详细的注释说明:
    /**
 * TablePrinter 终极版
 * 版本:1.0
 * 最后修改:2024年
 * 作者:hw完成
 *
 * 功能特点:
 * 1. 支持指定列范围打印(0-10列等)
 * 2. 支持自动缩放适应页面
 * 3. 支持文本自动换行
 * 4. 支持自定义字体大小和行高
 * 5. 支持打印预览
 * 6. 支持页眉页脚
 * 7. 支持网格线和交替行背景色
 *
 * 使用示例:
 * TablePrinter::PrintOptions options;
 * options.title = "报表";
 * options.contentFontSize = 10;
 * options.rowHeightFactor = 12.0;
 * TablePrinter::print(tableView, options, parent);
 */
    struct PrintOptions {
        // 构造函数,设置所有默认值
        PrintOptions() :
            // 打印范围控制
            startColumn(0),          // [范围: 0~列数-1] 开始列索引,0表示第一列
            endColumn(-1),           // [范围: -1~列数-1] 结束列索引,-1表示到最后一列
            startRow(0),             // [范围: 0~行数-1] 开始行索引,0表示第一行
            endRow(-1),              // [范围: -1~行数-1] 结束行索引,-1表示到最后一行

            // 显示控制
            showGrid(true),          // [true/false] 是否显示网格线
            showHeader(true),        // [true/false] 是否显示表头
            showFooter(true),        // [true/false] 是否显示页脚

            // 文本换行控制
            textWrap(false),         // [true/false] 是否启用文本自动换行
            maxTextLines(3),         // [范围: 1~10] 最大换行行数,防止文本过长

            // 字体大小控制(单位:点/磅)
            titleFontSize(25),       // [建议: 20-30] 标题字体大小
            headerFontSize(11),      // [建议: 10-14] 表头字体大小
            contentFontSize(10),     // [建议: 8-12] 内容字体大小
            footerFontSize(10),      // [建议: 8-12] 页脚字体大小

            // 高度因子控制(相对于字体高度的倍数)
            rowHeightFactor(12.0),   // [建议: 10-20] 行高因子,行高=内容字体高度×此值
            headerHeightFactor(2.0), // [建议: 1.5-2.5] 表头高度因子
            titleHeightFactor(5.0),  // [建议: 4-6] 标题高度因子
            footerHeightFactor(2.0), // [建议: 1.5-2.5] 页脚高度因子

            // 布局控制
            marginMM(15),            // [建议: 10-20] 页面边距(毫米)
            cellPadding(8),          // [建议: 5-15] 单元格内边距(像素),解决文字太靠近边框
            scaleToPage(true),       // [true/false] 是否自动缩放以适应页面
            stretchColumns(true),    // [true/false] 是否拉伸列宽以适应页面宽度

            // 标题文本
            title("")                // 打印标题文本
        {}

        // ==================== 参数详细说明 ====================

        // 1. 打印范围参数
        int startColumn;          ///< 开始列索引:0表示第一列,通常设为0
        int endColumn;            ///< 结束列索引:-1表示自动到最后一列
        int startRow;             ///< 开始行索引:0表示第一行
        int endRow;               ///< 结束行索引:-1表示自动到最后一行

        // 2. 显示控制参数
        bool showGrid;            ///< 网格线:true显示细网格线,false不显示(更简洁)
        bool showHeader;          ///< 表头:true显示列标题,false不显示
        bool showFooter;          ///< 页脚:true显示页码和时间,false不显示

        // 3. 文本换行参数
        bool textWrap;            ///< 文本换行:true启用自动换行,false不换行(使用省略号)
        int maxTextLines;         ///< 最大行数:控制换行时最多显示几行,超出显示...

        // 4. 字体大小参数(点/磅为单位)
        int titleFontSize;        ///< 标题字体:通常最大,用于突出显示
        int headerFontSize;       ///< 表头字体:通常比内容稍大且加粗
        int contentFontSize;      ///< 内容字体:主要数据的字体大小
        int footerFontSize;       ///< 页脚字体:通常与内容相同或稍小

        // 5. 高度因子参数(关键参数,影响行高)
        double rowHeightFactor;   ///< 行高因子:最重要的参数!控制行的高度
            ///< 值越大行越高,建议10-20之间
            ///< 换行打印需要更大的值(15-25)

        double headerHeightFactor;///< 表头高度因子:控制表头行的高度
        double titleHeightFactor; ///< 标题高度因子:控制标题区域的高度
        double footerHeightFactor;///< 页脚高度因子:控制页脚区域的高度

        // 6. 布局参数
        int marginMM;             ///< 页面边距:控制纸张四周的空白,毫米为单位
        int cellPadding;          ///< 单元格内边距:控制文字与边框的距离
        bool scaleToPage;         ///< 自动缩放:true自动缩放表格以适应页面宽度
        bool stretchColumns;      ///< 拉伸列宽:true时如果表格宽度小于页面,会拉伸列宽填满

        // 7. 其他
        QString title;            ///< 标题文本:显示在每页顶部的标题
    };

    /**
     * @brief 打印表格
     * @param tableView 要打印的表格视图
     * @param options 打印选项配置
     * @param parent 父窗口(用于显示对话框)
     * @return 成功返回true,失败返回false
     */
    static bool print(QTableView* tableView,
                      const PrintOptions& options = PrintOptions(),
                      QWidget* parent = nullptr);

    /**
     * @brief 打印预览
     * @param tableView 要打印的表格视图
     * @param options 打印选项配置
     * @param parent 父窗口
     * @return 成功返回true,失败返回false
     */
    static bool printPreview(QTableView* tableView,
                             const PrintOptions& options = PrintOptions(),
                             QWidget* parent = nullptr);

private:
    // 私有辅助函数


//void drawWrappedText(QPainter& painter, const QRectF& rect,
                                       //const QString& text, const QFont& font);


    /* @brief 绘制换行文本
            */
        static void drawWrappedText(QPainter& painter, const QRectF& rect,
                        const QString& text, const QFont& font);

    /**
     * @brief 绘制换行文本(简单版本)
     */
    static void drawWrappedTextSimple(QPainter& painter, const QRectF& rect,
                                      const QString& text, const QFont& font);





    /**
     * @brief 实际执行打印操作
     */
    static bool printToPrinter(QTableView* tableView, QPrinter& printer,
                               const PrintOptions& options);

    /**
     * @brief 毫米转换为像素
     */
    static double mmToPx(double mm, double dpi);

    /**
     * @brief 获取页面内容区域(减去边距)
     */
    static QRectF getPageContentRect(QPrinter& printer, const PrintOptions& options);

    /**
     * @brief 计算列宽(考虑拉伸选项)
     */
    static QVector<double> calculateColumnWidths(QTableView* tableView,
                                                 const PrintOptions& options,
                                                 double availableWidth);

    /**
     * @brief 绘制表格内容(包括表头和数据行)
     * @param cellPadding 单元格内边距,解决文字太靠近边框的问题
     */
    static void drawTable(QPainter& painter,
                          QAbstractItemModel* model,
                          const PrintOptions& options,
                          const QVector<double>& colWidths,
                          double startX, double startY,
                          double rowHeight,
                          int startRow, int endRow);

    /**
     * @brief 绘制页眉(标题)
     */
    static void drawPageHeader(QPainter& painter,
                               const QRectF& contentRect,
                               const PrintOptions& options,
                               double scaleFactor);

    /**
     * @brief 绘制页脚(页码、时间、标题)
     */
    static void drawPageFooter(QPainter& painter,
                               const QRectF& contentRect,
                               const PrintOptions& options,
                               int pageNum, int totalPages,
                               double scaleFactor);

    /**
     * @brief 文本换行处理
     * @return 换行后的文本
     */
    static QString wrapText(const QString& text,
                            const QFontMetricsF& fm,
                            double maxWidth,
                            int maxLines);
};

#endif // TABLEPRINTER_H







.CPP



#include "tableprinter.h"
#include <QPrintDialog>
#include <QPrintPreviewDialog>
#include <QHeaderView>
#include <QDebug>
#include <QDateTime>
#include <QtAlgorithms>
#include <cmath>
#include <numeric>

/**
 * @brief 将毫米转换为像素
 * @param mm 毫米值
 * @param dpi 打印机DPI
 * @return 像素值
 */
double TablePrinter::mmToPx(double mm, double dpi)
{
    // 转换公式:像素 = (毫米 / 25.4) × DPI
    // 25.4是一英寸的毫米数
    return (mm / 25.4) * dpi;
}

/**
 * @brief 打印表格
 */
bool TablePrinter::print(QTableView* tableView, const PrintOptions& options, QWidget* parent)
{
    // 参数检查
    if (!tableView || !tableView->model()) {
        qWarning() << "TablePrinter: 表格视图或数据模型为空";
        return false;
    }

    // 创建打印机对象
    QPrinter printer(QPrinter::HighResolution);
    printer.setPageSize(QPageSize(QPageSize::A4));

    // 显示打印对话框
    QPrintDialog dialog(&printer, parent);
    dialog.setWindowTitle("打印表格");

    // 用户取消打印
    if (dialog.exec() != QDialog::Accepted) {
        return false;
    }

    // 执行打印
    return printToPrinter(tableView, printer, options);
}

/**
 * @brief 打印预览
 */
bool TablePrinter::printPreview(QTableView* tableView, const PrintOptions& options, QWidget* parent)
{
    // 参数检查
    if (!tableView || !tableView->model()) {
        qWarning() << "TablePrinter: 表格视图或数据模型为空";
        return false;
    }

    // 创建打印机对象
    QPrinter printer(QPrinter::HighResolution);
    printer.setPageSize(QPageSize(QPageSize::A4));

    // 创建预览对话框
    QPrintPreviewDialog preview(&printer, parent);
    preview.setWindowTitle("打印预览");
    preview.setMinimumSize(1000, 700);

    // 连接绘制信号
    QObject::connect(&preview, &QPrintPreviewDialog::paintRequested,
        [=](QPrinter* previewPrinter) {
            printToPrinter(tableView, *previewPrinter, options);
        });

    // 显示预览
    preview.exec();
    return true;
}

/**
 * @brief 获取页面内容区域(减去边距后的可用区域)
 */
QRectF TablePrinter::getPageContentRect(QPrinter& printer, const PrintOptions& options)
{
    double dpi = printer.resolution();
    double marginPx = mmToPx(options.marginMM, dpi);

    // 获取纸张矩形(像素单位)
    QRectF pageRect = printer.paperRect(QPrinter::DevicePixel);

    // 减去边距得到内容区域
    return QRectF(pageRect.left() + marginPx,
                  pageRect.top() + marginPx,
                  pageRect.width() - 2 * marginPx,
                  pageRect.height() - 2 * marginPx);
}

/**
 * @brief 实际执行打印操作
 */
bool TablePrinter::printToPrinter(QTableView* tableView, QPrinter& printer, const PrintOptions& options)
{
    QPainter painter;
    if (!painter.begin(&printer)) {
        qWarning() << "TablePrinter: 无法开始绘制";
        return false;
    }

    QAbstractItemModel* model = tableView->model();
    double dpi = printer.resolution();

    // 获取内容区域
    QRectF contentRect = getPageContentRect(printer, options);

    // 确定打印范围
    int startCol = qMax(0, options.startColumn);
    int endCol = (options.endColumn < 0) ? model->columnCount() - 1 :
                 qMin(options.endColumn, model->columnCount() - 1);

    int startRow = qMax(0, options.startRow);
    int endRow = (options.endRow < 0) ? model->rowCount() - 1 :
                 qMin(options.endRow, model->rowCount() - 1);

    // 检查打印范围有效性
    if (startCol > endCol || startRow > endRow) {
        painter.end();
        return false;
    }

    // ==================== 计算各种字体和高度 ====================

    // 内容字体和高度
    QFont contentFont;
    contentFont.setPointSize(options.contentFontSize);
    QFontMetricsF contentFontMetrics(contentFont);
    double contentFontHeight = contentFontMetrics.height();
    double rowHeight = contentFontHeight * options.rowHeightFactor;

    // 表头字体和高度
    QFont headerFont;
    headerFont.setPointSize(options.headerFontSize);
    headerFont.setBold(true);
    QFontMetricsF headerFontMetrics(headerFont);
    double headerHeight = headerFontMetrics.height() * options.headerHeightFactor;

    // 标题字体和高度
    QFont titleFont;
    titleFont.setPointSize(options.titleFontSize);
    titleFont.setBold(true);
    QFontMetricsF titleFontMetrics(titleFont);
    double titleHeight = titleFontMetrics.height() * options.titleHeightFactor;

    // 页脚字体和高度
    QFont footerFont;
    footerFont.setPointSize(options.footerFontSize);
    QFontMetricsF footerFontMetrics(footerFont);
    double footerHeight = footerFontMetrics.height() * options.footerHeightFactor;

    // ==================== 计算列宽和缩放因子 ====================

    // 计算列宽
    QVector<double> colWidths = calculateColumnWidths(tableView, options, contentRect.width());
    double tableWidth = std::accumulate(colWidths.begin(), colWidths.end(), 0.0);

    // 计算缩放因子
    double scaleFactor = 1.0;
    if (options.scaleToPage && tableWidth > 0) {
        scaleFactor = contentRect.width() / tableWidth;
        // 限制缩放范围在0.5到1.5之间
        scaleFactor = qMax(0.5, qMin(1.5, scaleFactor));
    }

    // ==================== 计算分页 ====================

    // 计算可用高度(减去标题和页脚)
    double pageLogicalHeight = contentRect.height() / scaleFactor;
    double reservedHeight = 0;

    if (!options.title.isEmpty()) {
        reservedHeight += titleHeight;
    }
    if (options.showFooter) {
        reservedHeight += footerHeight;
    }

    double availableHeightForTable = pageLogicalHeight - reservedHeight;

    // 计算每页行数
    double rowsPerPageDouble = 0;
    double currentHeight = 0;

    if (options.showHeader) {
        currentHeight += headerHeight;
    }

    for (int row = startRow; row <= endRow; row++) {
        currentHeight += rowHeight;
        if (currentHeight <= availableHeightForTable) {
            rowsPerPageDouble++;
        } else {
            break;
        }
    }

    if (rowsPerPageDouble < 1) rowsPerPageDouble = 1;
    int rowsPerPage = static_cast<int>(rowsPerPageDouble);

    // 计算总页数
    int totalRows = endRow - startRow + 1;
    int totalPages = static_cast<int>(ceil(static_cast<double>(totalRows) / rowsPerPage));

    // 调试信息
    qDebug() << "TablePrinter调试信息:";
    qDebug() << "  页面逻辑高度:" << pageLogicalHeight;
    qDebug() << "  表格宽度:" << tableWidth << "缩放因子:" << scaleFactor;
    qDebug() << "  每页行数:" << rowsPerPage << "总页数:" << totalPages;

    // ==================== 开始分页打印 ====================

    // 应用缩放和平移
    painter.save();
    painter.translate(contentRect.left(), contentRect.top());
    painter.scale(scaleFactor, scaleFactor);

    int currentRow = startRow;
    int pageNum = 1;

    while (currentRow <= endRow) {
        // 如果不是第一页,开始新页面
        if (pageNum > 1) {
            printer.newPage();
            painter.restore();
            painter.save();
            painter.translate(contentRect.left(), contentRect.top());
            painter.scale(scaleFactor, scaleFactor);
        }

        // 1. 绘制页眉(标题)
        if (!options.title.isEmpty()) {
            drawPageHeader(painter, contentRect, options, scaleFactor);
        }

        // 2. 绘制表格
        double tableStartY = options.title.isEmpty() ? 0 : titleHeight;

        // 计算当前页的结束行
        int pageEndRow = currentRow + rowsPerPage - 1;
        if (pageEndRow > endRow) {
            pageEndRow = endRow;
        }

        // 绘制表格内容(使用修正后的内边距)
        drawTable(painter, model, options, colWidths,
                 0, tableStartY,
                 rowHeight,
                 currentRow, pageEndRow);

        // 3. 绘制页脚
        if (options.showFooter) {
            drawPageFooter(painter, contentRect, options, pageNum, totalPages, scaleFactor);
        }

        // 移动到下一页
        currentRow = pageEndRow + 1;
        pageNum++;
    }

    painter.restore();
    painter.end();

    return true;
}

/**
 * @brief 计算列宽
 * @param availableWidth 可用宽度
 * @return 列宽数组
 */
QVector<double> TablePrinter::calculateColumnWidths(QTableView* tableView,
                                                   const PrintOptions& options,
                                                   double availableWidth)
{
    QVector<double> colWidths;
    QAbstractItemModel* model = tableView->model();

    int startCol = qMax(0, options.startColumn);
    int endCol = (options.endColumn < 0) ? model->columnCount() - 1 :
                 qMin(options.endColumn, model->columnCount() - 1);

    // 计算原始列宽
    double totalWidth = 0;
    for (int col = startCol; col <= endCol; ++col) {
        double width = tableView->columnWidth(col);

        // 如果列宽太小,根据内容估算
        if (width <= 10) {
            QFontMetricsF fm(QFont("Arial", options.contentFontSize));

            // 检查表头宽度
            QString header = model->headerData(col, Qt::Horizontal).toString();
            double headerWidth = fm.horizontalAdvance(header) + options.cellPadding * 2;

            // 检查前几行数据宽度
            double maxDataWidth = headerWidth;
            int checkRows = qMin(20, model->rowCount());
            for (int row = 0; row < checkRows; ++row) {
                QString data = model->data(model->index(row, col)).toString();
                double dataWidth = fm.horizontalAdvance(data) + options.cellPadding * 2;
                maxDataWidth = qMax(maxDataWidth, dataWidth);
            }

            width = maxDataWidth;
        }

        colWidths.append(width);
        totalWidth += width;
    }

    // 如果需要拉伸列宽以适应页面
    if (options.stretchColumns && totalWidth > 0 && totalWidth < availableWidth) {
        double stretchFactor = availableWidth / totalWidth;
        for (int i = 0; i < colWidths.size(); ++i) {
            colWidths[i] *= stretchFactor;
        }
        totalWidth = availableWidth;
    }

    return colWidths;
}


/**
 * @brief 绘制页眉(标题)
 */
void TablePrinter::drawPageHeader(QPainter& painter,
                                 const QRectF& contentRect,
                                 const PrintOptions& options,
                                 double scaleFactor)
{
    painter.save();

    // 设置标题样式
    QFont titleFont;
    titleFont.setPointSize(options.titleFontSize);
    titleFont.setBold(true);
    painter.setFont(titleFont);
    painter.setPen(Qt::darkBlue);

    // 绘制标题背景(浅蓝色)
    painter.setBrush(QColor(220, 230, 240));
    painter.setPen(Qt::NoPen);
    double titleHeight = QFontMetricsF(titleFont).height() * options.titleHeightFactor;
    painter.drawRect(QRectF(0, 0, contentRect.width() / scaleFactor, titleHeight));

    // 绘制标题文本
    painter.setPen(Qt::darkBlue);
    QRectF titleRect(0, 0, contentRect.width() / scaleFactor, titleHeight);
    painter.drawText(titleRect, Qt::AlignCenter | Qt::AlignVCenter, options.title);

    painter.restore();
}

/**
 * @brief 绘制页脚
 */
void TablePrinter::drawPageFooter(QPainter& painter,
                                 const QRectF& contentRect,
                                 const PrintOptions& options,
                                 int pageNum, int totalPages,
                                 double scaleFactor)
{
    painter.save();

    // 设置页脚字体
    QFont footerFont;
    footerFont.setPointSize(options.footerFontSize);
    painter.setFont(footerFont);
    painter.setPen(Qt::darkGray);

    // 计算页脚位置(固定在页面底部)
    double pageLogicalHeight = contentRect.height() / scaleFactor;
    double footerFontHeight = QFontMetricsF(footerFont).height();
    double footerHeight = footerFontHeight * options.footerHeightFactor;
    double footerY = pageLogicalHeight - footerHeight;

    // 给页脚足够的绘制区域(解决页脚文字显示不全的问题)
    double footerRectHeight = 40; // 固定40像素高度,确保文字完整显示

    // 绘制页脚分隔线(可选)
    painter.setPen(QPen(QColor(180, 180, 180), 0.5));
    painter.drawLine(0, footerY - 5, contentRect.width() / scaleFactor, footerY - 5);

    // 恢复文本颜色
    painter.setPen(Qt::darkGray);

    // 绘制打印时间(左侧)
    QString timeText = QDateTime::currentDateTime().toString("打印: yyyy-MM-dd hh:mm");
    painter.drawText(QRectF(10, footerY,
                          contentRect.width() / scaleFactor ,
                          footerRectHeight* 2),
                    Qt::AlignLeft | Qt::AlignVCenter, timeText);

    // 绘制页码(居中)- 给足够大的绘制区域
    QString pageText = QString("第 %1 页 / 共 %2 页").arg(pageNum).arg(totalPages);
    painter.drawText(QRectF(0, footerY,
                          contentRect.width() / scaleFactor,
                          footerRectHeight* 2),
                    Qt::AlignCenter | Qt::AlignVCenter, pageText);

    // 绘制标题(右侧)- 如果标题太长则截断
    if (!options.title.isEmpty()) {
        QString displayTitle = options.title;
        QFontMetricsF fm(footerFont);
        double maxWidth = contentRect.width() / scaleFactor ;
        if (fm.horizontalAdvance(displayTitle) > maxWidth) {
            displayTitle = fm.elidedText(displayTitle, Qt::ElideRight, maxWidth);
        }
        painter.drawText(QRectF(0, footerY,
                              contentRect.width() / scaleFactor - 10,
                              footerRectHeight* 2),
                        Qt::AlignRight | Qt::AlignVCenter, displayTitle);
    }

    painter.restore();
}

/**
 * @brief 文本换行处理
 * @param text 原始文本
 * @param fm 字体度量
 * @param maxWidth 最大宽度
 * @param maxLines 最大行数
 * @return 换行后的文本
 */


QString TablePrinter::wrapText(const QString& text,
                               const QFontMetricsF& fm,
                               double maxWidth,
                               int maxLines)
{
    if (text.isEmpty() || maxWidth <= 0) {
        return text;
    }

    // 如果文本本来就不需要换行,直接返回
    if (fm.horizontalAdvance(text) <= maxWidth) {
        return text;
    }

    QStringList lines;
    QString currentLine;
    QStringList words = text.split(' ', Qt::SkipEmptyParts);

    for (const QString& word : words) {
        QString testLine = currentLine.isEmpty() ? word : currentLine + " " + word;

        if (fm.horizontalAdvance(testLine) <= maxWidth) {
            currentLine = testLine;
        } else {
            if (!currentLine.isEmpty()) {
                lines.append(currentLine);
                if (lines.size() >= maxLines) {
                    // 已达到最大行数
                    if (lines.size() > maxLines) {
                        lines = lines.mid(0, maxLines);
                    }
                    // 在最后一行添加省略号
                    QString lastLine = lines.last();
                    QString ellipsisLine = lastLine;

                    // 检查添加省略号后的宽度
                    QString testEllipsis = lastLine + "...";
                    if (fm.horizontalAdvance(testEllipsis) > maxWidth) {
                        // 需要截断
                        ellipsisLine = fm.elidedText(lastLine, Qt::ElideRight,
                                                     maxWidth - fm.horizontalAdvance("..."));
                    }
                    lines.last() = ellipsisLine + "...";
                    return lines.join("\n");
                }
            }
            currentLine = word;
        }
    }

    // 添加最后一行
    if (!currentLine.isEmpty()) {
        lines.append(currentLine);
    }

    // 如果行数超过最大行数
    if (lines.size() > maxLines) {
        lines = lines.mid(0, maxLines);
        QString lastLine = lines.last();
        QString ellipsisLine = lastLine;

        QString testEllipsis = lastLine + "...";
        if (fm.horizontalAdvance(testEllipsis) > maxWidth) {
            ellipsisLine = fm.elidedText(lastLine, Qt::ElideRight,
                                         maxWidth - fm.horizontalAdvance("..."));
        }
        lines.last() = ellipsisLine + "...";
    }

    return lines.join("\n");
}






void TablePrinter::drawTable(QPainter& painter,
                             QAbstractItemModel* model,
                             const PrintOptions& options,
                             const QVector<double>& colWidths,
                             double startX, double startY,
                             double rowHeight,
                             int startRow, int endRow)
{
    int startCol = qMax(0, options.startColumn);
    int endCol = (options.endColumn < 0) ? model->columnCount() - 1 :
                     qMin(options.endColumn, model->columnCount() - 1);

    double currentX = startX;
    double currentY = startY;

    // ==================== 绘制表头 ====================

    if (options.showHeader) {
        painter.save();

        // 设置表头样式 - 确保字体大小正确
        QFont headerFont;
        headerFont.setPointSize(options.headerFontSize); // 使用配置的字体大小
        headerFont.setBold(true);
        painter.setFont(headerFont);

        // 表头背景色
        painter.setBrush(QColor(240, 240, 240));
        painter.setPen(QPen(Qt::black, 1));

        // 绘制表头背景
        double totalWidth = std::accumulate(colWidths.begin(), colWidths.end(), 0.0);
        painter.drawRect(QRectF(currentX, currentY, totalWidth, rowHeight * 1.2));

        // 绘制表头文本
        double cellX = currentX;
        for (int i = 0; i < colWidths.size(); ++i) {
            int col = startCol + i;
            QString header = model->headerData(col, Qt::Horizontal).toString();

            QRectF cellRect(cellX, currentY, colWidths[i], rowHeight * 1.2);
            QRectF textRect = cellRect.adjusted(options.cellPadding, 0, -options.cellPadding, 0);

            // 绘制列分隔线
            if (i < colWidths.size() - 1) {
                painter.setPen(QPen(Qt::gray, 1));
                painter.drawLine(cellX + colWidths[i], currentY,
                                 cellX + colWidths[i], currentY + rowHeight * 1.2);
                painter.setPen(QPen(Qt::black, 1));
            }

            // 绘制表头文本
            painter.drawText(textRect, Qt::AlignCenter | Qt::AlignVCenter, header);

            cellX += colWidths[i];
        }

        painter.restore();
        currentY += rowHeight * 1.2;
    }

    // ==================== 绘制内容行 ====================

    painter.save();

    // 设置内容样式 - 关键:使用配置的字体大小
    QFont contentFont;
    contentFont.setPointSize(options.contentFontSize); // 这是关键!
    painter.setFont(contentFont);

    // 获取字体度量,用于换行计算
    QFontMetricsF fm(contentFont);
    double lineSpacing = fm.lineSpacing();

    for (int row = startRow; row <= endRow; ++row) {
        double cellX = currentX;

        // 记录每个单元格需要的额外高度(用于换行)
        QVector<double> cellExtraHeights(colWidths.size(), 0);
        double maxExtraHeight = 0;

        // 第一遍:计算需要换行的单元格的额外高度
        if (options.textWrap) {
            double tempX = currentX;
            for (int i = 0; i < colWidths.size(); ++i) {
                int col = startCol + i;
                QModelIndex index = model->index(row, col);
                QString text = model->data(index).toString();

                if (!text.isEmpty()) {
                    QRectF cellRect(tempX, currentY, colWidths[i], rowHeight);
                    QRectF textRect = cellRect.adjusted(options.cellPadding, 3, -options.cellPadding, -3);

                    // 计算文本需要的行数
                    QString wrappedText = wrapText(text, fm, textRect.width(), options.maxTextLines);
                    int lineCount = wrappedText.count('\n') + 1;

                    // 计算文本总高度
                    double textHeight = lineSpacing * lineCount;

                    // 如果超过单元格高度,需要额外空间
                    if (textHeight > textRect.height()) {
                        double extraHeight = textHeight - textRect.height();
                        cellExtraHeights[i] = extraHeight;
                        maxExtraHeight = qMax(maxExtraHeight, extraHeight);
                    }
                }
                tempX += colWidths[i];
            }
        }

        // 应用最大额外高度到这一行
        double actualRowHeight = rowHeight + maxExtraHeight;

        // 交替行背景色
        if ((row - startRow) % 2 == 0) {
            painter.save();
            painter.setBrush(QColor(250, 250, 250));
            painter.setPen(Qt::NoPen);
            double totalWidth = std::accumulate(colWidths.begin(), colWidths.end(), 0.0);
            painter.drawRect(QRectF(currentX, currentY, totalWidth, actualRowHeight));
            painter.restore();
        }

        // 第二遍:实际绘制单元格
        for (int i = 0; i < colWidths.size(); ++i) {
            int col = startCol + i;
            QModelIndex index = model->index(row, col);
            QString text = model->data(index).toString();

            QRectF cellRect(cellX, currentY, colWidths[i], actualRowHeight);

            // 绘制单元格边框
            if (options.showGrid) {
                painter.setPen(QPen(QColor(200, 200, 200), 1));
                painter.drawRect(cellRect);
            }

            // 设置文本颜色
            painter.setPen(Qt::black);

            // 计算文本区域
            QRectF textRect = cellRect.adjusted(options.cellPadding, 3, -options.cellPadding, -3);

            if (options.textWrap && !text.isEmpty()) {
                // 处理换行文本
                QString wrappedText = wrapText(text, fm, textRect.width(), options.maxTextLines);

                // 绘制换行文本
                drawWrappedText(painter, textRect, wrappedText, contentFont);
            } else {
                // 不换行,使用省略号
                QString displayText = text;
                if (fm.horizontalAdvance(text) > textRect.width()) {
                    displayText = fm.elidedText(text, Qt::ElideRight, textRect.width());
                }
                painter.drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, displayText);
            }

            cellX += colWidths[i];
        }

        currentY += actualRowHeight;
    }

    painter.restore();
}

void TablePrinter::drawWrappedText(QPainter& painter, const QRectF& rect,
                                   const QString& text, const QFont& font)
{
    painter.save();

    // 重要:保持原来的字体设置
    painter.setFont(font);

    // 设置文本选项
    QTextOption textOption;
    textOption.setWrapMode(QTextOption::WordWrap);
    textOption.setAlignment(Qt::AlignLeft | Qt::AlignTop);

    // 绘制文本
    painter.drawText(rect, text, textOption);

    painter.restore();
}



使用举例


// 打印指定列
void UnitManagerWindow::onPrintCustom()
{


   TablePrinter::PrintOptions options;

   // ==================== 1. 基本设置 ====================
   options.title = "数据报表";// 报表标题,显示在每页顶部
   options.startColumn = 0;           // 从第1列开始
   options.endColumn = 5;             // 到第10列结束


 // ==================== 2. 页面布局设置 ====================
   options.marginMM = 15;                         // 页面边距:15毫米(约0.6英寸)// 值越小,可用打印区域越大
   options.titleFontSize = 25;        // 标题字体:25点(最大,突出显示)
  options.headerFontSize = 11;                   // 表头字体:11点(比内容稍大)// 建议范围:10-14点// 表头会自动加粗显示
  options.contentFontSize = 10;                  // 内容字体:10点(标准大小)// 建议范围:8-12点// 这是最重要的字体设置
  options.footerFontSize = 10;                   // 页脚字体:10点(与内容相同)// 控制页码和时间的字体大小
      // ==================== 4. 高度因子设置(关键!) ====================
   options.rowHeightFactor = 12.0;                // 行高因子:12.0(行高=字体高度×12) // 这是最重要的参数!// 普通打印:10-15
    options.headerHeightFactor = 2.0;              // 表头高度因子:2.0// 建议范围:1.5-2.5 // 控制表头行的高度
   options.titleHeightFactor = 5.0;               // 标题高度因子:5.0 // 建议范围:4-6// 控制标题区域的高度
    options.footerHeightFactor = 2.0;              // 页脚高度因子:2.0/ 建议范围:1.5-2.5 // 控制页脚区域的高度
       // ==================== 5. 换行设置 ====================
    options.textWrap = true;                      // 文本换行:false不换行(默认)// true:启用自动换行 // false:长文本显示省略号
     options.maxTextLines = 2;                      // 最大换行行数:3行// 启用换行时有效// 建议范围:2-5行
    // ==================== 6. 内边距设置 ====================
   options.cellPadding = 15;                       // 单元格内边距:8像素// 解决文字太靠近边框的问题// 建议范围:5-15像素// 值越大,文字离边框越远

   // ==================== 7. 缩放和拉伸设置 ====================
   options.scaleToPage = true;                    // 自动缩放:true(默认)// true:自动缩放表格以适应页面宽度// false:按原始尺寸打印
   options.stretchColumns = true;                 // 拉伸列宽:true(默认)// true:如果表格宽度小于页面,拉伸填满 // false:保持原始列宽比例
     // ==================== 8. 显示控制设置 ====================
   options.showGrid = true;                       // 显示网格线:true显示// true:显示浅灰色网格线// false:不显示网格线(更简洁)
   options.showHeader = true;                     // 显示表头:true显示// true:显示列标题 // false:不显示表头
    options.showFooter = true;                     // 显示页脚:true显示// true:显示页码、打印时间和标题  // false:不显示页脚
    // ==================== 9. 打印执行 ====================
  //TablePrinter::print(m_tableView, options, this);直接打印
    TablePrinter::printPreview(m_tableView, options, this);//预览打印

}
相关推荐
oioihoii7 小时前
构建高并发AI服务网关:C++与gRPC的工程实践
开发语言·c++·人工智能
X***07888 小时前
从底层逻辑到工程实践,深入理解C语言在计算机世界中的核心地位与持久价值
c语言·开发语言
晚枫歌F8 小时前
io_uring的介绍和实现
开发语言·php
时光追逐者8 小时前
TIOBE 公布 C# 是 2025 年度编程语言
开发语言·c#·.net·.net core·tiobe
花归去8 小时前
echarts 柱状图曲线图
开发语言·前端·javascript
2501_941870568 小时前
面向微服务熔断与流量削峰策略的互联网系统稳定性设计与多语言工程实践分享
开发语言·python
modelmd8 小时前
Go 编程语言指南 练习题目分享
开发语言·学习·golang
带土19 小时前
4. C++ static关键字
开发语言·c++
C++ 老炮儿的技术栈9 小时前
什么是通信规约
开发语言·数据结构·c++·windows·算法·安全·链表