7.PyQt创建额外窗口
在之前的教程中,我们已经介绍了如何打开对话框窗口。这些特殊的窗口(默认情况下)会抓住用户的注意力,并运行它们自己的事件循环,从而有效地阻止应用程序其余部分的执行。
但是,您常常希望在不中断主窗口的情况下在应用程序中打开第二个窗口——例如,显示一些长时间运行的流程的输出,或显示图形或其他可视化。或者,您可能希望创建一个应用程序,该应用程序允许您在多个文档的窗口中同时处理它们。
打开新窗口相对简单,但要确保它们正常工作,需要记住一些事情。在本教程中,我们将逐步介绍如何创建一个新窗口,以及如何根据需要显示和隐藏外部窗口。
创建新窗口
在Qt中,任何没有父组件的小部件都是窗口。这意味着,要显示一个新窗口,您只需创建一个小部件的新实例。这可以是任何小部件类型(技术上是QWidget的任何子类),如果你愿意,可以包括另一个QMainWindow。
您可以拥有的QMainWindow实例的数量没有限制。如果你需要工具栏或菜单在第二个窗口,你将不得不使用一个QMainWindow来实现这一点。但这可能会让用户感到困惑,所以请确保这是必要的。
与主窗口一样,创建窗口是不够的,还必须显示它。
from PyQt5.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()
在这个方法中,我们创建窗口(小部件)对象,将其存储在变量w中并显示出来。然而,一旦我们离开这个方法,我们就不再有对w变量的引用(它是一个局部变量),因此它将被清理——窗口将被销毁。要解决这个问题,我们需要在某个地方保留对窗口的引用,例如在self对象上。
def show_new_window(self, checked):
self.w = AnotherWindow()
self.w.show()
现在,当您单击按钮以显示新窗口时,它将保持不变。
但是,如果再次点击按钮会发生什么呢?窗口将被重新创建!这个新窗口将取代自我中的旧窗口。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 块只在创建窗口时运行。如果您一直点击按钮,数字将改变,显示窗口正在重新创建。
一种解决方案是简单地在创建窗口之前检查窗口是否已经被创建。下面的示例演示了这一点。
窗口,创建时随机生成一个标签。
使用该按钮,您可以弹出窗口,并使用窗口控件关闭它。如果再次单击该按钮,将再次出现相同的窗口。
这种方法适用于临时创建的窗口—例如,如果您想弹出一个窗口来显示特定的图形或日志输出。然而,对于许多应用程序,您有许多标准窗口,您希望能够按需显示/隐藏它们。
在下一部分中,我们将看看如何使用这些类型的窗口。
切换窗口
通常,您会希望使用工具栏或菜单上的操作切换窗口的显示。正如我们前面看到的,如果没有保留对窗口的引用,那么它将被丢弃(并关闭)。我们可以使用这个行为来关闭一个窗口,用-替换上一个例子中的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 to None对该窗口的引用将丢失,窗口将关闭。
如果我们将它设置为任何其他值None,窗口将仍然关闭,但if self.w is None测试将不会通过下次我们点击按钮,所以我们将不能重新创建一个窗口。
这只会在您没有在其他地方保留对该窗口的引用时起作用。为了确保窗口无论如何都会关闭,您可能需要对其显式调用.close()。完整的示例如下所示。
from PyQt5.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()
Persistent windows
到目前为止,我们已经了解了如何根据需要创建新窗口。然而,有时您有许多标准的应用程序窗口。在本例中,与其在需要时创建窗口,还不如在启动时创建窗口,然后在需要时使用.show()来显示它们。
在下面的例子中,我们在主窗口的 init 块中创建了外部窗口,然后我们的show_new_window方法简单地调用self.w.show()来显示它。
from PyQt5.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 PyQt5.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 PyQt5.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函数截取每个按钮的信号并通过适当的窗口。我们也可以丢弃检查的值,因为我们不使用它。
import sys
from random import randint
from PyQt5.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()