在前面的教程中,我们已经介绍了如何打开对话窗口。这些特殊的窗口(默认情况下)会抓住用户的焦点,并运行自己的事件循环,有效地阻止了你的应用程序的其他部分的执行。
然而,很多时候你会想在一个应用程序中打开第二个窗口,而不打断主窗口--例如,显示一些长期运行的进程的输出,或显示图形或其他可视化。另外,你可能想创建一个应用程序,允许你在自己的窗口中同时处理多个文档。
打开新的窗口是比较简单的,但有一些事情需要注意,以确保它们工作良好。 在本教程中,我们将逐步了解如何创建一个新的窗口,以及如何根据需要显示和隐藏外部窗口。
创建一个新的窗口
在Qt中,任何没有父对象的部件都是一个窗口。这意味着,要显示一个新的窗口,你只需要创建一个新的窗口部件的实例。这可以是任何widget类型(技术上是QWidget
的任何子类),包括另一个QMainWindow
,如果你愿意的话。
对你可以拥有的QMainWindow
实例的数量没有限制。如果你在第二个窗口上需要工具栏或菜单,你将不得不使用QMainWindow
来实现这一点。然而这可能会让用户感到困惑,所以要确保它是必要的。
和你的主窗口一样,创建一个窗口是不够的,你还必须显示它。
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel, QVBoxLayout, QWidget
import sys
class AnotherWindow(QWidget):
"""
This "window" is a QWidget. If it has no parent, it
will appear as a free-floating window as we want.
"""
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()
在这个方法中,我们正在创建我们的窗口(widget)对象,将其存储在变量w
,并显示它。然而,一旦我们离开这个方法,我们就不再有对w
变量的引用(它是一个局部变量),因此它将被清理掉--窗口也被销毁。为了解决这个问题,我们需要在某个地方保留对该窗口的引用,例如在self
对象上。
def show_new_window(self, checked):
self.w = AnotherWindow()
self.w.show()
现在,当你点击按钮显示新窗口时,它将持续存在。
然而,如果你再次点击这个按钮会发生什么呢?窗口将被重新创建!这个新窗口将取代self.w
变量中的旧窗口,并且--因为现在没有对它的引用--以前的窗口将被销毁。
如果你改变窗口的定义,使其在每次创建时在标签中显示一个随机数字,你就可以看到这个动作。
from random import randint
class AnotherWindow(QWidget):
"""
This "window" is a QWidget. If it has no parent, it
will appear as a free-floating window as we want.
"""
def __init__(self):
super().__init__()
layout = QVBoxLayout()
self.label = QLabel("Another Window % d" % randint(0,100))
layout.addWidget(self.label)
self.setLayout(layout)
__init__
块只在创建窗口时运行。如果你不断地点击按钮,数字就会改变,显示窗口正在被重新创建。
一个解决方案是在创建窗口之前简单地检查该窗口是否已经被创建。下面的例子显示了这个方法的作用。
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel, QVBoxLayout, QWidget
import sys
from random import randint
class AnotherWindow(QWidget):
"""
This "window" is a QWidget. If it has no parent, it
will appear as a free-floating window as we want.
"""
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 = 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()
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
使用这个按钮,你可以弹出窗口,并使用窗口控制来关闭它。如果你再次点击按钮,同一个窗口会重新出现。
这种方法对于你临时创建的窗口来说是很好的--例如,如果你想弹出一个窗口来显示一个特定的图,或者日志输出。然而,对于许多应用程序来说,你有许多标准窗口,你希望能够按需显示/隐藏它们。
在下一部分,我们将看看如何处理这些类型的窗口。
切换一个窗口
通常你想通过工具栏或菜单上的操作来切换一个窗口的显示。正如我们之前看到的,如果不保留对一个窗口的引用,它将被丢弃(和关闭)。show_new_window
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
,窗口仍然会关闭,但在下次点击按钮时,if self.w is None
测试不会通过,因此我们将无法重新创建一个窗口。
这只有在你没有在其他地方保留对这个窗口的引用时才会起作用。为了确保该窗口无论如何都会关闭,你可能想明确地对它调用.close()
。完整的例子显示在下面。
python
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel, QVBoxLayout, QWidget
import sys
from random import randint
class AnotherWindow(QWidget):
"""
This "window" is a QWidget. If it has no parent, it
will appear as a free-floating window as we want.
"""
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 = 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()
else:
self.w.close() # Close window.
self.w = None # Discard reference.
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
持久性窗口
到目前为止,我们已经研究了如何按需创建新窗口。然而,有时你有许多标准的应用程序窗口。在这种情况下,与其在你想显示这些窗口时创建它们,不如在启动时创建它们,然后在需要时使用.show()
来显示它们,这样做往往更有意义。
在下面的例子中,我们在__init__
块中为主窗口创建了我们的外部窗口,然后我们的show_new_window
方法简单地调用self.w.show()
来显示它。
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel, QVBoxLayout, QWidget
import sys
from random import randint
class AnotherWindow(QWidget):
"""
This "window" is a QWidget. If it has no parent, it
will appear as a free-floating window as we want.
"""
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()
下面是这个持久化窗口和切换显示/隐藏状态的完整工作例子。
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel, QVBoxLayout, QWidget
import sys
from random import randint
class AnotherWindow(QWidget):
"""
This "window" is a QWidget. If it has no parent, it
will appear as a free-floating window as we want.
"""
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.toggle_window)
self.setCentralWidget(self.button)
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_()
请注意,同样,这个窗口只被创建一次--每次重新显示这个窗口时,窗口的__init__
块不会被重新运行(所以标签中的数字不会改变)。
多个窗口
你可以使用同样的原则来创建多个窗口--只要你保持对窗口的引用,事情就会按预期进行。最简单的方法是创建一个单独的方法来切换每个窗口的显示。
import sys
from random import randint
from 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_()
然而,你也可以创建一个通用的方法来处理所有窗口的切换--参见用Qt信号传输额外的数据,以获得关于如何工作的详细解释。下面的例子显示了它的作用,使用一个lambda
函数来拦截每个按钮的信号,并传递给相应的窗口。我们也可以丢弃checked
,因为我们并没有使用它。
import sys
from random import randint
from 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(
lambda checked: self.toggle_window(self.window1)
)
l.addWidget(button1)
button2 = QPushButton("Push for Window 2")
button2.clicked.connect(
lambda checked: self.toggle_window(self.window2)
)
l.addWidget(button2)
w = QWidget()
w.setLayout(l)
self.setCentralWidget(w)
def toggle_window(self, window):
if window.isVisible():
window.hide()
else:
window.show()
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()