在PySide6中创建多个窗口--为你的应用程序打开新的窗口(为PySide6更新)

1,610 阅读8分钟

在前面的教程中,我们已经介绍了如何打开对话窗口。这些特殊的窗口(默认情况下)会抓住用户的焦点,并运行自己的事件循环,有效地阻止了你的应用程序的其他部分的执行。

然而,很多时候你会想在一个应用程序中打开第二个窗口,而不打断主窗口--例如,显示一些长期运行的进程的输出,或显示图形或其他可视化。另外,你可能想创建一个应用程序,允许你在自己的窗口中同时处理多个文档。

打开新的窗口是比较简单的,但有一些事情需要注意,以确保它们工作良好。 在本教程中,我们将逐步了解如何创建一个新的窗口,以及如何根据需要显示和隐藏外部窗口。

创建一个新的窗口

在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_()