QFileDialog 在将其添加到 QHBoxLayout 时以新 window 打开

QFileDialog opens in new window while adding it to QHBoxLayout

我的问题是,当我将 QFileDialog 添加到 QVBoxLayout 时,它会在新 window 中打开。下面是产生我的问题的代码。

from PyQt5.QtWidgets import QVBoxLayout, QFileDialog, QPushButton, QWidget


class MainWindow(QtWidgets.QWidget):
    def __init__(self):
       super(MainWindow, self).__init__()
       self.setWindowTitle("My own MainWindow")

       self.fileDialog = QFileDialog()

       self.confirmAction = QPushButton("Press me", self)

       mainLayout = QVBoxLayout()

       mainLayout.addWidget(self.fileDialog)
       mainLayout.addWidget(self.confirmAction)
       self.setLayout(mainLayout)

根据 docs:

Window flags are a combination of a type (e.g. Qt::Dialog) and zero or more hints to the window system (e.g. Qt::FramelessWindowHint).

If the widget had type Qt::Widget or Qt::SubWindow and becomes a window (Qt::Window, Qt::Dialog, etc.), it is put at position (0, 0) on the desktop. If the widget is a window and becomes a Qt::Widget or Qt::SubWindow, it is put at position (0, 0) relative to its parent widget.

所以这些标志用于改变小部件的行为,例如将其转换为 window、对话框、工具提示等。

docs中给出如下列表:

Qt::Widget: This is the default type for QWidget. Widgets of this type are child widgets if they have a parent, and independent windows if they have no parent. See also Qt::Window and Qt::SubWindow.

Qt::Window: Indicates that the widget is a window, usually with a window system frame and a title bar, irrespective of whether the widget has a parent or not. Note that it is not possible to unset this flag if the widget does not have a parent.

Qt::Dialog :Window Indicates that the widget is a window that should be decorated as a dialog (i.e., typically no maximize or minimize buttons in the title bar). This is the default type for QDialog. If you want to use it as a modal dialog, it should be launched from another window, or have a parent and used with the QWidget::windowModality property. If you make it modal, the dialog will prevent other top-level windows in the application from getting any input. We refer to a top-level window that has a parent as a secondary window.

Qt::Sheet: Window Indicates that the window is a Macintosh sheet. Since using a sheet implies window modality, the recommended way is to use QWidget::setWindowModality(), or QDialog::open(), instead.

Qt::Drawer: Window Indicates that the widget is a Macintosh drawer.

Qt::Popup : Window Indicates that the widget is a pop-up top-level window, i.e. that it is modal, but has a window system frame appropriate for pop-up menus.

Qt::Tool: Window Indicates that the widget is a tool window. A tool window is often a small window with a smaller than usual title bar and decoration, typically used for collections of tool buttons. If there is a parent, the tool window will always be kept on top of it. If there isn't a parent, you may consider using Qt::WindowStaysOnTopHint as well. If the window system supports it, a tool window can be decorated with a somewhat lighter frame. It can also be combined with Qt::FramelessWindowHint.

On Mac OS X, tool windows correspond to the Floating class of windows. This means that the window lives on a level above normal windows; it impossible to put a normal window on top of it. By default, tool windows will disappear when the application is inactive. This can be controlled by the Qt::WA_MacAlwaysShowToolWindow attribute.

Qt::ToolTip:Window Indicates that the widget is a tooltip. This is used internally to implement tooltips.

Qt::SplashScreen: Window Indicates that the window is a splash screen. This is the default type for QSplashScreen.

Qt::Desktop:Window Indicates that this widget is the desktop. This is the type for QDesktopWidget.

Qt::SubWindow: Indicates that this widget is a sub-window, such as a QMdiSubWindow widget.

在您的情况下,我们必须将 Qt::Dialog 的行为更改为 Qt::Widget,在下面的代码中,我展示了执行此操作的代码:

class MainWindow(QWidget):
    def __init__(self):
       super(MainWindow, self).__init__()
       self.setWindowTitle("My own MainWindow")

       self.fileDialog = QFileDialog(self)
       self.fileDialog.setOption(QFileDialog.DontUseNativeDialog)
       self.fileDialog.setWindowFlags(Qt.Widget)

       self.confirmAction = QPushButton("Press me", self)

       mainLayout = QVBoxLayout()

       mainLayout.addWidget(self.fileDialog)
       mainLayout.addWidget(self.confirmAction)
       self.setLayout(mainLayout)

截图:

我自己一直在研究这个问题,对 "just use non-native dialogs" 位不满意。我一直在研究 KDE 平台文件对话框的实现,并且已经(卡住了)非常接近我想要的东西。

我发现的唯一连接点就在对话框实际显示之前;在此之前似乎没有办法知道实际的父窗口小部件。但是我们可以:

  • 找到父QWidget(来自父QWindow
  • 从中获取(第一个)(用户端)QFileDialog 实例
  • 如果父QWidget有布局,将找到的QFileDialog实例替换为我们自己的对话框
  • 保存原来的用户端QFileDialog实例
  • 在 dtor 中,要么恢复布局中的原始 QFD,要么对其调用 deleteLater()(并将其设置为 NULL,以防该操作导致 dtor 的递归调用)。

故障: - 对话可能以 2 组 OK/Cancel/etc 结束。纽扣 - 如果没有,这些按钮实际上可能只会关闭嵌入式 QFD 而不会关闭封闭对话框(参见上面链接的 python 示例) - 调整大小有效,但 saveSize/restoreSize 机制无效 - AFAICT 所有信号都没有正确连接(Scribus 打开文件对话框中的预览不会对选择文件做出应有的反应)。文件打开确实有效。

这张 BKO 票的完整补丁在这里: https://bugs.kde.org/show_bug.cgi?id=404833#c15

显然这只对黑客和可以发布自己的平台主题插件(这是 KDE 平台文件对话框的来源)的软件有用。幸运的是,这些插件往往相对较小。