在上一章中,我们了解了如何打开对话窗口。 这些是特殊的窗口(默认情况下)抓住用户的焦点,并运行自己的事件循环,有效地阻止了应用程序其余部分的执行。
但是,您经常希望在不阻塞主窗口的情况下在应用程序中打开第二个窗口——例如,显示一些长时间运行的进程的输出,或者显示图形或其他可视化。 或者,您可能想要创建一个应用程序,允许您一次处理多个文档,所有文档都在它们自己的窗口中。
在 PySide6 中打开新窗口相对简单,但要记住一些事项以确保它们正常工作。 在本教程中,我们将逐步介绍如何创建新窗口以及如何按需显示和隐藏外部窗口。
创建一个新窗口要在 PySide6 中创建一个新窗口,您只需要创建一个没有父级的小部件对象的新实例。 如果您愿意,这可以是任何小部件(技术上是 QWidget 的任何子类),包括另一个 QMainWindow。
您可以拥有的 QMainWindow 实例的数量没有限制,如果您需要在第二个窗口上使用工具栏或菜单,您也需要使用 QMainWindow。
与您的主窗口一样,创建一个窗口是不够的,您还必须显示它。
# windows_1.pyimport sysfrom PySide6.QtWidgets import (QApplication, QLabel, QMainWindow, QPushButton, QVBoxLayout, QWidget)class AnotherWindow(QWidget): """ This "window" is a QWidget. If it has no parent, it will appear as a free-floating window. """ def __init__(self): super().__init__() layout = QVBoxLayout() self.label = QLabel("Another Window") layout.addWidget(self.label) self.setLayout(layout)class MainWindow(QMainWindow): def __init__(self): super().__init__() self.button = QPushButton("Push for Window") self.button.clicked.connect(self.show_new_window) self.setCentralWidget(self.button) def show_new_window(self, checked): w = AnotherWindow() w.show()app = QApplication(sys.argv)w = MainWindow()w.show()app.exec_()
如果你运行这个,你会看到主窗口。 单击该按钮可能会显示第二个窗口,但如果您看到它,它只会在几分之一秒内可见。发生了什么?
def show_new_window(self, checked): w = AnotherWindow() w.show()
我们正在这个方法中创建我们的第二个窗口,将它存储在变量 w 中并显示它。 然而,一旦我们离开这个方法 w 变量将被 Python 清理,并且窗口被破坏。 为了解决这个问题,我们需要在某处保留对窗口的引用——例如,在主窗口 self 对象上。
import sysfrom PySide6.QtWidgets import ( QApplication, QLabel, QMainWindow, QPushButton, QVBoxLayout, QWidget,)class AnotherWindow(QWidget): """ This "window" is a QWidget. If it has no parent, it will appear as a free-floating window. """ def __init__(self): super().__init__() layout = QVBoxLayout() self.label = QLabel("Another Window") layout.addWidget(self.label) self.setLayout(layout)class MainWindow(QMainWindow): def __init__(self): super().__init__() self.button = QPushButton("Push for Window") self.button.clicked.connect(self.show_new_window) self.setCentralWidget(self.button) # tag::show_new_window[] def show_new_window(self, checked): self.w = AnotherWindow() self.w.show() # end::show_new_window[]app = QApplication(sys.argv)w = MainWindow()w.show()app.exec_()
现在,当您单击按钮以显示新窗口时,它将持续存在。
但是,如果再次单击该按钮会发生什么? 将重新创建窗口! 这个新窗口将替换 self.w 变量中的旧窗口,并且前一个窗口将被销毁。 如果您更改 AnotherWindow 定义以在每次创建标签时在标签中显示随机数,您可以更清楚地看到这一点。
# windows_2.pyimport sys# tag::imports[]from random import randintfrom PySide6.QtWidgets import ( QApplication, QLabel, QMainWindow, QPushButton, QVBoxLayout, QWidget,)# end::imports[]# tag::AnotherWindow[]class AnotherWindow(QWidget): """ This "window" is a QWidget. If it has no parent, it will appear as a free-floating window. """ def __init__(self): super().__init__() layout = QVBoxLayout() self.label = QLabel("Another Window % d" % randint(0, 100)) layout.addWidget(self.label) self.setLayout(layout)# end::AnotherWindow[]class MainWindow(QMainWindow): def __init__(self): super().__init__() self.button = QPushButton("Push for Window") self.button.clicked.connect(self.show_new_window) self.setCentralWidget(self.button) def show_new_window(self, checked): self.w = AnotherWindow() self.w.show()app = QApplication(sys.argv)w = MainWindow()w.show()app.exec_()
__init__ 块仅在创建窗口时运行。 如果您继续单击该按钮,数字将发生变化,表明正在重新创建窗口。
一种解决方案是在创建窗口之前简单地检查窗口是否已经被创建。 下面的完整示例显示了这一点。
# windows_3.pyimport sysfrom random import randintfrom PySide6.QtWidgets import ( QApplication, QLabel, QMainWindow, QPushButton, QVBoxLayout, QWidget,)class AnotherWindow(QWidget): """ This "window" is a QWidget. If it has no parent, it will appear as a free-floating window. """ def __init__(self): super().__init__() layout = QVBoxLayout() self.label = QLabel("Another Window % d" % randint(0, 100)) layout.addWidget(self.label) self.setLayout(layout)# tag::MainWindow[]class MainWindow(QMainWindow): def __init__(self): super().__init__() self.w = None # No external window yet. self.button = QPushButton("Push for Window") self.button.clicked.connect(self.show_new_window) self.setCentralWidget(self.button) def show_new_window(self, checked): if self.w is None: self.w = AnotherWindow() self.w.show()# end::MainWindow[]app = QApplication(sys.argv)w = MainWindow()w.show()app.exec_()
这种方法适用于您临时创建的窗口,或者需要根据程序的当前状态进行更改的窗口 - 例如,如果您想显示特定的绘图或日志输出。 但是,对于许多应用程序,您希望能够按需显示/隐藏许多标准窗口。
在下一部分中,我们将研究如何使用这些类型的窗口。
关闭窗口正如我们之前看到的,如果没有保留对窗口的引用,它将被丢弃(并关闭)。 我们可以使用这种行为来关闭一个窗口,将上一个示例中的 show_new_window 方法替换如下。
# windows_4.py def show_new_window(self, checked): if self.w is None: self.w = AnotherWindow() self.w.show() else: self.w = None # Discard reference, close window.
通过将 self.w 设置为 None (或任何其他值),对窗口的引用将丢失,窗口将关闭。 但是,如果我们将其设置为 None 以外的任何其他值,则第一个测试将无法通过,并且我们将无法重新创建窗口。
这仅在您没有在其他地方保留对此窗口的引用时才有效。 为了确保窗口关闭,您可能需要对其显式调用 .close() 。
# windows_4b.py def show_new_window(self, checked): if self.w is None: self.w = AnotherWindow() self.w.show() else: self.w.close() self.w = None # Discard reference, close window.
持久化窗口
到目前为止,我们已经了解了如何按需创建新窗口。 但是,有时您有许多标准的应用程序窗口。 在这种情况下,首先创建附加窗口通常更有意义,然后在需要时使用 .show() 显示它们。
在下面的示例中,我们在主窗口的 __init__ 块中创建外部窗口,然后我们的 show_new_window 方法简单地调用 self.w.show() 来显示它。
# windows_5.pyimport sysfrom random import randintfrom PySide6.QtWidgets import ( QApplication, QLabel, QMainWindow, QPushButton, QVBoxLayout, QWidget,)class AnotherWindow(QWidget): """ This "window" is a QWidget. If it has no parent, it will appear as a free-floating window. """ def __init__(self): super().__init__() layout = QVBoxLayout() self.label = QLabel("Another Window % d" % randint(0, 100)) layout.addWidget(self.label) self.setLayout(layout)class MainWindow(QMainWindow): def __init__(self): super().__init__() self.w = AnotherWindow() 电脑 self.button = QPushButton("Push for Window") self.button.clicked.connect(self.show_new_window) self.setCentralWidget(self.button) def show_new_window(self, checked): self.w.show()app = QApplication(sys.argv)w = MainWindow()w.show()app.exec_()
如果你运行它,点击按钮将像以前一样显示窗口。 请注意,该窗口仅创建一次,并且在已经可见的窗口上调用 .show() 无效。
显示和隐藏窗口创建持久性窗口后,您可以显示和隐藏它,而无需重新创建它。 一旦隐藏窗口仍然存在,但将不可见并接受鼠标或其他输入。 但是你可以继续调用窗口上的方法并更新它的状态——包括改变它的外观。 重新显示后,任何更改都将可见。
下面我们更新我们的主窗口以创建一个 toggle_window 方法,该方法使用 .isVisible() 来检查窗口当前是否可见。 如果不是,则使用 .show() 显示,如果它已经可见,则使用 .hide() 隐藏它。
class MainWindow(QMainWindow): def __init__(self): super().__init__() self.w = AnotherWindow() self.button = QPushButton("Push for Window") self.button.clicked.connect(self.toggle_window) 电脑 self.setCentralWidget(self.button) def toggle_window(self, checked): if self.w.isVisible(): self.w.hide() else: self.w.show()
这个持久窗口和切换显示/隐藏状态的完整工作示例如下所示。
# windows_6b.pyimport sysfrom random import randintfrom PySide6.QtWidgets import ( QApplication, QLabel, QMainWindow, QPushButton, QVBoxLayout, QWidget,)class AnotherWindow(QWidget): """ This "window" is a QWidget. If it has no parent, it will appear as a free-floating window. """ def __init__(self): super().__init__() layout = QVBoxLayout() self.label = QLabel("Another Window % d" % randint(0, 100)) layout.addWidget(self.label) self.setLayout(layout)class MainWindow(QMainWindow): def __init__(self): super().__init__() self.window1 = AnotherWindow() self.window2 = AnotherWindow() l = QVBoxLayout() button1 = QPushButton("Push for Window 1") button1.clicked.connect(self.toggle_window1) l.addWidget(button1) button2 = QPushButton("Push for Window 2") button2.clicked.connect(self.toggle_window2) l.addWidget(button2) w = QWidget() w.setLayout(l) self.setCentralWidget(w) def toggle_window1(self, checked): if self.window1.isVisible(): self.window1.hide() else: self.window1.show() def toggle_window2(self, checked): if self.window2.isVisible(): self.window2.hide() else: self.window2.show()app = QApplication(sys.argv)w = MainWindow()w.show()app.exec_()
同样,窗口只创建一次——每次重新显示窗口时都不会重新运行窗口的 __init__ 块(因此标签中的数字不会改变)。
连接窗口之间的信号在信号一章中,我们看到了如何使用信号和槽直接将小部件连接在一起。 我们所需要的只是创建目标小部件并通过变量对其进行引用。 当跨窗口连接信号时,同样的原则适用——您可以将一个窗口中的信号连接到另一个窗口中的插槽,您只需要能够访问该插槽。
在下面的示例中,我们将主窗口上的文本输入连接到子窗口上的 QLabel。
# windows_7.pyimport sysfrom random import randintfrom PySide6.QtWidgets import ( QApplication, QLabel, QMainWindow, QPushButton, QVBoxLayout, QWidget, QLineEdit)class AnotherWindow(QWidget): """ This "window" is a QWidget. If it has no parent, it will appear as a free-floating window. """ def __init__(self): super().__init__() layout = QVBoxLayout() self.label = QLabel("Another Window") # <2> layout.addWidget(self.label) self.setLayout(layout)class MainWindow(QMainWindow): def __init__(self): super().__init__() self.w = AnotherWindow() self.button = QPushButton("Push for Window") self.button.clicked.connect(self.toggle_window) self.input = QLineEdit() self.input.textChanged.connect(self.w.label.setText) # <1> layout = QVBoxLayout() layout.addWidget(self.button) layout.addWidget(self.input) container = QWidget() container.setLayout(layout) self.setCentralWidget(container) def toggle_window(self, checked): if self.w.isVisible(): self.w.hide() else: self.w.show()app = QApplication(sys.argv)w = MainWindow()w.show()app.exec_()
运行! 在上方的框中输入一些文本,您会看到它立即出现在标签上。 看看如果在窗口隐藏时在框中键入文本会发生什么 - 它仍然在后台更新! 更新小部件的状态不依赖于它们是否可见。
当然,您也可以自由地将一个窗口上的信号连接到另一个窗口上的自定义方法。 任何事情都可以,只要它是可访问的(并且对于 Qt 插槽类型匹配),您就可以将其与信号连接。
确保组件可导入且彼此可访问是构建逻辑项目结构的一个很好的动机。 在主窗口/模块中集中连接组件通常是有意义的,以避免交叉导入所有内容。
电脑