我如何 "Print Preview" 由 PyQt5 中的 QTextDocument 创建的文档?

How can I "Print Preview" of document created by QTextDocument in PyQt5?

专家您好!!希望您今天过得愉快。我是 GUI 编程的新手,特别是 PyQt5。我正在练习简单的 GUI 发票应用程序。在这个应用程序中,我成功地通过 QTextDocument 生成了发票。现在我想添加打印对话和打印预览选项。我在代码中遇到问题。这是说

AttributeError: 'InvoiceForm' object has no attribute 'printpreviewDialog

因为我是新手,我在那里有点困惑。你能修复代码吗?这将对我的学习有很大帮助。非常感谢。 代码如下:-

import sys
from PyQt5.QtCore import pyqtSignal, QSize, QSizeF, QDate
from PyQt5.QtGui import QTextDocument, QTextCursor, QFont
from PyQt5.QtPrintSupport import QPrinter, QPrintPreviewDialog
from PyQt5.QtWidgets import QWidget, QFormLayout, QLineEdit, QPlainTextEdit, QSpinBox, QDateEdit, QTableWidget, \
    QHeaderView, QPushButton, QHBoxLayout, QTextEdit, QApplication, QMainWindow

font= QFont('Arial',16)

class InvoiceForm(QWidget):
    submitted = pyqtSignal(dict)
    def __init__(self):
        super().__init__()
        self.setLayout(QFormLayout())
        self.inputs = dict()
        self.inputs['Customer Name'] = QLineEdit()
        self.inputs['Customer Address'] = QPlainTextEdit()
        self.inputs['Invoice Date'] = QDateEdit(date=QDate.currentDate(), calendarPopup=True)
        self.inputs['Days until Due'] = QSpinBox()
        for label, widget in self.inputs.items():
            self.layout().addRow(label, widget)

        self.line_items = QTableWidget(rowCount=10, columnCount=3)
        self.line_items.setHorizontalHeaderLabels(['Job', 'Rate', 'Hours'])
        self.line_items.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.layout().addRow(self.line_items)
        for row in range(self.line_items.rowCount()):
            for col in range(self.line_items.columnCount()):
                if col > 0:
                    w = QSpinBox()
                    self.line_items.setCellWidget(row, col, w)

        submit = QPushButton('Create Invoice', clicked=self.on_submit)
        print = QPushButton('Print Invoice', clicked=self.printpreviewDialog)
        self.layout().addRow(submit,print)

    def on_submit(self):
        data = {'c_name': self.inputs['Customer Name'].text(),
                'c_addr': self.inputs['Customer Address'].toPlainText(),
                'i_date': self.inputs['Invoice Date'].date().toString(),
                'i_due': self.inputs['Invoice Date'].date().addDays(self.inputs['Days until Due'].value()).toString(),
                'i_terms': '{} days'.format(self.inputs['Days until Due'].value()),
                'line_items': list()}

        for row in range(self.line_items.rowCount()):
            if not self.line_items.item(row, 0):
                continue
            job = self.line_items.item(row, 0).text()
            rate = self.line_items.cellWidget(row, 1).value()
            hours = self.line_items.cellWidget(row, 2).value()
            total = rate * hours
            row_data = [job, rate, hours, total]
            if any(row_data):
                data['line_items'].append(row_data)

        data['total_due'] = sum(x[3] for x in data['line_items'])
        self.submitted.emit(data)
        # remove everything else in this function below this point

class InvoiceView(QTextEdit):
    dpi = 72
    doc_width = 8.5 * dpi
    doc_height = 6 * dpi

    def __init__(self):
        super().__init__(readOnly=True)
        self.setFixedSize(QSize(self.doc_width, self.doc_height))

    def build_invoice(self, data):
        document = QTextDocument()
        self.setDocument(document)
        document.setPageSize(QSizeF(self.doc_width, self.doc_height))
        document.setDefaultFont(font)
        cursor = QTextCursor(document)
        cursor.insertText(f"Customer Name: {data['c_name']}\n")
        cursor.insertText(f"Customer Address: {data['c_addr']}\n")
        cursor.insertText(f"Date: {data['i_date']}\n")
        cursor.insertText(f"Total Due: {data['total_due']}\n")

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        central = QWidget()
        self.setCentralWidget(central)
        layout = QHBoxLayout(central)

        self.invoiceForm = InvoiceForm()
        layout.addWidget(self.invoiceForm)

        self.invoiceView = InvoiceView()
        layout.addWidget(self.invoiceView)
        # hide the widget right now...
        self.invoiceView.setVisible(False)

        self.invoiceForm.submitted.connect(self.showPreview)

    def showPreview(self, data):
        self.invoiceView.setVisible(True)
        self.invoiceView.build_invoice(data)

    def printpreviewDialog(self):
        printer = QPrinter(QPrinter.HighResolution)
        previewDialog = QPrintPreviewDialog(printer, self)
        previewDialog.paintRequested.connect(self.printPreview)
        previewDialog.exec_()

    def printPreview(self, printer):
        self.invoiceView.build_invoice.print_(printer)

def main():
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    app.exec_()

if __name__ == '__main__':
    main()

主要问题是 self.printpreviewDialogMainWindow 的成员,而不是 InvoiceForm 的成员,所以你应该从主 [=33] 连接 clicked 信号=] 代替。

另请注意,您尝试使用 self.invoiceView.build_invoice.print_(),但这不会起作用,因为您没有 调用 build_invoice,即使您使用了, 该函数没有 return 任何东西。

您应该使用self.invoiceView.document()代替,但您必须确保之前已构建数据。

class InvoiceForm(QWidget):
    submitted = pyqtSignal(dict)
    def __init__(self):
        # ...
        submit = QPushButton('Create Invoice', clicked=self.on_submit)
        # make the button a member of the instance instead of a local variable, 
        # so that we can connect from the main window instance
        <b>self.printButton</b> = QPushButton('Print Invoice')
        self.layout().addRow(submit, <b>self.printButton</b>)

# ...

class MainWindow(QMainWindow):
    def __init__(self):
        # ...
        <b>self.invoiceForm.printButton.clicked.connect(self.printpreviewDialog)</b>
    # ...

    def printPreview(self, printer):
        <b>self.invoiceView.document().print_(printer)</b>

注意:从不,从不 使用 built-in 函数和语句作为变量名,例如 print.

试一试:

import sys
from PyQt5.QtCore import pyqtSignal, QSize, QSizeF, QDate
from PyQt5.QtGui import QTextDocument, QTextCursor, QFont
from PyQt5.QtPrintSupport import QPrinter, QPrintPreviewDialog
from PyQt5.QtWidgets import (QWidget, QFormLayout, QLineEdit, QPlainTextEdit, 
    QSpinBox, QDateEdit, QTableWidget, QHeaderView, QPushButton, QHBoxLayout, 
    QTextEdit, QApplication, QMainWindow)


font = QFont('Arial',16)


class InvoiceForm(QWidget):
    submitted = pyqtSignal(dict)
    
    def __init__(self, parent=None):                                        # + parent=None
        super().__init__(parent)                                            # + parent
        self.setLayout(QFormLayout())
        self.inputs = dict()
        self.inputs['Customer Name'] = QLineEdit()
        self.inputs['Customer Address'] = QPlainTextEdit()
        self.inputs['Invoice Date'] = QDateEdit(date=QDate.currentDate(), calendarPopup=True)
        self.inputs['Days until Due'] = QSpinBox()
        for label, widget in self.inputs.items():
            self.layout().addRow(label, widget)

        self.line_items = QTableWidget(rowCount=10, columnCount=3)
        self.line_items.setHorizontalHeaderLabels(['Job', 'Rate', 'Hours'])
        self.line_items.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.layout().addRow(self.line_items)
        for row in range(self.line_items.rowCount()):
            for col in range(self.line_items.columnCount()):
                if col > 0:
                    w = QSpinBox()
                    self.line_items.setCellWidget(row, col, w)

        submit = QPushButton('Create Invoice', clicked=self.on_submit)
        
# +     vvvvvv                                        vvvvvvvvvvvvv 
        _print = QPushButton('Print Invoice', clicked=self.window().printpreviewDialog)  # + _print, + self.window()
        self.layout().addRow(submit, _print)                                             # + _print

    def on_submit(self):
        data = {'c_name': self.inputs['Customer Name'].text(),
                'c_addr': self.inputs['Customer Address'].toPlainText(),
                'i_date': self.inputs['Invoice Date'].date().toString(),
                'i_due': self.inputs['Invoice Date'].date().addDays(self.inputs['Days until Due'].value()).toString(),
                'i_terms': '{} days'.format(self.inputs['Days until Due'].value()),
                'line_items': list()}

        for row in range(self.line_items.rowCount()):
            if not self.line_items.item(row, 0):
                continue
            job = self.line_items.item(row, 0).text()
            rate = self.line_items.cellWidget(row, 1).value()
            hours = self.line_items.cellWidget(row, 2).value()
            total = rate * hours
            row_data = [job, rate, hours, total]
            if any(row_data):
                data['line_items'].append(row_data)

        data['total_due'] = sum(x[3] for x in data['line_items'])
        self.submitted.emit(data)
        # remove everything else in this function below this point
# +
        return data                                                            # +++
        

class InvoiceView(QTextEdit):
    dpi = 72
    doc_width = 8.5 * dpi
    doc_height = 6 * dpi

    def __init__(self):
        super().__init__(readOnly=True)
        self.setFixedSize(QSize(self.doc_width, self.doc_height))

    def build_invoice(self, data):
        document = QTextDocument()
        self.setDocument(document)
        document.setPageSize(QSizeF(self.doc_width, self.doc_height))
        document.setDefaultFont(font)
        cursor = QTextCursor(document)
        cursor.insertText(f"Customer Name: {data['c_name']}\n")
        cursor.insertText(f"Customer Address: {data['c_addr']}\n")
        cursor.insertText(f"Date: {data['i_date']}\n")
        cursor.insertText(f"Total Due: {data['total_due']}\n")
# +        
        return document                                                         # +++
        

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        central = QWidget()
        self.setCentralWidget(central)
        layout = QHBoxLayout(central)
# +                                    vvvv
        self.invoiceForm = InvoiceForm(self)                                    # + self
        
        layout.addWidget(self.invoiceForm)
        self.invoiceView = InvoiceView()
        layout.addWidget(self.invoiceView)
        # hide the widget right now...
        self.invoiceView.setVisible(False)
        self.invoiceForm.submitted.connect(self.showPreview)

    def showPreview(self, data):
        self.invoiceView.setVisible(True)
        self.invoiceView.build_invoice(data)

# +++ vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
    def printpreviewDialog(self):
        previewDialog = QPrintPreviewDialog()
        previewDialog.paintRequested.connect(self.printPreview)  
        previewDialog.exec_()

    def printPreview(self, printer):
#        self.invoiceView.build_invoice.print_(printer)        
        data = self.invoiceForm.on_submit()
        document = self.invoiceView.build_invoice(data)
        document.print_(printer)
# +++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


def main():
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    app.exec_()

if __name__ == '__main__':
    main()