在之前的教程中,我们已经介绍了如何打开对话框窗口。这些特殊的窗口(默认情况下)抓住用户的焦点,并运行自己的事件循环,有效地阻止了应用程序其余部分的执行。
然而,您经常希望在不中断主窗口的情况下在应用程序中打开第二个窗口——例如,显示一些长时间运行的进程的输出,或者显示图形或其他可视化。或者,您可能希望创建一个应用程序,允许您在各自的窗口中同时处理多个文档。
打开新窗口相对简单,但有几件事要记住,以确保它们正常工作。在本教程中,我们将逐步了解如何创建一个新窗口,以及如何根据需要显示和隐藏外部窗口。
创建一个新窗口
在Qt中,任何没有父组件的小部件都是窗口。这意味着,要显示一个新窗口,您只需要创建一个小部件的新实例。这可以是任何小部件类型(严格来说是QWidget的任何子类),如果您愿意,还可以包括另一个QMainWindow。
QMainWindow实例的数量没有限制。如果你在第二个窗口上需要工具栏或菜单,你将不得不使用QMainWindow来实现这一点。然而,这可能会让用户感到困惑,所以要确保这是必要的。
与主窗口一样,创建一个窗口是不够的,还必须显示它。
from PyQt6.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()
现在,当您单击按钮以显示新窗口时,它将持续存在。
但是,如果再次单击按钮会发生什么?窗口将被重新创建!这个新窗口将取代自我中的旧窗口。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 PyQt6.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 = None测试将不会在下次单击按钮时通过,因此我们将无法重新创建窗口。
只有当您没有在其他地方保留对该窗口的引用时,这才有效。为了确保窗口无论如何都会关闭,您可能希望显式地对其调用.close()。完整示例如下所示。
from PyQt6.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 PyQt6.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 PyQt6.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 PyQt6.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()
但是,您也可以创建一个通用方法来处理所有窗口的切换。下面的示例演示了如何使用lambda函数拦截来自每个按钮的信号并通过适当的窗口。
import sys
from random import randint
from PyQt6.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()
另外说明一下上述lambda表达式,以免有小伙伴看不懂,lambda有两种形式,简单点的如这种
x = lambda a : a + 10
print(x(5))
我相信这种很多小伙伴不懂也能猜出来,在表达式中a作为参数传入
语法规则:lambda 参数 : 表达式,执行表达式并返回结果,当前也支持多个参数,这里不再举例。
另一种形式为:
def myfunc(n):
return lambda a : a * n
mydoubler = myfunc(2)
print(mydoubler(11))
输出结果是11*2。
button1.clicked.connect(
lambda checked: self.toggle_window(self.window1)
)
def toggle_window(self, window):
if window.isVisible():
window.hide()
else:
window.show()
这个代码参数checked并没有使用到