如何在取消 QFileDialog 时停止 child window 关闭

How to stop child window closing when a QFileDialog is cancelled

我有一个 parent class 来处理打开的项目。可以从 child window 打开项目,它调用 parent 函数来处理打开项目。但是,当从 child window 取消 file-dialog 时,整个应用程序退出。

from PyQt5.QtCore import Qt, QDateTime
from PyQt5.QtWidgets import *
from PyQt5 import QtGui

class ParentWindow(QDialog):
    def __init__(self):
        super(ParentWindow, self).__init__()
        self.cw = None

        self.setFixedSize(300, 100)
        self.button = QPushButton('Open')
        self.button.clicked.connect(self.open)

        layout = QHBoxLayout()
        layout.addWidget(self.button)
        self.setLayout(layout)

        self.show()

    def open(self):
        fileDialog = QFileDialog(self, 'Projects')
        fileDialog.setFileMode(QFileDialog.DirectoryOnly)

        if fileDialog.exec():
            self.hide()
            name = fileDialog.selectedFiles()[0]
            if self.cw:
                self.cw.close()
            self.cw = ChildWindow(self, name)

class ChildWindow(QDialog):
    def __init__(self, parent, name):
        super(ChildWindow, self).__init__(parent)
        self.setFixedSize(500, 100)
        self.setWindowTitle(name)

        self.openButton = QPushButton('Open')
        self.openButton.clicked.connect(self.parent().open)

        layout = QHBoxLayout()
        layout.addWidget(self.openButton)
        self.setLayout(layout)

        self.show()

我不明白为什么在 file-dialg 中按下取消时程序不会 return 到 child window。有没有办法让 parent 负责打开项目并解决这个问题?

问题可能在于 hideshow 事件的不同事件时间:我想,直到 open 函数 returns,Qt 还没有然而 "registered" 作为 window 的 child 将检查 QApplication.quitOnLastWindowClosed() 选项,这意味着即使 child window 显示为一小部分时间,它仍然 "thinks" 只有一个 window(parent)。

根据您的要求有两种可能:

  • 在应用程序实例上使用 setQuitOnLastWindowClosed(False),记得在 parent window(或任何其他 window 的 CloseEvent 中调用 quit你想在关闭时退出);
  • 使用 QTimer.singleShot(1, self.hide)应该 足够延迟隐藏以避免这个问题;

第一种解决方案通常更好,我强烈建议您使用它。
我什至不确定使用 one-ms 延迟实际上是否足以允许向应用程序发出 "a new window exists" 通知:可能需要更高的数量,并且该值也可以是任意的,具体取决于各种条件(包括平台实施)。
根据 source code,一旦顶级小部件关闭,它就会检查所有 QApplication.topLevelWidgets(),但根据我的测试,列表不会立即更新:ChildWindow 通常 "appears" 一些 时间在 show() 之后,但有时(通常 <2 毫秒之后)它根本没有出现在列表中。

这是一个非常简单的修复方法:

def open(self):
    fileDialog = QFileDialog(self, 'Projects')
    fileDialog.setAttribute(Qt.WA_QuitOnClose, False)

或更简单:

def open(self):
    fileDialog = QFileDialog(self.sender(), 'Projects')

这里的问题是,每当 window 关闭时,Qt 会检查是否还有其他 windows 也应该关闭。在大多数情况下,如果满足以下两个条件,它将自动关闭 window:

  1. 设置了WA_QuitOnClose属性,
  2. 没有父级,父级被隐藏

不幸的是,在您的示例中,文件对话框和子 window 都是如此,这导致 windows 都被关闭。另外,由于quitOnLastWindowClosed默认为真,应用也会自动退出。

上面的第一个修复通过确保至少一个 window 没有设置 quit-on-close 属性来实现,第二个修复通过确保文件对话框的父级始终可见 window.