使用 Python3 和 PyQt6进行学习,参考
下文中的 QT 均指 PyQT6
创建一个 QT 应用
示例
from PyQt6.QtWidgets import QApplication, QWidget
# Only needed for access to command line arguments
import sys
# You need one (and only one) QApplication instance per application.
# Pass in sys.argv to allow command line arguments for your app.
# If you know you won't use command line arguments QApplication([]) works too.
app = QApplication(sys.argv)
# Create a Qt widget, which will be our window.
window = QWidget()
window.show() # IMPORTANT!!!!! Windows are hidden by default.
# Start the event loop.
app.exec()
# Your application won't reach here until you exit and the event
# loop has stopped.
运行上述代码后会得到一个空的窗口
QT 的主要模组为 QtWidgets QtGui 和 QtCore。第一行进行相应的导入操作。接着创建一个 QApplication 实例,其中 sys.argv 用于命令行控制,如果不使用命令行,则可以写为
app = QApplication([])
接着创建一个 Qwidget 实例,变量名为window
window = QWidget()
window.show()
QT的顶级组件为 windows,(window将在 QMainWindow 小节中介绍)他们没有父组件,也就意味着不能将窗口相互嵌套。window 里有所有的用户界面,每个程序至少有一个 window,当所有 window 退出后,程序将停止。
当组件没有父组件时默认不可见,因此需要使用 .show() 来显示,移除 .show() 后程序可以运行,但没有显示 GUI 也没有办法通过关闭按钮结束程序。
事件循环 event loop
每一个程序有且只有一个 QApplication object,它管理着 event loop —— 管理着用户与 GUI 的所有交互。
也就是说:每一个程序有且只有一个 event loop。与 GUI 的每一个交互,例如 点击鼠标、按下按键等都会在 event queue 中生成一个 event,该队列一直在循环,直到发现指定的 event。
当该 event 被发现后,指定的 event handler 负责处理,处理完后将控制权交回 event loop,等待更多的 event(用户与 GUI 的交互)
QMainWindow
Qt中有三个创建 GUI 程序的方法,QMainWindow 预构建了一些基础功能,如菜单栏、状态栏等,详见 Stack overflow 中的讨论
每一个 QT 组件都能成为一个 window ,例如修改上述程序,使用 QPushButton 代替 QtWidget
from PyQt6.QtWidgets import QApplication, QPushButton
import sys
app = QApplication(sys.argv)
window = QPushButton("Push Me")
window.show()
app.exec()
运行后得到了一个只有一个按钮的窗口,使用 layouts 可以将组件嵌套,那么使用 QWidget (参考上一个程序)在其中创建复杂的UI。
与其自行创建,QT 官方给出的解决方法是 QMainWindow,它包含了许多标准窗口的功能,例如:点击、菜单、状态栏等等。现在添加一个空的 QMainWindow!
from PyQt6.QtWidgets import QApplication, QMainWindow
import sys
app = QApplication(sys.argv)
window = QMainWindow()
window.show()
# Start the event loop.
app.exec()
运行后虽然也是一个空白的窗口,但是与之不同的是使用了 QMainWindow,可以向其中添加子类,包括 __init__ 区块,这允许 window 管理自己的行为,使用子类名 MainWindow 添加一个新的子类
import sys
from PyQt6.QtCore import QSize, Qt
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton
# Subclass QMainWindow to customize your application's main window
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
button = QPushButton("Press Me!")
# Set the central widget of the Window.
self.setCentralWidget(button)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
使用 .setCentralWidget 来将组件(本例为按钮)放在 QMainWindow 中,其默认占据了整个窗口。
在 __init__ 区块中,使用 .setWindowTitle() 设置了窗口标题,添加了一个 QPushButton 组件, .setCentralWidget() 可以使组件居中,它是 QMainWindow 中的一个函数。
改变窗口和组件的大小
现在的窗口可以自由缩放,当使用固定大小的窗口时,使用 QSize object。单位为像素
import sys
from PyQt6.QtCore import QSize, Qt
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton
# Subclass QMainWindow to customize your application's main window
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
button = QPushButton("Press Me!")
self.setFixedSize(QSize(400, 300))
# Set the central widget of the Window.
self.setCentralWidget(button)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
此时,得到了一个 400*500 像素的窗口,该窗口不可缩放。
除了使用 .setFixedSize() 还可以使用 .setMinimumSize() 和 .setMaximumSize() 来设置窗口的最小和最大尺寸。
PyQt6 Signals, Slots & Events
Signals & Slots
singles 是由 widgets 在当某个动作发生时,发出的通知。例如鼠标点击、输入字符等。大部分 signals 由 用户行为生成。除了发送通知,signals 还可以发送一些数据。
slots 负责接收 signals ,在 python 中其它函数或方法都可以作为 slot。当 signal 发送数据后,接收函数就会收到相应的数据。许多 Qt 组件都有一些内置的 slots,可以直接使用。
QPushButton Signals
import sys
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
button = QPushButton("Press Me!")
button.setCheckable(True)
button.clicked.connect(self.the_button_was_clicked)
# Set the central widget of the Window.
self.setCentralWidget(button)
def the_button_was_clicked(self):
print("Clicked!")
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
现在,当我们点击这个按钮,在控制台会出现 Clicked!
这个程序使用 QMainWindow 包含了一个 QPushButton,放在窗口中间。根据上述定义 the_button_was_clicked 是我们创建的 slot,负责接收 QPushButton 发出的 clicked signal。
接收数据
根据上文我们知道 signals 还可以发送数据以提供更多的信息。例如 .clicked 信号提供了一个 checked 状态(bool类型)。(这个功能就像自锁开关,按下导通再按下断开)这个功能默认关闭,使用 button.setCheckable(True) 开启该功能。
import sys
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
button = QPushButton("Press Me!")
button.setCheckable(True)
button.clicked.connect(self.the_button_was_clicked)
button.clicked.connect(self.the_button_was_toggled)
self.setCentralWidget(button)
def the_button_was_clicked(self):
print("Clicked!")
def the_button_was_toggled(self, checked):
print("Checked?", checked)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
点击按钮得到了下面这样的输出
Clicked!
Checked? True
Clicked!
Checked? False
Clicked!
Checked? True
Clicked!
Checked? False
Clicked!
Checked? True
同样,也可以将多个 slots 连接到一个 signal。
储存数据
使用变量可以储存 signal 发出的数据,本例中使用 button_is_checked 变量来存储 checked 的值。
import sys
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.button_is_checked = True
self.setWindowTitle("My App")
button = QPushButton("Press Me!")
button.setCheckable(True)
button.clicked.connect(self.the_button_was_toggled)
button.setChecked(self.button_is_checked)
self.setCentralWidget(button)
def the_button_was_toggled(self, checked):
self.button_is_checked = checked
print(self.button_is_checked)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
首先给变脸设置默认值为 True,然后初始化组件,当组件状态改变时发出信号和数据,收到信号后更新变量的值。
如果组件没有提供发送当前状态的功能,则需要直接取出值。
import sys
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.button_is_checked = True
self.setWindowTitle("My App")
self.button = QPushButton("Press Me!")
self.button.setCheckable(True)
self.button.released.connect(self.the_button_was_released)
self.button.setChecked(self.button_is_checked)
self.setCentralWidget(self.button)
def the_button_was_released(self):
self.button_is_checked = self.button.isChecked()
print(self.button_is_checked)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
该程序运行起来与之前效果一致,不同的是:使用了 isChecked() 来获取的状态,而不是直接接收值,对于更多的函数,可以参考文档或在 IDE 中点击转到定义
class QAbstractButton(QWidget):
def __init__(self, parent: typing.Optional[QWidget] = ...) -> None: ...
def timerEvent(self, e: QtCore.QTimerEvent) -> None: ...
def changeEvent(self, e: QtCore.QEvent) -> None: ...
def focusOutEvent(self, e: QtGui.QFocusEvent) -> None: ...
def focusInEvent(self, e: QtGui.QFocusEvent) -> None: ...
def mouseMoveEvent(self, e: QtGui.QMouseEvent) -> None: ...
def mouseReleaseEvent(self, e: QtGui.QMouseEvent) -> None: ...
def mousePressEvent(self, e: QtGui.QMouseEvent) -> None: ...
def keyReleaseEvent(self, e: QtGui.QKeyEvent) -> None: ...
def keyPressEvent(self, e: QtGui.QKeyEvent) -> None: ...
def event(self, e: QtCore.QEvent) -> bool: ...
def nextCheckState(self) -> None: ...
def checkStateSet(self) -> None: ...
def hitButton(self, pos: QtCore.QPoint) -> bool: ...
def paintEvent(self, e: QtGui.QPaintEvent) -> None: ...
def toggled(self, checked: bool) -> None: ...
def clicked(self, checked: bool = ...) -> None: ...
def released(self) -> None: ...
def pressed(self) -> None: ...
def setChecked(self, a0: bool) -> None: ...
def toggle(self) -> None: ...
def click(self) -> None: ...
def animateClick(self) -> None: ...
def setIconSize(self, size: QtCore.QSize) -> None: ...
def group(self) -> 'QButtonGroup': ...
def autoExclusive(self) -> bool: ...
def setAutoExclusive(self, a0: bool) -> None: ...
def autoRepeat(self) -> bool: ...
def setAutoRepeat(self, a0: bool) -> None: ...
def isDown(self) -> bool: ...
def setDown(self, a0: bool) -> None: ...
def isChecked(self) -> bool: ...
def isCheckable(self) -> bool: ...
def setCheckable(self, a0: bool) -> None: ...
def shortcut(self) -> QtGui.QKeySequence: ...
def setShortcut(self, key: typing.Union[QtGui.QKeySequence, QtGui.QKeySequence.StandardKey, str, int]) -> None: ...
def iconSize(self) -> QtCore.QSize: ...
def icon(self) -> QtGui.QIcon: ...
def setIcon(self, icon: QtGui.QIcon) -> None: ...
def text(self) -> str: ...
def setText(self, text: str) -> None: ...
def autoRepeatInterval(self) -> int: ...
def setAutoRepeatInterval(self, a0: int) -> None: ...
def autoRepeatDelay(self) -> int: ...
def setAutoRepeatDelay(self, a0: int) -> None: ...
改变界面
目前按钮状态的变化输出在控制台中,现在将变化输出到图形界面
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
self.button = QPushButton("Press Me!")
self.button.clicked.connect(self.the_button_was_clicked)
self.setCentralWidget(self.button)
def the_button_was_clicked(self):
self.button.setText("You already clicked me.")
self.button.setEnabled(False)
# Also change the window title.
self.setWindowTitle("My Oneshot App")
app = QApplication([])
window = MainWindow()
window.show()
app.exec()
现在运行程序后,当按钮被点击,按钮的文字会被设置为 You already clicked me. 接着按钮会被设置为不可点击的状态。
因为需要获得 button 的信息 the_button_was_clicked 方法需引用 self。使用了 .setText() 改变了按钮内的文字,将 .setEnabled() 设为 False 来使其不可被点击。
不仅可以改变文字,也可以根据需求改变其它内容,例如:改变窗口标题
self.setWindowTitle("A new window title")
大部分组件都有自己的 sginals, QMainWindow 也不例外,下面的例子将 .windowTitleChanged 信号连接到自定义的 slot (下例中为 the_window_title_changed 方法)
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton
import sys
from random import choice
window_titles = [
'My App',
'My App',
'Still My App',
'Still My App',
'What on earth',
'What on earth',
'This is surprising',
'This is surprising',
'Something went wrong'
]
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.n_times_clicked = 0
self.setWindowTitle("My App")
self.button = QPushButton("Press Me!")
self.button.clicked.connect(self.the_button_was_clicked)
self.windowTitleChanged.connect(self.the_window_title_changed)
# Set the central widget of the Window.
self.setCentralWidget(self.button)
def the_button_was_clicked(self):
print("Clicked.")
new_window_title = choice(window_titles)
print("Setting title: %s" % new_window_title)
self.setWindowTitle(new_window_title)
def the_window_title_changed(self, window_title):
print("Window title changed: %s" % window_title)
if window_title == 'Something went wrong':
self.button.setDisabled(True)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
首先使用 list 设置了一组窗口标题,设置了两个 slot 使用 random.choice() 随机选取了一个标题,当选到 “Something went wrong” 时 buttom 设为不可点击。
首先, windowTitleChanged 并不会总是触发,列表内有重复的标题,只有当标题改变的时候该 sginal 才会被发送。
其次,这里有一个链式程序:点击按钮–>触发 the_button_was_clicked 方法–>标题若被修改–>触发 the_window_title_changed 方法
这些子序列不需要知道是谁触发了它们,而是遵顼一些接单的规则来依次执行。将效果(行为)从触发器(信号)解耦是 GUI 程序设计中的重要概念
直接连接组件
至今为止,之前使用的都是定义一个函数作为 slot 来接收 signal,实际上可以直接使用一个组件来接收。
下例中,在 window 中添加了一个 QLineEdit 和一个 QLabel 组件,当修改输入框中的内容后 .textChanged 发出 signal 后 .setText 负责接收,然后将其显示出来。(layout部分将在后序说明)
from PyQt6.QtWidgets import QApplication, QMainWindow, QLabel, QLineEdit, QVBoxLayout, QWidget
import sys
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
self.label = QLabel()
self.input = QLineEdit()
self.input.textChanged.connect(self.label.setText)
layout = QVBoxLayout()
layout.addWidget(self.input)
layout.addWidget(self.label)
container = QWidget()
container.setLayout(layout)
# Set the central widget of the Window.
self.setCentralWidget(container)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
大部分组件都有 slot 因此当发送和接收的数据类型先同时就可以直接将其连接到一起,可以在文档中查看内容详见 https://doc.qt.io/qt-6/qlabel.html#public-slots[QLabel]
Events
用户和 QT 程序的每一个交互都是一个 event,event有很多类型,每一类又有很多不同的交互情况。Qt 使用 event object 来表示,其打包了一系列发生的交互信息。
QMouseEvent 当鼠标发生一系列交互时会发出 signal,对于这些 handle 方法可以进行重写,下例中的重写需要一个变量来接收 QtGui.QMouseEvent 具体重写时需参考接口和对应的文档,示例中变量名为 e
import sys
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QApplication, QLabel, QMainWindow, QTextEdit
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.label = QLabel("Click in this window")
self.setCentralWidget(self.label)
def mouseMoveEvent(self, e):
self.label.setText("mouseMoveEvent")
def mousePressEvent(self, e):
self.label.setText("mousePressEvent")
def mouseReleaseEvent(self, e):
self.label.setText("mouseReleaseEvent")
def mouseDoubleClickEvent(self, e):
self.label.setText("mouseDoubleClickEvent")
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
运行后可以发现鼠标移动的事件只有在有按键按下时才会发生,在 __init__ 中使用 self.setMouseTracking(True) 可实现无点击时触发事件。
本例中由于使用了 QMainWindow,和其子类 lable,并且对 mouseMoveEvent 进行了重写,所以还要设置子类的 setMouseTracking,因此,还需要添加 self.label.setMouseTracking(True)
在本例中双击(该动作为连续点击某按键两次,共2次按下,2次松开)时可以发现 mousePressEvent 和 mouseDoubleClickEvent 都被触发了1次,而 mouseReleaseEvent 被触发了1次。实际上在 GUI 程序中检测鼠标的点击需要同时检测按下和松开。
Mouse events
所有的鼠标事件都在 QMouseEvent 中下面有一些方法
Method | Returns |
.button() | Specific button that triggered this event |
.buttons() | State of all mouse buttons (OR’ed flags) |
.position() | Widget-relative position as a QPoint integer |
下面的程序使用了 .buttom() 方法,并开启了 MouseTracking
import sys
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QApplication, QLabel, QMainWindow, QTextEdit
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.label = QLabel("Click in this window")
self.setCentralWidget(self.label)
self.setMouseTracking(True)
self.label.setMouseTracking(True)
def mouseMoveEvent(self, e):
self.label.setText("mouseMoveEvent")
def mousePressEvent(self, e):
if e.button() == Qt.MouseButton.LeftButton:
# handle the left-button press in here
self.label.setText("mousePressEvent LEFT")
elif e.button() == Qt.MouseButton.MiddleButton:
# handle the middle-button press in here.
self.label.setText("mousePressEvent MIDDLE")
elif e.button() == Qt.MouseButton.RightButton:
# handle the right-button press in here.
self.label.setText("mousePressEvent RIGHT")
def mouseReleaseEvent(self, e):
if e.button() == Qt.MouseButton.LeftButton:
self.label.setText("mouseReleaseEvent LEFT")
elif e.button() == Qt.MouseButton.MiddleButton:
self.label.setText("mouseReleaseEvent MIDDLE")
elif e.button() == Qt.MouseButton.RightButton:
self.label.setText("mouseReleaseEvent RIGHT")
def mouseDoubleClickEvent(self, e):
if e.button() == Qt.MouseButton.LeftButton:
self.label.setText("mouseDoubleClickEvent LEFT")
elif e.button() == Qt.MouseButton.MiddleButton:
self.label.setText("mouseDoubleClickEvent MIDDLE")
elif e.button() == Qt.MouseButton.RightButton:
self.label.setText("mouseDoubleClickEvent RIGHT")
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
内容菜单
内容菜单常常出现,一般是在窗口右键时打开,例如在浏览器中单击右键或在文件管理器中单击右键,Qt 中对 contextMenuEvent 进行重写来实现。
import sys
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QAction
from PyQt6.QtWidgets import QApplication, QLabel, QMainWindow, QMenu
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
def contextMenuEvent(self, e):
context = QMenu(self)
context.addAction(QAction("test 1", self))
context.addAction(QAction("test 2", self))
context.addAction(QAction("test 3", self))
context.exec(e.globalPos())
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
下面使用 single 来实现
import sys
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QAction
from PyQt6.QtWidgets import QApplication, QLabel, QMainWindow, QMenu
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.show()
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.on_context_menu)
def on_context_menu(self, pos):
context = QMenu(self)
context.addAction(QAction("test 1", self))
context.addAction(QAction("test 2", self))
context.addAction(QAction("test 3", self))
context.exec(self.mapToGlobal(pos))
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
PyQt6 组件
更多示例演示可查看 Widgets Gallery Example
快速介绍
测试,运行下面代码如果可以正常运行则代表需要的功能可以正常工作,一下内容作为模板直接更改 MainWindow 类就可以。
import sys
from PyQt6.QtWidgets import (
QMainWindow, QApplication,
QLabel, QCheckBox, QComboBox, QListWidget, QLineEdit,
QLineEdit, QSpinBox, QDoubleSpinBox, QSlider
)
from PyQt6.QtCore import Qt
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("My App")
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec()
运行下面这些代码可以查看一些常用的组件
import sys
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import (
QApplication,
QCheckBox,
QComboBox,
QDateEdit,
QDateTimeEdit,
QDial,
QDoubleSpinBox,
QFontComboBox,
QLabel,
QLCDNumber,
QLineEdit,
QMainWindow,
QProgressBar,
QPushButton,
QRadioButton,
QSlider,
QSpinBox,
QTimeEdit,
QVBoxLayout,
QWidget,
)
# Subclass QMainWindow to customize your application's main window
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Widgets App")
layout = QVBoxLayout()
widgets = [
QCheckBox,
QComboBox,
QDateEdit,
QDateTimeEdit,
QDial,
QDoubleSpinBox,
QFontComboBox,
QLCDNumber,
QLabel,
QLineEdit,
QProgressBar,
QPushButton,
QRadioButton,
QSlider,
QSpinBox,
QTimeEdit,
]
for w in widgets:
layout.addWidget(w())
widget = QWidget()
widget.setLayout(layout)
# Set the central widget of the Window. Widget will expand
# to take up all the space in the window by default.
self.setCentralWidget(widget)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
QLabel
QLabel 是最简单的 widget,可以将一个单行文本放入应用程序,使用时可以用字符串传入,或者使用 .setText() 方法
widget = QLabel("1") # The label is created with the text 1.
widget.setText("2") # The label now shows 2.
同时也可以设置字体相关的选项
import sys
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QAction
from PyQt6.QtWidgets import QApplication, QLabel, QMainWindow
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("My App")
widget = QLabel("Hello")
font = widget.font()
font.setPointSize(30)
widget.setFont(font)
widget.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter)
self.setCentralWidget(widget)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
注:如果要更改组件的字体,最好是获得当前系统使用的字体,并在它的基础上修改它,而不是直接使用某一个字体,在不同系统上,字体可能不同
关于中英文文字对齐方式,可以参考CAD相关的机械制图、工程制图、图形设计等设计类文章,这里不做解释。
使用管道符可以将多个 flag 连接起来,本例中水平和垂直方向都设为了居中对齐,这里还可以使用 Qt.AlignmentFlag.AlignCenter 来实现两个方向的居中。
Qlabel 使用 .setPixmap() 方法也可以用于显示位图,在没有使用 IDE 前,建议的使用方法如下:
import sys
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QAction, QPixmap
from PyQt6.QtWidgets import QApplication, QLabel, QMainWindow
import os.path
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("My App")
widget = QLabel(self)
widget.setScaledContents(True)
CURRENT_DIRECTORY = os.path.dirname(os.path.realpath(__file__))
filename = os.path.join(CURRENT_DIRECTORY, "pic.jpg")
widget.setPixmap(QPixmap(filename))
self.setCentralWidget(widget)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
代码中使用了 os.path 来获取绝对路径,保证输出正常,图片默认会按照原比例显示,填满 label 使用了 widget.setScaledContents(True) 可以让图片进行拉伸、缩放,此时调整窗口大小,图片会一起变化
QCheckBox
可勾选的框和文字,这个勾选框共三种状态
PyQt6 flag (long name) | Behavior | Number |
Qt.CheckState.Unchecked | 取消选中 | 0 |
Qt.CheckState.PartiallyChecked | 部分选中 | 1 |
Qt.CheckState.Checked | 选中 | 2 |
右侧的数字为是否选中的状态,例如未勾选时,对应的值是0,部分选中时对应的值是1。
关于部分选中使用的场景较少,一般在有父级 checkbox 时使用。
可以使用 Qt.CheckState.PartiallyChecked 将其设为部分选中,
或者使用 .setTriState(True) 开启部分选中功能,但不设为部分选中。
import sys
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QApplication, QCheckBox, QMainWindow
import os.path
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("My App")
widget = QCheckBox("my box")
widget.setCheckState(Qt.CheckState.Checked)
# For tristate: widget.setCheckState(Qt.PartiallyChecked)
# widget.setTristate(True)
widget.stateChanged.connect(self.show_state)
self.setCentralWidget(widget)
def show_state(self, s):
print(s == Qt.CheckState.Checked.value)
print(s)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
例中,不需要记住每个状态对应的值,直接使用 Qt.CheckState.Checked.value (在定义中可以看到 class CheckState(enum.Enum): 这是一个枚举类,使用value获得对应的值)
QComboBox
包含一个下拉菜单,让用户进行选择。默认选择为空,需要用户点击箭头打开下拉菜单,选择一个选项。 .addItems() 用于添加选项,内部是一个列表。使用 .currentIndexChanged 和 .currentTextChanged 当被选中的数组索引或文字变化时发出 signal 和对应的值
import sys
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QApplication, QComboBox, QMainWindow
import os.path
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("My App")
widget = QComboBox()
widget.addItems(["One", "Two", "Three"])
# Sends the current index (position) of the selected item.
widget.currentIndexChanged.connect(self.index_changed)
# There is an alternate signal to send the text.
widget.currentTextChanged.connect(self.text_changed)
self.setCentralWidget(widget)
def index_changed(self, i): # i is an int
print(i)
def text_changed(self, s): # s is a str
print(s)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
QComboBox 可以被编辑,允许用户输入一个下拉菜单中不存在的值,或直接输入一个值,使用 .setEditable(True) 启用
当用户插入新的值的时候,使用 .InsertPolicy 来选择插入到列表的某个位置,默认为 .InsertAtBottom,还可以通过 .setMaxCount 来设置用户最多可插入的值
具体使用可参考 https://www.pythonguis.com/docs/qcombobox/
QListWidget
类似于 QComboBox 但选项由可以滚动的列表构成。currentItemChanged 和 currentTextChanged 与之前的使用方法类似,item 不是 index 而是 QListItem。
import sys
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QApplication, QListWidget, QMainWindow
import os.path
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("My App")
widget = QListWidget()
widget.addItems(["One", "Two", "Three"])
widget.currentItemChanged.connect(self.index_changed)
widget.currentTextChanged.connect(self.text_changed)
self.setCentralWidget(widget)
def index_changed(self, i): # Not an index, i is a QListItem
print(i.text())
def text_changed(self, s): # s is a str
print(s)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
QLineEdit
单行输入框,用户可以输入一些值例如输入邮件地址、计算机名字等
在修改内容时,会触发 .textEdited 默认情况下按下回车时输入会被提交,触发 .returnPressed 同时若文字被修改则触发 .textChanged。当使用鼠标左键选中文本时会触发 .selectionChanged
import sys
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QApplication, QLineEdit, QMainWindow
import os.path
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("My App")
widget = QLineEdit()
widget.setMaxLength(10)
widget.setPlaceholderText("Enter your text")
#widget.setReadOnly(True) # uncomment this to make readonly
widget.returnPressed.connect(self.return_pressed)
widget.selectionChanged.connect(self.selection_changed)
widget.textChanged.connect(self.text_changed)
widget.textEdited.connect(self.text_edited)
self.setCentralWidget(widget)
def return_pressed(self):
print("Return pressed!")
self.centralWidget().setText("BOOM!")
def selection_changed(self):
print("Selection changed")
print(self.centralWidget().selectedText())
def text_changed(self, s):
print("Text changed...")
print(s)
def text_edited(self, s):
print("Text edited...")
print(s)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
可以设置掩码,指定输入格式,例如
widget.setInputMask('000.000.000.000;_')
可被用于验证是否是ip地址
QSpinBox and QDoubleSpinBox
常用于输入数字,右侧有箭头来调整输入的值,QSpinBox 支持整数,QDoubleSpinBox 支持浮点数
import sys
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QApplication, QSpinBox, QMainWindow
import os.path
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
widget = QSpinBox()
# Or: widget = QDoubleSpinBox()
widget.setMinimum(-10)
widget.setMaximum(3)
# Or: widget.setRange(-10,3)
widget.setPrefix("$")
widget.setSuffix("c")
widget.setSingleStep(3) # Or e.g. 0.5 for QDoubleSpinBox
widget.valueChanged.connect(self.value_changed)
widget.textChanged.connect(self.value_changed_str)
self.setCentralWidget(widget)
def value_changed(self, i):
print(i)
def value_changed_str(self, s):
print(s)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
.setMinimum 和 .setMaximum 设最小值为 -10 最大值为 3
.setSingleStep 让箭头的步长为 3
.setPrefix 和 .setSuffix 并且有一些固定的前缀或后缀文字
也有检测变化的方法,其中 .textChanged 发送的是字符串,.valueChanged 返回的是值
QSlider
是个可滑动的条,QDoubleSpinBox 不显示数值,适用于调整不需要绝对值时的情况,常用于音量条。
import sys
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QApplication, QSlider, QMainWindow
import os.path
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
widget = QSlider()
widget.setMinimum(-10)
widget.setMaximum(3)
# Or: widget.setRange(-10,3)
widget.setSingleStep(3)
widget.valueChanged.connect(self.value_changed)
widget.sliderMoved.connect(self.slider_position)
widget.sliderPressed.connect(self.slider_pressed)
widget.sliderReleased.connect(self.slider_released)
self.setCentralWidget(widget)
def value_changed(self, i):
print(i)
def slider_position(self, p):
print("position", p)
def slider_pressed(self):
print("Pressed!")
def slider_released(self):
print("Released")
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
其它用法类似,还可以通过 Qt.Orientiation.Vertical 和 Qt.Orientiation.Horizontal 创建竖直或水平的滑动条
widget.QSlider(Qt.Orientiation.Vertical)
#or
widget.QSlider(Qt.Orientiation.Horizontal)
QDial
有用,但不是完全有用,用在音视频处理、艺术创作的 APP 中,一般不好用也没卵用。主要原因是鼠标需要画圆来操作。
import sys
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QApplication, QDial, QMainWindow
import os.path
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
widget = QDial()
widget.setRange(-10, 100)
widget.setSingleStep(1)
widget.valueChanged.connect(self.value_changed)
widget.sliderMoved.connect(self.slider_position)
widget.sliderPressed.connect(self.slider_pressed)
widget.sliderReleased.connect(self.slider_released)
self.setCentralWidget(widget)
def value_changed(self, i):
print(i)
def slider_position(self, p):
print("position", p)
def slider_pressed(self):
print("Pressed!")
def slider_released(self):
print("Released")
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
PyQt6 Layouts
没有 layout,一个窗口只有一个组件,使用 layout 负责排列各个组件。
layout 可以在 Qt designer 中直接设计,目前使用代码的方式来了解什么是 layout
Layout | Behavior |
QHBoxLayout | 沿直线水平布置 |
QVBoxLayout | 沿直线处置布置 |
QGridLayout | 使用网格布置 |
QStackedLayout | 将一个放在另一个上面 |
同上一章一样,以下是通用内容
import sys
from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget
from PyQt6.QtGui import QPalette, QColor
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("My App")
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
使用下面这些代码
import sys
from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget
from PyQt6.QtGui import QPalette, QColor
class Color(QWidget):
def __init__(self, color):
super(Color, self).__init__()
self.setAutoFillBackground(True)
palette = self.palette()
palette.setColor(QPalette.ColorRole.Window, QColor(color))
self.setPalette(palette)
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("My App")
widget = Color('red')
self.setCentralWidget(widget)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
创建了自定义的组件 Color,.setAutoFillBackground 使颜色填满窗口 获得当前使用的颜色库,并输入某种颜色(由 widget = Color(‘red’) 传进红色),最后应用该颜色。
自定义组件将在后序内容中了解,这里只要了解该自定义组件可以把红色填满组件
接着使用 .setCentralWidget 将组件居中,运行后得到一个红色的窗口,拉伸修改窗口大小红色依旧填满窗口
QVBoxLayout
组件由上到下依次排列,使用 .addWidget() 来添加组件
为了将 Layout 和 自定义 widget 放入窗口,这里需要一个空的 QWidget,然后 .setCentralWidget 放入窗口
import sys
from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout
from PyQt6.QtGui import QPalette, QColor
class Color(QWidget):
def __init__(self, color):
super(Color, self).__init__()
self.setAutoFillBackground(True)
palette = self.palette()
palette.setColor(QPalette.ColorRole.Window, QColor(color))
self.setPalette(palette)
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("My App")
layout = QVBoxLayout()
layout.addWidget(Color('red'))
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
运行后有一个边框出现,这是 layout 的间距,稍后提到,此时如果添加多个组件可以看到它们将按顺序由上到下排列。
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("My App")
layout = QVBoxLayout()
layout.addWidget(Color('red'))
layout.addWidget(Color('green'))
layout.addWidget(Color('blue'))
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
QHBoxLayout
水平同理,顺序为从左到右排列
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("My App")
layout = QHBoxLayout()
layout.addWidget(Color('red'))
layout.addWidget(Color('green'))
layout.addWidget(Color('blue'))
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
嵌套 layout
layout 可以嵌套,思考下面的代码将会如何运行
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("My App")
layout1 = QHBoxLayout()
layout2 = QVBoxLayout()
layout3 = QVBoxLayout()
layout2.addWidget(Color('red'))
layout2.addWidget(Color('yellow'))
layout2.addWidget(Color('purple'))
layout1.addLayout( layout2 )
layout1.addWidget(Color('green'))
layout3.addWidget(Color('red'))
layout3.addWidget(Color('purple'))
layout1.addLayout( layout3 )
widget = QWidget()
widget.setLayout(layout1)
self.setCentralWidget(widget)
使用 .setContentMargins 按照“左,上,右,下”的顺序设置layout 间的间距,使用 .setSpacing 可以设置两个元素间的间距,在创建完 layout 后加入下面的代码先将一组值修改为 0 然后修改另一组值,查看变化
layout1.setContentsMargins(0,0,0,0)
layout1.setSpacing(20)
QGridLayout
当越来越多的 layout 需要被嵌套,使用时逻辑会非常复杂,使用 QGridLayout 解决该问题
例如,可以创建这样一个 gird,对应的坐标如下
0,0 | 0,1 | 0,2 | 0,3 |
1,0 | 1,1 | 1,2 | 1,3 |
2,0 | 2,1 | 2,2 | 2,3 |
3,0 | 3,1 | 3,2 | 3,3 |
可以根据需要使用对应的网格,不需要全部填满
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("My App")
layout = QGridLayout()
layout.addWidget(Color('red'), 0, 0)
layout.addWidget(Color('green'), 1, 0)
layout.addWidget(Color('blue'), 1, 1)
layout.addWidget(Color('purple'), 2, 1)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
QStackedLayout
类似于图层的概念,一层在另一层的上面,可以选择要显示哪一层,可以用于构建类似于浏览器标签的页面。
这里有一个 QStackedWidget 组件,和 QStackedLayout 类似。这是一个容器,可以含有多个组件,它可以直接将 widget 直接放入 QMainWindow 而不创建空的组件。
import sys
from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget, QStackedLayout
from PyQt6.QtGui import QPalette, QColor
class Color(QWidget):
def __init__(self, color):
super(Color, self).__init__()
self.setAutoFillBackground(True)
palette = self.palette()
palette.setColor(QPalette.ColorRole.Window, QColor(color))
self.setPalette(palette)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
layout = QStackedLayout()
layout.addWidget(Color("red"))
layout.addWidget(Color("green"))
layout.addWidget(Color("blue"))
layout.addWidget(Color("yellow"))
layout.setCurrentIndex(3)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
练习
写一个程序上方有三个按钮,代表三个不同颜色,下方有一块区域显示该颜色,当按下按钮时切换到对应的颜色
import sys
from tkinter import Widget
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import (
QApplication,
QHBoxLayout,
QLabel,
QMainWindow,
QPushButton,
QStackedLayout,
QVBoxLayout,
QWidget,
)
from PyQt6.QtGui import QPalette, QColor
class Color(QWidget):
def __init__(self, color):
super(Color, self).__init__()
self.setAutoFillBackground(True)
palette = self.palette()
palette.setColor(QPalette.ColorRole.Window, QColor(color))
self.setPalette(palette)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
layout_button = QHBoxLayout()
self.layout_screen = QStackedLayout()
layout_page = QVBoxLayout()
layout_page.addLayout(layout_button)
layout_page.addLayout(self.layout_screen)
button_red = QPushButton("red")
button_green = QPushButton("green")
button_yellow = QPushButton("yellow")
button_red.pressed.connect(self.active_red)
button_green.pressed.connect(self.active_green)
button_yellow.pressed.connect(self.active_yellow)
layout_button.addWidget(button_red)
layout_button.addWidget(button_green)
layout_button.addWidget(button_yellow)
self.layout_screen.addWidget(Color("red"))
self.layout_screen.addWidget(Color("green"))
self.layout_screen.addWidget(Color("yellow"))
widget = QWidget()
widget.setLayout(layout_page)
self.setCentralWidget(widget)
def active_red(self):
self.layout_screen.setCurrentIndex(0)
def active_green(self):
self.layout_screen.setCurrentIndex(1)
def active_yellow(self):
self.layout_screen.setCurrentIndex(2)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
这段程序中,两个子类都需要修改 layout_screen,因此需要写成 self.layout_screen
Qt 还提供了 QTabWidget
使用 QTabWidget.West 可使 tab 放在窗口左侧
使用 .setMovable(True) 可使 tab 具有拖拽功能,类似于浏览器标签
使用 .setDocumentMode(True) 可用于在 MacOS 平台调整显示风格
这时,只需要创建各个 tab 就可以了,不需要取处理其它关系,会生成类似的可交互的窗口。
import sys
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import (
QApplication,
QMainWindow,
QWidget,
QTabWidget
)
from PyQt6.QtGui import QPalette, QColor
class Color(QWidget):
def __init__(self, color):
super(Color, self).__init__()
self.setAutoFillBackground(True)
palette = self.palette()
palette.setColor(QPalette.ColorRole.Window, QColor(color))
self.setPalette(palette)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
tabs = QTabWidget()
tabs.setTabPosition(QTabWidget.TabPosition.West)
tabs.setMovable(True)
for n, color in enumerate(["red", "green", "blue", "yellow"]):
tabs.addTab(Color(color), color)
self.setCentralWidget(tabs)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
PyQt6 Toolbars & Menus — QAction
工具栏很常见,大部分应用程序都有工具栏,比如浏览器的工具栏,IDE 的工具栏。MS Office 曾经使用工具栏,现在使用了功能区,对于大部分应用程序工具栏足够使用了。
和菜单栏不同的是,工具栏通常是一个按钮,点击后按钮会执行响应的动作,比如浏览器的前进、后退、刷新页面。
同样的,以下内容为通用部分
import sys
from PyQt6.QtWidgets import (
QMainWindow, QApplication,
QLabel, QToolBar, QStatusBar
)
from PyQt6.QtGui import QAction, QIcon
from PyQt6.QtCore import Qt
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("My Awesome App")
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec()
添加一个工具栏
继承 QToolBar 类用于创建工具栏,由于使用了 QMainWindow 它本身就有一个工具栏,可以直接使用 QToolBar 创建一个工具栏,然后直接 .addToolbar 添加就可以了
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("My Awesome App")
label = QLabel("Hello!")
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.setCentralWidget(label)
toolbar = QToolBar("My main toolbar")
self.addToolBar(toolbar)
def onMyToolBarButtonClick(self, s):
print("click", s)
运行后可以看到窗口上有一个工具栏,右键工具栏点击它的名字可以把工具栏关掉。如果想要再打开该工具栏还需要提供额外的交互界面。如:鼠标右键菜单
可以直接使用 QButton 向工具栏添加一个按钮,但 Qt 中提供的 QAction 具有更丰富的功能,可以再单个对象中定义多个元素(如工具按钮),实现交互效果统一。
例如:在 word 的编辑菜单中点击撤销、直接点击撤销的箭头、使用快捷键达到的效果都是撤销上一个动作。如果没有 QAction 可能要把一个动作定义很多遍,而有了 QAction 只需要一次定义、然后设置触发条件,然后把这个触发条件放到指定位置。
每一个 QAction 都有名字、信息、图标和信号(names, status messages, icons and signals)可以自行修改,还可以添加更多内容
import sys
from PyQt6.QtWidgets import (
QMainWindow, QApplication,
QLabel, QToolBar, QStatusBar
)
from PyQt6.QtGui import QAction, QIcon
from PyQt6.QtCore import Qt
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("My Awesome App")
label = QLabel("Hello!")
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.setCentralWidget(label)
toolbar = QToolBar("My main toolbar")
self.addToolBar(toolbar)
button_action = QAction("Your button", self)
button_action.setStatusTip("This is your button")
button_action.triggered.connect(self.onMyToolBarButtonClick)
toolbar.addAction(button_action)
def onMyToolBarButtonClick(self, s):
print("click", s)
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec()
创建了一个 onMyToolBarButtonClick 函数用于接收 signal 的 slot 以便观察。实例化 QAction 时,可以设置名字和图标,还需要一个 QObject 作为夫元素,这里使用了 self,作为主窗口,对于 QAction 父元素在末尾位置。
.setStatusTip 用于在状态栏显示按钮信息(目前没有状态栏)最后把 triggered 信号传递给自定义的函数
运行后会发现总是控制台总是输出 false,这是因为这是个自复位按钮(按下后自己弹起)没有 checked 状态
接下来,使用 QStatusBar 添加状态栏,目前不需要修改它。
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("My Awesome App")
label = QLabel("Hello!")
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.setCentralWidget(label)
toolbar = QToolBar("My main toolbar")
self.addToolBar(toolbar)
button_action = QAction("Your button", self)
button_action.setStatusTip("This is your button")
button_action.triggered.connect(self.onMyToolBarButtonClick)
toolbar.addAction(button_action)
self.setStatusBar(QStatusBar(self))
def onMyToolBarButtonClick(self, s):
print("click", s)
此时,当鼠标悬停到工具栏按钮时就会看到状态栏的提示信息
接下来使用 setCheckable(True) 来让按钮可以 checked,在 .addAction 前添加
button_action.setCheckable(True)
现在点击该按钮,可以发现按钮具有了按下的状态,控制台会输出 True 和 False。
还有一个 .toggled signal,当可被 checked 按钮按下时触发,但它与 .triggered 功能一样,所以没啥用。
现在可以为这些按钮添加图标,可以下载 fugue icon set 或者在图标库搜索后下载,其它图标库还有很多。
还需要使用 .setIconSize(QSize object) 让工具栏知道图标有多大,否则可能会显示异常。
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("My Awesome App")
label = QLabel("Hello!")
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.setCentralWidget(label)
toolbar = QToolBar("My main toolbar")
toolbar.setIconSize(QSize(16, 16))
self.addToolBar(toolbar)
button_action = QAction(QIcon("bug.png"), "Your button", self)
button_action.setStatusTip("This is your button")
button_action.triggered.connect(self.onMyToolBarButtonClick)
button_action.setCheckable(True)
toolbar.addAction(button_action)
self.setStatusBar(QStatusBar(self))
def onMyToolBarButtonClick(self, s):
print("click", s)
如果图标不显示,可以使用绝对路径,Windows 下使用 \ 时需注意转义字符。
Qt 默认使用操作系统的设置来决定显示图标、文字还是图标和文字,可以使用 .setToolButtonStyle 重载。
PyQt6 flag (long name) | Behavior |
Qt.ToolButtonIconOnly | 仅图标 |
Qt.ToolButtonTextOnly | 仅文本 |
Qt.ToolButtonTextBesideIcon | 文本在图标旁边 |
Qt.ToolButtonTextUnderIcon | 文本在图标下方 |
Qt.ToolButtonFollowStyle | (默认)跟随系统 |
建议使用跟随系统方案,来让应用得更加适应系统风格。
现在,添加一个按钮和一个 checkbox 组件,实际上之前提到的组件都可以放在工具栏中,以下为示例
import sys
from PyQt6.QtCore import QSize, Qt
from PyQt6.QtGui import QAction, QIcon
from PyQt6.QtWidgets import (
QApplication,
QCheckBox,
QLabel,
QMainWindow,
QStatusBar,
QToolBar,
)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
label = QLabel("Hello!")
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.setCentralWidget(label)
toolbar = QToolBar("My main toolbar")
toolbar.setIconSize(QSize(16, 16))
self.addToolBar(toolbar)
button_action = QAction(QIcon("bug.png"), "&Your button", self)
button_action.setStatusTip("This is your button")
button_action.triggered.connect(self.onMyToolBarButtonClick)
button_action.setCheckable(True)
toolbar.addAction(button_action)
toolbar.addSeparator()
button_action2 = QAction(QIcon("\bug.png"), "Your &button2", self)
button_action2.setStatusTip("This is your button2")
button_action2.triggered.connect(self.onMyToolBarButtonClick)
button_action2.setCheckable(True)
toolbar.addAction(button_action2)
toolbar.addWidget(QLabel("Hello"))
toolbar.addWidget(QCheckBox(""))
self.setStatusBar(QStatusBar(self))
def onMyToolBarButtonClick(self, s):
print("click", s)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
菜单
菜单是另一个非常重要的 UI 组成,它们基本上都在窗口的最上方,可以控制应用程序的全部功能。
同样的,在使用 QMainWindow 时 .menuBar() 用于创建菜单,使用 .addMenu() 将其加入窗口中
本例中第一个菜单选项名为使用 ‘&File’ ,& 符号定义了使用快捷键 Alt 来使用/打开它,不少应用程序的菜单栏都是这么打开的,可以在 Firefox 浏览器中按下 Alt 来尝试打开菜单栏。(在 MacOS 下该快捷键不适用)
现在在菜单栏中我们可以直接使用 .addAction 来添加菜单选项,添加一个定义过的按钮,而不是定义一个新按钮
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
label = QLabel("Hello!")
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.setCentralWidget(label)
toolbar = QToolBar("My main toolbar")
toolbar.setIconSize(QSize(16, 16))
self.addToolBar(toolbar)
button_action = QAction(QIcon("bug.png"), "&Your button", self)
button_action.setStatusTip("This is your button")
button_action.triggered.connect(self.onMyToolBarButtonClick)
button_action.setCheckable(True)
toolbar.addAction(button_action)
toolbar.addSeparator()
button_action2 = QAction(QIcon("bug.png"), "Your &button2", self)
button_action2.setStatusTip("This is your button2")
button_action2.triggered.connect(self.onMyToolBarButtonClick)
button_action2.setCheckable(True)
toolbar.addAction(button_action2)
toolbar.addWidget(QLabel("Hello"))
toolbar.addWidget(QCheckBox())
self.setStatusBar(QStatusBar(self))
menu = self.menuBar()
file_menu = menu.addMenu("&File")
file_menu.addAction(button_action)
def onMyToolBarButtonClick(self, s):
print("click", s)
添加更多内容
file_menu = menu.addMenu("&File")
file_menu.addAction(button_action)
file_menu.addSeparator()
file_menu.addAction(button_action2)
现在有了两个菜单选项和一个分隔符,还可以使用 & 符号添加快捷键
现在添加一个子菜单,在父级菜单上直接使用 addMenu() 就可以,例如
file_menu = menu.addMenu("&File")
file_menu.addAction(button_action)
file_menu.addSeparator()
file_submenu = file_menu.addMenu("Submenu")
file_submenu.addAction(button_action2)
使用 setKeySequence() 可以设置快捷键,完整示例如下
PyQt6 Dialogs and Alerts
QDialog
Dialog 是一种常用的 GUI 组件,用于和用户交流,它们总是在主窗口的前面,例如使用浏览器上传或下载文件时弹出的浏览窗口
同其它组件一样,QDialog 的使用用需要创建一个 QDialog 对象
import sys
from PyQt6.QtWidgets import QApplication, QDialog, QMainWindow, QPushButton
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
button = QPushButton("Press me for a dialog!")
button.clicked.connect(self.button_clicked)
self.setCentralWidget(button)
def button_clicked(self, s):
print("click", s)
dlg = QDialog(self)
dlg.setWindowTitle("HELLO!")
dlg.exec()
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
dlg.exec() 创建了一个新的事件循环,类似于末尾的 app.exec() 。该事件循环将只服务于弹出的 dialog。目前, QDialog 弹出会锁定之前的内容,也就是说主程序会暂停运行。如果想要同时运行,可以使用多线/进程来处理这个问题。
继承 QDialog 类可以实现自定义 dialog
class CustomDialog(QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle("HELLO!")
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
self.buttonBox = QDialogButtonBox(QBtn)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
self.layout = QVBoxLayout()
message = QLabel("Something happened, is that OK?")
self.layout.addWidget(message)
self.layout.addWidget(self.buttonBox)
self.setLayout(self.layout)
创建了一个子类 CustomDialog ,使用时在 QMainWindow 中的 __init__ 部分应用(apply)该子类。使用 .setWindowTitle() 创建 dialog 窗口标题,实际上和普通窗口标题一样。
接下来创建、显示这些按钮,相比普通按钮,这个创建过程相对复杂,主要原因是为了在不同平台上都能兼容
创建按钮时应使用标准、通用的布局,例如对于 yes 和 no 这类,要遵从 yes 在左边,no在右边,QDialogButtonBox 除了 .StandardButton.Ok 和 .StandardButton.Cancel 还有其它按钮,可以参考文档
如上例中所示,在一行内插入多个 button 可以直接使用管道符连接多个按钮,然后使用 QDialogButtonBox() 来创建两个按钮,在本例中连接了 accepted 和 rejected
最后,为了使 QDialog 正常显示,添加了一个 layout ,并在 MainWindow 子类中使用
import sys
from PyQt6.QtWidgets import QApplication, QDialog, QMainWindow, QPushButton, QDialogButtonBox, QVBoxLayout, QLabel
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
button = QPushButton("Press me for a dialog!")
button.clicked.connect(self.button_clicked)
self.setCentralWidget(button)
def button_clicked(self, s):
print("click", s)
dlg = CustomDialog()
if dlg.exec():
print("Success!")
else:
print("Cancel!")
class CustomDialog(QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle("HELLO!")
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
self.buttonBox = QDialogButtonBox(QBtn)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
self.layout = QVBoxLayout()
message = QLabel("Something happened, is that OK?")
self.layout.addWidget(message)
self.layout.addWidget(self.buttonBox)
self.setLayout(self.layout)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
运行后会发现 dialog 会在屏幕中间弹出,实际上程序应该在通常需要在父窗口的中间弹出,添加一个父级窗口即可
class CustomDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("HELLO!")
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
self.buttonBox = QDialogButtonBox(QBtn)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
self.layout = QVBoxLayout()
message = QLabel("Something happened, is that OK?")
self.layout.addWidget(message)
self.layout.addWidget(self.buttonBox)
self.setLayout(self.layout)
设置 parent=None 可以保留屏幕中心弹出的功能,下面这个 CustomDialog(self) 传进了使用的 QMainWindow
def button_clicked(self, s):
print("click", s)
dlg = CustomDialog(self)
if dlg.exec():
print("Success!")
else:
print("Cancel!")
完整程序如下
import sys
from PyQt6.QtWidgets import QApplication, QDialog, QMainWindow, QPushButton, QDialogButtonBox, QVBoxLayout, QLabel
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
button = QPushButton("Press me for a dialog!")
button.clicked.connect(self.button_clicked)
self.setCentralWidget(button)
def button_clicked(self, s):
print("click", s)
dlg = CustomDialog(self)
if dlg.exec():
print("Success!")
else:
print("Cancel!")
class CustomDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("HELLO!")
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
self.buttonBox = QDialogButtonBox(QBtn)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
self.layout = QVBoxLayout()
message = QLabel("Something happened, is that OK?")
self.layout.addWidget(message)
self.layout.addWidget(self.buttonBox)
self.setLayout(self.layout)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
现在运行程序后, dialog 会出现在窗口中间
QMessageBox
对于上面创建的这种简单的 dialog 可以使用 QMessageBox 代替,可以在定义中看到:该类继承自 QDialog
import sys
from PyQt6.QtWidgets import QApplication, QDialog, QMainWindow, QMessageBox, QPushButton
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
button = QPushButton("Press me for a dialog!")
button.clicked.connect(self.button_clicked)
self.setCentralWidget(button)
def button_clicked(self, s):
dlg = QMessageBox(self)
dlg.setWindowTitle("I have a question!")
dlg.setText("This is a simple dialog")
button = dlg.exec()
if button == QMessageBox.StandardButton.Ok:
print("OK!")
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
同样的对于按钮,除了 QMessageBox.StandardButton.Ok 还有其它按钮,可以参考文档,它们也可以使用管道符 | 来连接
- QMessageBox.StandardButton.Ok
- QMessageBox.StandardButton.Open
- QMessageBox.StandardButton.Save
- QMessageBox.StandardButton.Cancel
- QMessageBox.StandardButton.Close
- QMessageBox.StandardButton.Discard
- QMessageBox.StandardButton.Apply
- QMessageBox.StandardButton.Reset
- QMessageBox.StandardButton.RestoreDefaults
- QMessageBox.StandardButton.Help
- QMessageBox.StandardButton.SaveAll
- QMessageBox.StandardButton.Yes
- QMessageBox.StandardButton.YesToAll
- QMessageBox.StandardButton.No
- QMessageBox.StandardButton.NoToAll
- QMessageBox.StandardButton.Abort
- QMessageBox.StandardButton.Retry
- QMessageBox.StandardButton.Ignore
- QMessageBox.StandardButton.NoButton
messagebox 的 dialog 里还可以放入一些通用图标
- QMessageBox.Icon.NoIcon
- QMessageBox.Icon.Question
- QMessageBox.Icon.Information
- QMessageBox.Icon.Warning
- QMessageBox.Icon.Critical
例如:创建一个 question dialog 包含 Yes 和 No 两个按钮
import sys
from PyQt6.QtWidgets import QApplication, QDialog, QMainWindow, QMessageBox, QPushButton
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
button = QPushButton("Press me for a dialog!")
button.clicked.connect(self.button_clicked)
self.setCentralWidget(button)
def button_clicked(self, s):
dlg = QMessageBox(self)
dlg.setWindowTitle("I have a question!")
dlg.setText("This is a question dialog")
dlg.setStandardButtons(
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
dlg.setIcon(QMessageBox.Icon.Question)
button = dlg.exec()
if button == QMessageBox.StandardButton.Yes.value:
print("Yes!")
else:
print("No!")
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
Built in QMessageBox dialogs
QMessageBox 有一些内置的 dialog 模板,不再需要手动添加按钮和布局。
QMessageBox.about(parent, title, message)
QMessageBox.critical(parent, title, message)
QMessageBox.information(parent, title, message)
QMessageBox.question(parent, title, message)
QMessageBox.warning(parent, title, message)
使用
def button_clicked(self, s):
button = QMessageBox.question(self, "Question dialog", "The longer message")
if button == QMessageBox.StandardButton.Yes:
print("Yes!")
else:
print("No!")
现在不再需要 .exec() 来运行 dialog 了,直接使用,这些按钮也提供了不同的类型,一般来收默认的就够使用了,这里使使用多个按钮的情况
def button_clicked(self, s):
button = QMessageBox.critical(
self,
"Oh dear!",
"Something went very wrong.",
buttons=QMessageBox.StandardButton.Discard | QMessageBox.StandardButton.NoToAll | QMessageBox.StandardButton.Ignore,
defaultButton=QMessageBox.StandardButton.Discard,
)
if button == QMessageBox.StandardButton.Discard:
print("Discard!")
elif button == QMessageBox.StandardButton.NoToAll:
print("No to all!")
else:
print("Ignore!")
创建额外的窗口
之前使用的 dialog 是一种特殊的窗口,主要为了让用户关注该窗口的内容,它们运行自己的事件循环,并且会阻止应用其它部分的运行。
通常,额外窗口不会打断原程序的运行,可被用在输出一些图表、运行的日志内容等。
创建新窗口
在 Qt 中,一个没有父级的组件就是一个窗口,如果要显示一个窗口,只需要添加 QWidget 或 QMainWindow 的实例。
一般新窗口用作其它用途,不会像主窗口一样包含状态栏、工具栏,因此,不需要使用带有预设的 QMainWindow,直接使用 QWidget。另外创建一个一样的包含工具栏、状态栏的窗口还会使用户更加迷惑,如非必要,不要使用 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()
在这个函数(方法)中创建了一个 widget 对象,并把它存在名为 w 的变量中,它是一个本地变量,在该方法内被申明,一旦该方法运行结束,该变量被清理,此时就不能使用 w 变量了,所以窗口被立即关闭。因此需要加上 self 对象
def show_new_window(self, checked):
self.w = AnotherWindow()
self.w.show()
现在点击按钮,新窗口被创建并且可以使用。然而如果再次点击该按钮,可以发现窗口被重新创建。新的窗口会更新旧的 self.w 变量,之前窗口被销毁
现在把窗口的功能改为生成一个随机数
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()
现在点击按钮弹出新窗口后,把新窗口放在其它位置,再点击按钮,该按钮不再新建窗口。接着,如果关闭这个新窗口,再点击按钮,可以发现相同的窗口弹出。
这样的窗口用于临时使用,例如弹出窗口输出一幅图像。
切换窗口
和之前一样,把变量的引用值或变量删掉可以销毁一个窗口,修改一下之前的程序。
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.
如果把 else 部分 self.w 的值设为其它值,那么明显的,窗口不能再被打开,为确保窗口关闭,可以使用 .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()
Persistent windows
有时需要创建多个窗口,与其在使用时创建,不如在运行程序初始化时就创建好,然后使用 .show() 来显示。示例中 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()
Showing & hiding persistent windows
这里可以显示、隐藏而不用重新创建,被隐藏的窗口依旧存在,但不可见也不接收输入。然而可以修改窗口外观、内容,下次显示时所有内容会被更改。
使用 .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()
现在实现了点击按钮 显示或隐藏窗口,而且窗口只被创建了一次
多窗口
使用相同的原则可以创建多个窗口。即:保持一个对窗口的引用变量。最简单的方法是单独使用一个方法来控制显示或隐藏。
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()
实际上可以借助 Python 的 Lambda 表达式,作为中间人,然后创建一个通用方法来管理窗口的显示和隐藏
往常情况下,将 signal 和 slot 直接连接,现在使用一个中间函数,signal 将数据给中间函数,中间函数进行处理后,再传递给 slot
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()