Qt Commercial Support Weekly #25 – Printing large tables

Have you ever had problems printing a large table? Every now and then support is approached with questions on how to achieve this. The problem comes when one has a QTableView and it’s larger than what is shown on the screen. Using grabWidget() method is the general way to go about this, but how do we use it so that the created pixmap can be used for printing?

 

Let’s start with a table where all the items do not fit in the view, instead scrollbars are used. To be able to print all the items we have to adjust the size of the widget before calling grabWidget() method so that the scrollbars are not used when painting the table, instead the table has fixed size. We can calculate the size of the table widget utilizing columnWidth() and rowHeight() methods and adding the width and height of the headers to it.

 

    const int rows = ui->tableWidget->model()->rowCount();
const int columns = ui->tableWidget->model()->columnCount();
double totalWidth = ui->tableWidget->verticalHeader()->width();
for (int c = 0; c < columns; ++c)
totalWidth += ui->tableWidget->columnWidth(c);
double totalHeight = ui->tableWidget->horizontalHeader()->height();
for (int r = 0; r < rows; ++r)
totalHeight += ui->tableWidget->rowHeight(r);

 

To avoid possible visual updates we will use a temporary QTableView which is not shown on the screen which we can ensure with the WA_DontShowOnScreen attribute. We will set the size of the table based on the width and height we just calculated. Also we will make sure that scrollbars are not shown for the table. Then we can use the grabWidget() method to paint the table.

 

    QTableView tempTable;
tempTable.setAttribute(Qt::WA_DontShowOnScreen);
tempTable.setModel(ui->tableWidget->model());
tempTable.setFixedSize(totalWidth, totalHeight);
tempTable.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
tempTable.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
QPixmap grabPixmap = QPixmap::grabWidget(&tempTable);

 

Now we have our table widget with all the items and headers painted to a pixmap. If we try to print this pixmap, then what will most likely happen is that the columns or the rows are not evenly divided between pages. Some of them may be cut in half. This makes reading the printed table very difficult. We have to print the table so that only full rows and columns are printed to each page.

 

What we will do to help resolve this is to use a helper rectangle to define the area from the pixmap that is printed into one page. To make this example straightforward to read we will define the amount of columns and rows printed. This can easily be changed so that the width and height is compared to the actual page size.

 

First we will take the first four rows and columns (along with the headers) and calculate the space they occupy. The helper rectangle is used for this. Then we will use this rectangle to take the correct part of the grabbed pixmap for printing. Needed variables are initialized and then the size of the rectangle is added up. The fact that the horizontal header is only printed with the top most items and the vertical header with the left most items is taken into account.

 

  QRectF sourceRect;
double totalPageHeight = ui->tableWidget->horizontalHeader()->height();
int columnCount = 0;
int rowCount = 0;
// First take the rows that fit into one page
for (int h = 0; h < ui->tableWidget->model()->rowCount(); h++) {
totalPageHeight += ui->tableWidget->rowHeight(h);
double totalPageWidth = ui->tableWidget->verticalHeader()->width();

 

When we have reached the amount of rows we will print into one page, the width of the columns we want into that page is defined.

 

  if (rowCount == 3 || h == ui->tableWidget->model()->rowCount() - 1) {
// Then take the columns that fit into one page
for (int w = 0; w < ui->tableWidget->model()->columnCount(); w++) {
totalPageWidth += ui->tableWidget->columnWidth(w);

 

Now that we have determined the size of the rows and columns, the size is set to the rectangle we use for printing. Now the first part of the table is ready for printing.


    if (columnCount == 3 || (w == ui->tableWidget->model()->columnCount()
- 1)) {

        sourceRect.setWidth(totalPageWidth);
        sourceRect.setHeight(totalPageHeight);
        painter.drawPixmap(printer.pageRect().topLeft(), pixmap,
sourceRect);

 

After printing the first page we have to adjust the rectangle so that it’s moved to the next part of the table we want to print. To be able to print the next area to a new page, a page break is added and the page width and column count are reset. This way we can continue on to the next page and start going through the columns from the beginning.

 

    sourceRect.setLeft(sourceRect.left() + totalPageWidth);
if (w != ui->tableWidget->model()->columnCount() - 1)
printer.newPage();
totalPageWidth = 0;
columnCount = 0;

 

A page break is also needed after all the rows in one page have been counted in. As with the rows, the rectangle is adjusted to the next part of the table and the page height is reset. We will cover all the parts of the pixmap this way until the whole table is printed out.

 

	sourceRect.setTop(sourceRect.top() + totalPageHeight);
sourceRect.setLeft(0);
if (h != ui->tableWidget->model()->rowCount() - 1)
printer.newPage();
totalPageHeight = 0;
rowCount = 0;

 

So here we have a large table printed to several pages and what’s even better, the items in the table are no longer chopped off in the middle. If you want to see the above code snippets in action you can find the full example here.

 

Hopefully this clarified some of the questions you might have regarding printing tables. If this raises questions you can always contact us in the Qt Commercial support team via the Qt Commercial Support portal.


Comments