We will now see how to achieve the same thing by creating a QTextDocument programmatically, rather than by creating an HTML string and using set-Html(). The code is more than twice as long (as is the code that uses QPainter that follows), but we should not infer from this particular example that these techniques will necessarily require more code in general.
dialog = QPrintDialog(self.printer, self) if not dialog.exec_(): return logo = QPixmap(":/logo.png") headFormat = QTextBlockFormat() headFormat.setAlignment(Qt.AlignLeft)
We iterate over each transaction, adding a new row to the table for each one. Then we add the closing table tag and add the final paragraph. We want a page break to follow the last paragraph, and this can be achieved by setting the page-break-after style option to always. This style option was added in Qt 4.2 and is ignored in earlier versions.
self.printer.pageRect().width() - logo.width() - 216) bodyFormat = QTextBlockFormat() bodyFormat.setAlignment(Qt.AlignJustify) lastParaBodyFormat = QTextBlockFormat(bodyFormat) lastParaBodyFormat.setPageBreakPolicy(
QTextFormat.PageBreak_AlwaysAfter) rightBodyFormat = QTextBlockFormat() rightBodyFormat.setAlignment(Qt.AlignRight) headCharFormat = QTextCharFormat() headCharFormat.setFont(QFont("Helvetica", 10)) bodyCharFormat = QTextCharFormat() bodyCharFormat.setFont(QFont("Times", 11)) redBodyCharFormat = QTextCharFormat(bodyCharFormat) redBodyCharFormat.setForeground(Qt.red) tableFormat = QTextTableFormat() tableFormat.setBorder(1) tableFormat.setCellPadding(2)
We have chosen to create the document only if the user clicks Print in the Print dialog, rather than creating it and only asking them at the end, as we did before. We create a set of text formats—some are QTextBlockFormats that have attributes which are applicable to entire paragraphs, and others are QTextChar-Formats with attributes that are applicable to fragments of paragraphs, such as phrases, words, and individual characters. We use the paragraph formats to set up text alignments, and the character formats to set up fonts and colors. The value, 216, is just an offset in points, 226", that is, 3 inches, by which the logo and address text will be indented.
document = QTextDocument() cursor = QTextCursor(document) mainFrame = cursor.currentFrame() page = 1
Once we have the formats ready, we create a QTextDocument. Then we create a QTextCursor for the document that gives us the programmatic equivalent of the user's insertion point in a QTextEdit.
Earlier we mentioned that QTextDocuments consist of a series of blocks; in fact, they consist of a root frame that itself contains a series of items which can be blocks (e.g., text blocks and table blocks), or frames, in a potentially recursive structure. In our case, we have a document with a single root frame that
The programmatic equivalent of setting the page-break-after style option in an HTML <p> tag is to use the QTextBlockFormat.setPageBreakPolicy() method on a paragraph format, but this is available only from Qt 4.2. In addition to the text and table formats used in this example, there are also formats for lists, frames, and images.
contains a series of text blocks and tables. Each cell in our tables holds a text block, and when we have finished inserting the cells in a table we need to go back up the document's hierarchy to the point that follows the table (but is not inside the table) so that we can insert the text that follows each table. For this reason we keep a reference to the currentFrame(), the one frame we are using, in the mainFrame variable.
for statement in self.statements:
cursor.insertBlock(headFormat, headCharFormat) cursor.insertImage(":/logo.png")
for text in ("Greasy Hands Ltd.", "New Lombard Street", "London", "WC13 4PX",
QDate.currentDate().toString(DATE_FORMAT)): cursor.insertBlock(headFormat, headCharFormat) cursor.insertText(text) for line in statement.address.split(", "):
cursor.insertBlock(bodyFormat, bodyCharFormat) cursor.insertText(line) cursor.insertBlock(bodyFormat) cursor.insertBlock(bodyFormat, bodyCharFormat) cursor.insertText("Dear %s," % statement.contact) cursor.insertBlock(bodyFormat) cursor.insertBlock(bodyFormat, bodyCharFormat) balance = statement.balance() cursor.insertText(QString(
"The balance of your account is $ %L1.").arg( float(balance), 0, "f", 2)) if balance < 0:
cursor.insertBlock(bodyFormat, redBodyCharFormat) cursor.insertText("Please remit the amount owing " "immediately.")
cursor.insertBlock(bodyFormat, bodyCharFormat) cursor.insertText("We are delighted to have done " "business with you.") cursor.insertBlock(bodyFormat, bodyCharFormat) cursor.insertText("Transactions:")
table = cursor.insertTable(len(statement.transactions), 3, tableFormat)
Once the document is set up and we have a QTextCursor through which we can insert items into the document, we are ready to iterate over each of the statements.
For each paragraph we want to insert, we insert a new block with a paragraph and a character format. We then insert the text or image we want the paragraph to contain. We can insert empty paragraphs (to consume vertical space) by inserting a block without inserting anything into it.
To insert a table we must specify how many rows and columns it should have, as well as its format.
for date, amount in statement.transactions:
cellCursor = table.cellAt(row, 0).firstCursorPosition()
cellCursor.insertText(date.toString(DATE_FORMAT), bodyCharFormat) cellCursor = table.cellAt(row, 1).firstCursorPosition() if amount > 0:
cellCursor.insertText("Credit", bodyCharFormat) else:
cellCursor.insertText("Debit", bodyCharFormat) cellCursor = table.cellAt(row, 2).firstCursorPosition() cellCursor.setBlockFormat(rightBodyFormat) format = bodyCharFormat if amount < 0:
format = redBodyCharFormat cellCursor.insertText(QString("$ %L1").arg( float(amount), 0, "f", 2), format)
Each row of the table represents a single transaction, with a date, some text ("Debit" or "Credit"), and the amount, colored red in the case of debits. To insert items into a table we must obtain a QTextCursor that gives access to a cell at a specified row and column. We do not have to insert a new block into a cell (unless we want more than one paragraph in a cell), so we simply set the cell's paragraph format and insert the text we want.
cursor.setPosition(mainFrame.lastPosition()) cursor.insertBlock(bodyFormat, bodyCharFormat) cursor.insertText("We hope to continue doing business "
"with you,") cursor.insertBlock(bodyFormat, bodyCharFormat) cursor.insertText("Yours sincerely") cursor.insertBlock(bodyFormat) if page == len(self.statements):
cursor.insertBlock(bodyFormat, bodyCharFormat) else:
cursor.insertBlock(lastParaBodyFormat, bodyCharFormat) cursor.insertText("K. Longrey, Manager") page += 1
Once we have finished populating a table and want to add items after it, we must reset the position of our text cursor to be just after the table. If we do not do this, the cursor will simply insert inside the table and we will end up with the rest of the first page inside the table, and the second page inside the first, and so on recursively! To avoid this problem we set the text cursor to be at the last position in the document, which is the position following the last thing we inserted, that is, just after the table.
Finishing the page is simply a matter of inserting additional blocks with the appropriate formats, followed by inserting the relevant texts. For all pages except the last, we set the last block's format to be lastParaBodyFormat, which (using Qt 4.2) will ensure that what follows will be on a fresh page.
The very last statement is where we print the document on the printer. At this point the document is complete, so we could call toHtml() on it to get it in HTML format if that was preferred. It also means that we can use a QTextCursor in conjunction with a QTextDocument to create HTML pages programmatically if we wanted.
The advantage of using QTextDocument, whether we give it an HTML string or whether we populate it using a QTextCursor, is that we can avoid doing lots of calculations to see where text should be placed on the page. The disadvantage is that PyQt puts page numbers on our documents whether we like them or not, and it does not give us fine positional control. Neither of these problems occurs if we use a QPainter.
Was this article helpful?
Download Tube Jacker And Discover Everything You Need To Know About Jacking Unlimited Traffic From The Video Giant. The drop dead easy way to create winning video campaigns that will FLOOD your website with unstoppable FREE traffic, all on complete and total autopilot. How to exploit a sneaky method of boosting exposure and getting your videos to the top of Google within 72 hours, guaranteed.