首页 > 解决方案 > 为什么当我单击提交按钮时,此 PyQt5 发票 GUI 应用程序的“Python 停止工作”?

问题描述

如果你能帮助我解决这个问题,我会很高兴。我正在练习 PyQt5 Gui 应用程序的编码。这是生成发票的简单 GUI 应用程序。但是每当我单击提交(创建发票)按钮时,它就会停止。我无法弄清楚问题所在。请帮我解决问题。

更新:任何人都可以无错误地修复代码吗?错误消息是:

>>> Exception "unhandled AttributeError"
'InvoiceForm' object has no attribute 'setCentralWidget'

代码如下: -

import sys

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


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)
        self.layout().addRow(submit)

    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)
        main = QWidget()
        main.setLayout(QHBoxLayout())
        self.setCentralWidget(main)

        form = InvoiceForm()
        main.layout().addWidget(form)
        self.preview = InvoiceView()
        main.layout().addWidget(self.preview)
        form.submitted.connect(self.preview.build_invoice)

class InvoiceView(QTextEdit):
    dpi = 72
    doc_width = 8.5 * dpi
    doc_height = 11 * 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))
        cursor = QTextCursor(document)
        cursor.insertText("Invoice, woohoo!")

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

if __name__ == '__main__':
    main()

标签: pythonpyqtpyqt5

解决方案


如果你在 shell/prompt 中运行你的代码,你会清楚地看到错误:

>>> Exception "unhandled AttributeError"
'InvoiceForm' object has no attribute 'setCentralWidget'

问题是这setCentralWidget是 QMainWindow 的一个功能,而您使用的是简单的 QWidget。

虽然您可以尝试InvoiceForm从 QMainWindow 子类化并相应地修改其布局和逻辑,但它不会很好地工作,因为您犯了一些概念错误:最重要的是尝试向自身添加一个实例,但您之前InvoiceForm也发出了submitted信号将其连接到,因此它不会按预期工作。build_invoice

最好的解决方案是创建一个 QMainWindow 子类并将这些小部件的实例添加到其中。

import sys

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


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)
        self.layout().addRow(submit)

    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):
    # ...

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 main():
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    app.exec_()

if __name__ == '__main__':
    main()

推荐阅读