5.1 主要内容
前面介绍Qt程序的基本结构时,说过主窗口不是必须的但又不能没有,听起来有点自相矛盾。其实,想要理解也不难,那就要说说本章要介绍的主窗口控件。
前面介绍Qt程序的基本结构时,说主窗口算控件的一种,其实指的是用于产生主窗口的三种主窗口控件,分别是QWidget控件、QDialog控件、QMainWindow控件,它们与其他控件的区别是,对于一个窗口来说,只能创建一个。但它们又和其他控件的区别没那么大,因为从根上说,除了QWidget控件本身就是QWidget类,其他控件的基类都是QWidget类,所以QWidget控件具备的部分功能,所有控件都有。
因此,这么来看的话,主窗口的自相矛盾特性就很好解释了。
只要创建控件,都有主窗口控件之一——QWidget控件的功能,相当于无论如何都有主窗口(控件),所以主窗口(控件)是始终存在的。而其他控件从另一方面论证的话,又不算主窗口控件,所以主窗口(控件)又不是必须的。
当然,真要是较真的话,一个Qt程序不创建任何控件(真正意义上的没有主窗口)也可以运行,但是因为没有主窗口,所以不显示主窗口,没法正常点击结束,只能通过任务管理器(Windows系统,Linux系统通过命令)强制结束,这种状态的Qt程序是不能正常使用的。
除了主窗口控件与其他控件有所区别,三种主窗口控件之间也有区别:
-
QWidget控件(完整用法可参考 https://doc.qt.io/qtforpython-6/PySide6/QtWidgets/QWidget.html#PySide6.QtWidgets.QWidget ),QWidget类是所有控件的基类,可以说其他控件都是基于QWidget控件实现的。因此,该控件主要用于创建简单的窗口或者通用控件。如果需要给窗口增加工具栏、菜单栏、状态栏,则需要手动添加(默认QWidget控件不包括)。此外,想要让窗口变为模态窗口(只允许当前窗口获得焦点,符合要求的其他窗口不能获得焦点,除非关闭当前窗口)的话,只能使用setWindowModality方法(仅支持应用级模态Qt.WindowModality.ApplicationModal)手动设置窗口的模态:
from PySide6.QtWidgets import (
QApplication,
QWidget,
)
from PySide6.QtCore import Qt
app = QApplication()
# 窗口1正常显示
window = QWidget()
window.setWindowTitle('窗口1')
window.resize(400,300)
window.show()
# 窗口2模态显示
window2 = QWidget()
window2.resize(300,200)
window2.setWindowTitle('窗口2')
window2.setWindowModality(Qt.WindowModality.ApplicationModal)
window2.show()
app.exec()
-
QDialog控件(完整用法可参考 https://doc.qt.io/qtforpython-6/PySide6/QtWidgets/QDialog.html#PySide6.QtWidgets.QDialog ),该控件的基类是QWidget类,生成的窗口只有关闭按钮,没有最大化、最小化按钮,一般用于创建简单的对话框,很多对话框控件也是通过继承QDialog类实现的。当然,对话框一般不需要工具栏、菜单栏、状态栏,自然也不包括。不同于QWidget控件只能手动设置窗口的模态,该控件还支持通过exec方法显示窗口(同时进入无限循环,阻止后续代码的运行),此时的窗口为模态窗口(其模态为窗口级模态Qt.WindowModality.WindowModal):
from PySide6.QtWidgets import (
QApplication,
QDialog,
)
app = QApplication()
# 窗口1正常显示
window = QDialog()
window.setWindowTitle('窗口1')
window.resize(400,300)
window.show()
# 窗口2模态显示
window2=QDialog(window)
window2.setWindowTitle('窗口2')
window2.resize(300,200)
window2.exec()
# 不关闭窗口2的话,窗口3不显示
window3=QDialog(window)
window3.setWindowTitle('窗口3')
window3.resize(300,200)
window3.show()
app.exec()
如上面的代码所示,QDialog控件与QWidget控件不同,可以在创建时设置父控件,组成父子关系,让父子窗口同时显示(QWidget控件不支持这样操作)。关于应用级模态与窗口级模态的区别,以及不同父子关系对模态影响,可以参考本节的扩展内容,这里受限于篇幅不做展开。
-
QMainWindow控件(完整用法可参考 https://doc.qt.io/qtforpython-6/PySide6/QtWidgets/QMainWindow.html#PySide6.QtWidgets.QMainWindow ),该控件的基类是QWidget类,生成的窗口功能丰富,包含工具栏、菜单栏、状态栏(需要手动添加内容),一般用作程序的主窗口(适用于不想额外创建工具栏、菜单栏、状态栏的情况)。虽然该控件也支持setWindowModality方法,但不建议设置为模态窗口。以下为在状态栏中添加控件的示例:
from PySide6.QtWidgets import (
QApplication,
QMainWindow,
QPushButton
)
app = QApplication()
window = QMainWindow()
window.resize(400,300)
window.setWindowTitle('MainWindow')
window.statusBar().addWidget(QPushButton('Hello'))
window.show()
app.exec()
三种主窗口控件的直观对比可以参考下面的表格:
|
QWidget |
QDialog |
QMainWindow |
| 继承关系 |
所有控件的基类 |
继承自QWidget |
继承自QWidget |
| 用途 |
简单窗口、通用控件 |
对话框 |
功能丰富的主窗口 |
| 工具栏、菜单栏、状态栏 |
需要手动实现 |
一般不添加 |
内置 |
| 模态支持 |
需要手动实现(通过setWindowModality方法) |
内置(通过exec方法) |
不推荐使用模态 |
| 中心区域 |
无 |
无 |
有(通过setCentralWidget方法) |
| 衍生控件 |
QPushButton等基础控件 |
QFileDialog、QMessageBox等对话框控件 |
无 |
5.2 扩展内容
5.2.1 控件与主窗口
在运行主窗口的show方法之前创建控件,需要指定控件的父控件为主窗口,这样创建出来控件才会显示在主窗口中。但是,在show方法之后创建的控件,则需要额外调用控件的show方法才能显示。
没有指定父控件为主窗口的控件都不属于主窗口,这样的控件显示(调用控件的show方法)时会额外创建一个窗口,并显示在新窗口中。
示例如下:
from PySide6.QtWidgets import (
QApplication,
QWidget,
QLabel
)
app = QApplication()
window = QWidget()
window.resize(400,300)
window.setWindowTitle('主窗口')
window.show()
# 在主窗口的show方法之后创建控件,需要调用控件的show方法才能显示
# 标签1的父控件为window,所以显示在主窗口中
label1 = QLabel('标签1',window)
label1.show()
# 标签2没有父控件,所以会自动创建新窗口
label2 = QLabel('标签2')
label2.resize(400,300)
label2.setWindowTitle('新窗口')
label2.show()
app.exec()
5.2.2 应用级模态与窗口级模态的区别
既然应用级模态与窗口级模态都能做到只允许当前窗口获得焦点,那为什么还要设计为两种模态,而不是合并为一种?存在即合理,既然有两种模态,肯定在用法上有所不同。接下来,就通过使用setWindowModality方法设置窗口的模态,看一下二者的区别。
先说应用级模态。无论其余窗口是使用QDialog控件创建,还是使用QWidget控件创建(只能创建为主窗口的兄弟窗口),也无论其余窗口的父子关系有多复杂,只要不是模态窗口及其子窗口,在关闭(或者隐藏)模态窗口之前,都不能获得焦点。
需要注意的是,对于主窗口以及其他与主窗口同级的兄弟窗口,如果全部关闭的话,程序会直接结束,哪怕它们的子窗口还存在或者处于显示状态。
示例如下:
from PySide6.QtWidgets import (
QApplication,
QDialog,
QWidget,
QPushButton
)
from PySide6.QtCore import Qt
app = QApplication()
# 窗口1正常显示
window = QWidget()
window.setWindowTitle('窗口1')
window.resize(400,300)
window.move(100,100)
window.show()
# 窗口2(窗口1的子窗口)模态显示
window2=QDialog(window)
window2.setWindowTitle('窗口2')
window2.resize(400,300)
window2.move(200,200)
# 设置为应用级模态
window2.setWindowModality(Qt.WindowModality.ApplicationModal)
# 隐藏窗口2
QPushButton('hide me',window2).clicked.connect(lambda:window2.hide())
window2.show()
# 窗口3(窗口1的子窗口,窗口2的兄弟窗口)正常显示
window3=QDialog(window)
window3.setWindowTitle('窗口3')
window3.resize(400,300)
window3.move(300,300)
window3.show()
# 窗口4(窗口2的子窗口)正常显示
window4=QDialog(window2)
window4.setWindowTitle('窗口4')
window4.resize(400,300)
window4.move(400,400)
window4.show()
# 窗口5(窗口1的兄弟窗口)正常显示
window5=QWidget()
window5.setWindowTitle('窗口5')
window5.resize(400,300)
window5.move(500,500)
window5.show()
app.exec()
再说窗口级模态。无论其余窗口是使用QDialog控件创建,还是使用QWidget控件创建(只能创建为主窗口的兄弟窗口),也无论其余窗口的父子关系有多复杂,只要与模态窗口的任一父窗口有父子关系,并且不是模态窗口及其子窗口,在关闭(或者隐藏)模态窗口之前,都不能获得焦点。
示例如下:
from PySide6.QtWidgets import (
QApplication,
QDialog,
QWidget,
QPushButton
)
from PySide6.QtCore import Qt
app = QApplication()
# 窗口1正常显示
window = QWidget()
window.setWindowTitle('窗口1')
window.resize(400,300)
window.move(100,100)
window.show()
# 窗口2(窗口1的子窗口)模态显示
window2=QDialog(window)
window2.setWindowTitle('窗口2')
window2.resize(400,300)
window2.move(200,200)
# 设置为窗口级模态
window2.setWindowModality(Qt.WindowModality.WindowModal)
# 隐藏窗口2
QPushButton('hide me',window2).clicked.connect(lambda:window2.hide())
window2.show()
# 窗口3(窗口1的子窗口,窗口2的兄弟窗口)正常显示
window3=QDialog(window)
window3.setWindowTitle('窗口3')
window3.resize(400,300)
window3.move(300,300)
window3.show()
# 窗口4(窗口2的子窗口)正常显示
window4=QDialog(window2)
window4.setWindowTitle('窗口4')
window4.resize(400,300)
window4.move(400,400)
window4.show()
# 窗口5(窗口1的兄弟窗口)正常显示
window5=QWidget()
window5.setWindowTitle('窗口5')
window5.resize(400,300)
window5.move(500,500)
window5.show()
app.exec()
可能看完代码和描述还是有点不太清楚,没关系,两个示例使用了相同的窗口父子关系,只是模态不同,接下来看看窗口的父子关系图:
当窗口2的模态为应用级模态时,除了窗口4是窗口2的子窗口,不受任何模态的影响,窗口1、窗口3、窗口5都与窗口2同属于一个程序类实例(应用程序),所以,在关闭(或者隐藏)窗口2之前,不能获得焦点。
当窗口2的模态为窗口级模态时,除了窗口4是窗口2的子窗口,不受任何模态的影响之外,窗口5与窗口2没有相同的父窗口(无限向上追溯,与窗口本身或者父窗口存在父子关系就算),也不受影响。窗口1、窗口3都与窗口2有相同的父窗口(无限向上追溯,与窗口本身或者父窗口存在父子关系就算),所以,在关闭(或者隐藏)窗口2之前,不能获得焦点。
5.2.3 高亮主窗口或者其兄弟窗口
上节提到主窗口也可以有兄弟窗口,这里顺便再说一个与之相关的功能,那就是QApplication类的alert方法(完整用法可参考 https://doc.qt.io/qtforpython-6/PySide6/QtWidgets/QApplication.html#PySide6.QtWidgets.QApplication.alert )。该方法可以高亮并闪烁当前没有获得焦点的主窗口或者其兄弟窗口,只有当地获得焦点时或者到一定时间后才会停止高亮和闪烁。
具体参数可以参考上面的文档链接,以下为示例:
from PySide6.QtWidgets import (
QApplication,
QDialog,
QWidget,
QPushButton
)
app = QApplication()
# 窗口1正常显示
window = QWidget()
window.setWindowTitle('窗口1')
window.resize(400,300)
window.move(100,100)
window.show()
# 窗口2(窗口1的兄弟窗口)正常显示
window2=QDialog()
window2.setWindowTitle('窗口2')
window2.resize(400,300)
window2.move(200,200)
QPushButton('高亮主窗口',window2).clicked.connect(lambda:app.alert(window,3000))
window2.show()
# 窗口3(窗口1的兄弟窗口)正常显示
window3=QDialog()
window3.setWindowTitle('窗口3')
window3.resize(400,300)
window3.move(300,300)
QPushButton('高亮主窗口的兄弟窗口',window3).clicked.connect(lambda:app.alert(window2,3000))
window3.show()
app.exec()