PyQt5 应用程序中的故障排除(QDialog -> QMainWindow)

Troubleshooting Crash within PyQt5 Application (QDialog -> QMainWindow)

我目前正在尝试解决 运行 我在 Python 中开发的 Qt 应用程序遇到的问题。我只有一个简单的 Main Window 和一个模态 QDialog 设置,用于在 Main Window 上触发菜单操作后收集用户输入。 QDialog 的行为符合预期,因为我已经能够确认测试打印是在 QDialog returning Accepted 时执行的,但是应用程序将在打印语句之后完全崩溃,没有错误消息。当 QDialog returns Rejected 时会发生相同的行为。澄清一下,Main Window 显示了几秒钟,应用程序崩溃且没有错误消息。我希望函数 return 在收到来自下面的 Process 函数的任一结果后聚焦到 Main Window(它仍然可以运行)。

我也试过使 QDialog 无模式(使用 show)并且 QDialog 中的 accept/reject 函数似乎 return 回到了预期的主要 window,但是调用再次调出 QDialog 的函数会使应用程序崩溃。我在这个项目中使用 pyqt 5.9,但我在 pyqt 5.6 的许多其他项目中使用了与下面代码基本相同的设置,但没有发生这种情况。我试图弄清楚 pyqt 5.9 是否存在任何可能导致此问题的已知问题,或者我的代码中是否存在导致此崩溃发生的错误。我正在考虑回滚到 5.6 看看是否可以解决这个问题,但我觉得可能是我误解了导致这种情况发生的原因。

我运行在 Windows 10 上从 Anaconda 提示符(Anaconda 4.8.3,Python 3.7)中提取代码,因为 Qt 应用程序仍然存在问题 运行 在 Spyder 中重复。我正在使用 Anaconda 附带的 pyqt,并且没有对 pyqt 进行任何额外的 pip 安装。

主要Window代码

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QGridLayout, QHBoxLayout, QVBoxLayout, QAction, QLabel
from PyQt5.QtWidgets import QDialog, QListWidget, QListWidgetItem, QAbstractItemView, QPushButton, QLineEdit, QSpacerItem, QSizePolicy


class FirstWindow(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        
        self.title = 'Window 1'
        self.left = 350
        self.top = 150
        self.width = 800
        self.height = 500
        
        self.setWindowTitle(self.title)
        self.setGeometry(self.left,self.top,self.width,self.height)
        
        widget = QWidget()
        self.setCentralWidget(widget)
        grid = QGridLayout()
        widget.setLayout(grid)
        
        mainMenu = self.menuBar()
        mainMenu.setNativeMenuBar(False)
        subMenu = mainMenu.addMenu('File')
        
        modifyDB = QAction('Test',self)
        subMenu.addAction(modifyDB)
        modifyDB.triggered.connect(self.Process)
        
        self.statusBar().showMessage('Ready')

    def Process(self):
        dialog = DialogWin()
        if dialog.exec_() == QDialog.Accepted:
            print('test passed')

if __name__ == '__main__':
    if not QApplication.instance():
        app = QApplication(sys.argv)
    else:
        app = QApplication.instance()
    mainWin = FirstWindow()
    mainWin.show()
    app.exec_()

对话框代码

class DialogWin(QDialog):
    def __init__(self,parent=None):
        super(DialogWin,self).__init__(parent)

        self.title = 'Database Table Name Selection'
        self.left = 350
        self.top = 150
        self.width = 300
        self.height = 300
        
        self.setWindowTitle(self.title)
        self.setGeometry(self.left,self.top,self.width,self.height)

        vbox = QVBoxLayout()
        spacer = QSpacerItem(10,10,QSizePolicy.Expanding,QSizePolicy.Expanding)

        self.list_exp = QLabel('Select Existing Table to Overwrite')
        vbox.addWidget(self.list_exp)

        self.table_list = QListWidget()
        self.table_list.setSelectionMode(QAbstractItemView.SingleSelection)
        vbox.addWidget(self.table_list)
        
        hbox = QHBoxLayout()
        hbox.addItem(spacer)
        self.option_exp = QLabel('<span style="font-size:8pt; font-weight:600; color:#aa0000;">OR</span>')
        hbox.addWidget(self.option_exp)
        hbox.addItem(spacer)
        vbox.addLayout(hbox)
       
        self.new_name = QLineEdit(placeholderText='Enter New Source Name')       
        vbox.addWidget(self.new_name)

        hbox = QHBoxLayout()
        self.okButton = QPushButton('OK')
        hbox.addWidget(self.okButton)
        self.okButton.clicked.connect(self.accept)
        
        self.cancelButton = QPushButton('Cancel')
        hbox.addWidget(self.cancelButton)
        self.cancelButton.clicked.connect(self.reject)
        
        vbox.addLayout(hbox)
        self.setLayout(vbox)

        item = QListWidgetItem('No Tables Available...')
        item.setFlags(item.flags() ^ Qt.ItemIsSelectable)
        self.table_list.addItem(item)

有关此问题的任何意见都将非常有帮助。我花了很多时间试图了解此设置如何在一个应用程序中为我工作而不在另一个应用程序中工作。

解释:

问题是您使用了同一个 QSpacerItem 2 次,当 QDialog 关闭时,因为它是一个局部变量,它将被删除,Qt 也会消除内部对象,在这种情况下,QSpacerItem将有导致“分段错误”的双重淘汰。

解决方案:

您必须创建 2 个 QSpacerItem:

# ...
hbox = QHBoxLayout()
hbox.addItem(spacer)
self.option_exp = QLabel('OR')
hbox.addWidget(self.option_exp)
<b>hbox.addItem(QSpacerItem(10,10,QSizePolicy.Expanding,QSizePolicy.Expanding))</b>
vbox.addLayout(hbox)
# ...

另一种选择是不使用 QSpacerItem 而是设置拉伸因子:

# ...
hbox = QHBoxLayout()
<b>hbox.addStretch()</b>
self.option_exp = QLabel('OR')
hbox.addWidget(self.option_exp)
<b>hbox.addStretch()</b>
vbox.addLayout(hbox)
# ...

或者不使用QHBoxLayout,通过设置对齐方式直接将QLabel设置为QVBoxLayout:

# ...
self.table_list = QListWidget()
self.table_list.setSelectionMode(QAbstractItemView.SingleSelection)
vbox.addWidget(self.table_list)

self.option_exp = QLabel('OR')
<b>vbox.addWidget(self.option_exp, alignment=Qt.AlignHCenter)</b>
   
self.new_name = QLineEdit(placeholderText='Enter New Source Name')       
vbox.addWidget(self.new_name)
# ...