从QDialog中引发一个可以移动的子窗口(QWidget)?

问题描述 投票:0回答:1

在阅读https://www.pythonguis.com/tutorials/creating-multiple-windows/时,我可能太字面地理解了以下内容:

在 Qt 中,任何没有父级的小部件都是窗口。这意味着,要显示新窗口,您只需创建小部件的新实例。

...这就是为什么我发现下面代码的行为有些令人惊讶。

在下面的代码中,我希望能够通过单击主应用程序中的按钮来启动/引发 QDialog 窗口;然后我希望能够从 QDialog 启动/升起另一个窗口:

import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel, QVBoxLayout, QWidget, QDialog
from PyQt5.QtCore import Qt

class MainWindow(QMainWindow):

  def __init__(self):
    super().__init__()
    self.title = QLabel("MainWindow")
    self.button = QPushButton("Click for SubWindow")
    self.button.clicked.connect(self.show_sub_window)
    self.layout = QVBoxLayout()
    self.layout.addWidget(self.title)
    self.layout.addWidget(self.button)
    self.central_widget = QWidget()
    self.central_widget.setLayout(self.layout)
    self.setCentralWidget(self.central_widget)

  def show_sub_window(self):
    print("show_sub_window")
    self.sub_win = QDialog() # if instantiating with (self), subsubwindow ends up created behind main window, but then we cannot move main window to show it!
    self.sub_win.title = QLabel("SubWindow")
    self.sub_win.button = QPushButton("Click for SubSubWindow")
    self.sub_win.button.clicked.connect(self.show_sub_sub_window)
    self.sub_win.layout = QVBoxLayout()
    self.sub_win.layout.addWidget(self.sub_win.title)
    self.sub_win.layout.addWidget(self.sub_win.button)
    self.sub_win.setLayout(self.sub_win.layout)
    self.sub_win.exec_()
    print("after sub_win.show()")

  def show_sub_sub_window(self):
    print("show_sub_sub_window")
    self.sub_sub_win = QWidget()
    self.sub_sub_win.title = QLabel("SubSubWindow")
    self.sub_sub_win.button = QPushButton("(Stop clicking.)")
    self.sub_sub_win.button.setEnabled(False)
    self.sub_sub_win.layout = QVBoxLayout()
    self.sub_sub_win.layout.addWidget(self.sub_sub_win.title)
    self.sub_sub_win.layout.addWidget(self.sub_sub_win.button)
    self.sub_sub_win.setLayout(self.sub_sub_win.layout)
    self.sub_sub_win.show()

app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec()

当我执行上述点击时,我得到如下信息:

pyqt5 gui result

也就是说,单击“单击子窗口”会引发子窗口,它是模态的,因此它阻止与主窗口交互(出于某种原因,我一直认为模态取决于是否在 QDialog 构造函数中设置父级,但是事实并非如此)。然后,单击“Click for SubSubWindow”会引发 SubSubWindow - 但与该窗口的交互also 被禁用(实际上,甚至无法通过单击右上角的 X 来关闭它)?!

那么,如何设置这段代码,使得SubSubWindow可以独立移动和交互呢?

pyqt5 modal-dialog
1个回答
0
投票

事实证明,有情态的事情要复杂得多;我想到目前为止,我只遇到过不需要深入研究的情况。但为了更全面地了解形态,必须查阅:

  • 模式对话框是阻止输入到同一应用程序中其他可见窗口的对话框。
  • 对话框可以是应用程序模式(默认)或窗口模式。
  • 打开应用程序模式对话框时,用户必须完成与该对话框的交互并关闭它,然后才能访问应用程序中的任何其他窗口。窗口模式对话框仅阻止访问与该对话框关联的窗口,允许用户继续使用应用程序中的其他窗口。
  • 显示模式对话框最常见的方法是调用其 exec() 函数。
    • 另一种方法是调用 setModal (true) 或 setWindowModality(),然后调用 show()。与 exec() 不同,show() 立即将控制权返回给调用者。调用 setModal (true) 对于进度对话框特别有用,其中用户必须具有与对话框交互的能力,例如取消长时间运行的操作。如果同时使用 show() 和 setModal(true) 来执行长操作,则必须在处理过程中定期调用 processEvents() 以使用户能够与对话框交互。
  • 无模式对话框是独立于同一应用程序中的其他窗口运行的对话框。非模式对话框使用 show() 显示,它立即将控制权返回给调用者。

...而且:

  • QWidget.setWindowModality(windowModality) - 该属性保存哪些窗口被模式小部件阻挡。该属性仅对 Windows 有意义。模态小部件可防止其他窗口中的小部件获取输入。该属性的值控制当小部件可见时哪些窗口被阻止。当窗口可见时更改此属性没有任何效果;您必须先隐藏()小部件,然后再次显示()它。默认情况下,此属性为 NonModal。
  • Qt.WindowModality - Qt.NonModal:窗口不是模态的,不会阻止其他窗口的输入。 Qt.WindowModal:该窗口是单个窗口层次结构的模态窗口,并阻止对其父窗口、所有祖父母窗口及其父窗口和祖父母窗口的所有兄弟窗口的输入。 Qt.ApplicationModal - 该窗口对于应用程序来说是模态的,并阻止所有窗口的输入。

因此,对于我用 OP 示例想象的应用程序,我发现 WindowModal 模式是最合适的;但是不要忘记“在窗口可见时更改此属性没有任何效果;您必须先隐藏()小部件,然后再次显示()它。”。

因此,使 SubSubWindow 可移动(并且可交互)所需的更改是:

# ...
  def show_sub_window(self):
    print("show_sub_window")
    self.sub_win = QDialog() # if instantiating with (self), subsubwindow ends up created behind main window, but then we cannot move main window to show it!
    self.sub_win.title = QLabel("SubWindow")
    self.sub_win.button = QPushButton("Click for SubSubWindow")
    self.sub_win.button.clicked.connect(self.show_sub_sub_window)
    self.sub_win.layout = QVBoxLayout()
    self.sub_win.layout.addWidget(self.sub_win.title)
    self.sub_win.layout.addWidget(self.sub_win.button)
    self.sub_win.setLayout(self.sub_win.layout)
    self.setWindowModality(Qt.WindowModal) # ADDED; "Changing this property while the window is visible has no effect; you must hide() the widget first, then show() it again."
    self.sub_win.show()                    # ADDED; ... actually, must .show() even when windows is not created yet - just to have it recognize the setWindowModality!
    self.sub_win.exec_()
    print("after sub_win.show()")
# ...
© www.soinside.com 2019 - 2024. All rights reserved.