# import PyQt
:Python 的第三方库,用于调用 Qt 的 API ,从而制作 GUI 软件。
- 官方文档 (opens new window)
- 安装:
pip install PyQt6
,另外建议安装 Qt Designer 。
# Qt
Qt 是一个 GUI 工具包。
- 很多工具都可以开发 GUI 软件,而 Qt 擅长开发跨平台的 GUI 软件。
- 使用 Qt 开发一个 GUI 软件之后,在不修改源代码的情况下,可以为 Windows、Linux、Android、MacOS、iOS 等平台,分别构建一个可执行文件。
- 例如 Linux 常见的图形桌面 KDE 就是基于 Qt 开发的。
- Qt 本身是用 C++ 开发的,因此最初只能用 C++ 开发 GUI 软件。但后来也可以用 C#、Python 等编程语言,调用 Qt 的 API ,从而开发 GUI 软件。
- 很多工具都可以开发 GUI 软件,而 Qt 擅长开发跨平台的 GUI 软件。
-
- 首次发布于 1995 年。
- Qt 4.0
- 于 2005 年发布。
- Qt 4.7 添加了 Qt Quick 和 Qt QML 。
- Qt 5.0
- 于 2012 年发布。
- 进行了重构,拆分为几十个模块。因此不兼容 Qt4 。
- Qt 6.0
- 于 2020 年发布。
- 兼容 Qt5 。
Qt5 的常用模块:
- Qt Core
- :提供 signal、slot 等核心的 Qt 功能。
- Qt GUI
- :提供 QPainter、QColor 等基础的 GUI 功能。
- Qt Widgets
- :提供 Window、Frame、Label、Button 等多种类型的 widget 。
- Qt Creator
- :一个 IDE 。
- Qt Designer
- :一个可视化编辑器。用户可以用鼠标拖动 widget ,来编辑 GUI 界面。
- 编辑结果,通常保存为一个 .ui 文件,用于描述 GUI 界面,采用 XML 语法。比如显示哪些 widget 、每个 widget 的样式。
- Qt QML
- Qt 最初采用 XML 语法来描述 GUI 界面,但它不方便人类阅读。于是 Qt 发明了一种新的语言,称为 QML ,比 XML 更简洁、功能更多。
- Qt Quick
- :提供一些动态的、可触摸的 widget ,更适合手机 app 。
- 传统 Qt 开发模式,是用 Qt Designer 设计 GUI 界面,基于 Qt Widgets 显示各种 widget 。
- 新的 Qt 开发模式,是用 QML 设计 GUI 界面,基于 Qt Quick 显示各种 widget 。本文不介绍 Qt Quick 。
- qmake
- :可以为 Windows、Linux 等不同平台,分别生成一个 Makefile 。
- Qt Network
- :用于进行网络通信,支持 TCP、UDP、HTTP 等协议。
- Qt Multimedia
- :用于播放多媒体,比如图片、音频、视频。
- Qt Core
Qt 可以显示 Window、Frame、Label、Button 等多种类型的 GUI 元素,它们统称为控件(widget)。
- 大部分 widget 之间存在父子关系。例如一个 Frame 可能包含多个 Label ,担任它们的父容器(parent)。
- 当父控件被销毁时,它的所有子控件都会被自动销毁。
- 一个 GUI 软件可能显示多个窗口。通常将第一个显示的窗口,称为主窗口。主窗口通常包含很多子控件。
- 除了 Qt 自带的各种 widget ,用户也可以自定义 widget ,做成插件,然后便可以显示在 Qt Designer 左侧的 widget 列表中,也可以通过 "promoted widget" 功能导入。
# 启动
- 执行以下代码,即可显示一个 GUI 窗口:
from PyQt6.QtWidgets import QApplication, QWidget import sys app = QApplication(sys.argv) # 创建一个 QApplication 对象,代表一个 GUI 程序 window = QWidget() # 创建一个窗口,作为主窗口 window.show() # 显示窗口 sys.exit(app.exec()) # 进入 app 的主循环,阻塞当前线程。一旦 app 结束,就终止当前 Python 进程
- QApplication 采用单例模式。可以重复调用
QApplication.instance()
,获取对 app 的引用。 - 同一 GUI 程序中,只能存在一个主窗口。如果重复调用
QWidget()
创建一个主窗口,则会自动销毁之前的主窗口。
- QApplication 采用单例模式。可以重复调用
# QtCore
# QEvent
用户对 widget 进行的鼠标点击、键盘输入等操作,统称为事件(event)。
- Qt 自带了多种 widget ,每种 widget 支持多种 event 。
可以重载 widget 对某种 event 的处理方法。使得每次发生该 event 时,就执行特定的操作。
- 例:
class MyWindow(QWidget): def __init__(self): super().__init__() def keyPressEvent(self, event): print(event.key()) # 获取用户按下的按键键值,比如按键 A 的键值是 65(不受大小写的影响) def mouseMoveEvent(self, event): print(event.pos()) # 获取鼠标的相对坐标(以窗口左上角为原点,当鼠标移到窗口外部时,坐标可能为负数)
- 默认情况下,当用户按住鼠标并移动时,才会触发 mouseMoveEvent 事件。如果设置
self.setMouseTracking(True)
,则只要用户在窗口范围内移动鼠标,就会触发 mouseMoveEvent 事件。
- 默认情况下,当用户按住鼠标并移动时,才会触发 mouseMoveEvent 事件。如果设置
- 例:
# Signal
- widget 可以在发生某种 event 时,自动发送某种信号(signal),通知其它对象。
- 用户可以将 widget 的某种信号,绑定到一个函数,称为槽(slot)函数。使得每次发生该信号时,就调用一次槽函数,从而执行特定的操作。
- 每种 widget 自带了几种信号、槽函数。用户也可以增加新的信号、槽函数。
- 例:
- QPushButton 按钮提供了几种信号:
>>> button = QPushButton('Close', window) >>> button.show() >>> button.pressed # 用户在按钮区域,按下鼠标左键时,会触发该信号 <bound PYQT_SIGNAL pressed of QPushButton object at 0x000001DBBDEBCC10> >>> button.clicked # 用户在按钮区域,按下鼠标左键并释放时,会触发该信号 <bound PYQT_SIGNAL clicked of QPushButton object at 0x000001DBBDEBCC10>
- 调用信号的
connect()
方法,可以将该信号,绑定到一个函数。>>> button.clicked.connect(print) # 绑定一个槽函数 <PyQt6.QtCore.QMetaObject.Connection object at 0x000001DBBDEF4C10> >>> button.clicked.connect(window.close) # 每种信号,可以绑定多个槽函数 <PyQt6.QtCore.QMetaObject.Connection object at 0x000001DBBDEF4C80>
- 可以将一个信号,绑定到另一个信号。使得前一个信号发生时,自动触发后一个信号。
button2 = QPushButton('button2', window) button2.clicked.connect(button2.clicked)
- 调用信号的
disconnect()
方法,可以解绑一个槽函数。(如果不存在该绑定关系,则会抛出异常)>>> button.clicked.disconnect(window.close)
- 调用信号的
emit()
方法,可以主动触发一次该信号。>>> button.clicked.emit()
- QPushButton 按钮提供了几种信号:
- 例:自定义信号
>>> from PyQt6.QtCore import pyqtSignal >>> class MyWindow(QWidget): ... _signal = pyqtSignal(str) # 自定义一个信号,并声明其各个形参的数据类型 ... def __init__(self): ... super().__init__() ... self._signal.connect(self._slot) ... self._signal.emit('testing') ... def _slot(self, string): ... print(string) ... >>> window = MyWindow() testing
# QTime
- :用于获取时间。
- 例:
>>> from PyQt6.QtCore import QDateTime, QDate, QTime >>> QDateTime.currentDateTime() PyQt6.QtCore.QDateTime(2020, 1, 12, 10, 56, 40, 638) >>> QDate.currentDate() PyQt6.QtCore.QDate(2020, 1, 12) >>> QTime.currentTime() PyQt6.QtCore.QTime(10, 57, 14, 447) >>> _.second() 14
- 也可使用 Python 自带的 time、datetime 模块,获取时间。
# QTimer
- :定时器。用于在一段时间之后,执行特定操作。
- 定义:
from PyQt6.QtCore import QTimer QTimer(parent: QObject = None)
- 例:
>>> timer = QTimer(window) # 创建一个定时器 >>> timer.timeout.connect(lambda:print(1)) # 当定时器到达目标时刻时,会发出 timeout 信号。这里给 timeout 信号绑定一个槽函数 <PyQt6.QtCore.QMetaObject.Connection object at 0x000001F8DBC5DBA0> >>> timer.setInterval(1000) # 设置定时器的间隔时间,即多久发出一次 timeout 信号,单位为毫秒 >>> timer.start() # 启动定时器。定时器会一直运行,循环发出 timeout 信号 1 # 可以用timer.start(1000),在启动时设置间隔时间 1 1 >>> timer.stop() # 停止定时器。此后可以用 timer.start() 再次启动
>>> timer.isActive() # 定时器是否激活,正在运行 False >>> timer.remainingTime() # 距离 timeout 的剩余时间 -1 >>> timer.setSingleShot(True) # 采用 SingleShot 模式,让定时器发生一次 timeout 之后,自动 stop >>> timer.isSingleShot() # 判断是否采用 SingleShot 模式 True
# QtGui
# QPainter
- 用于绘制 2D 图像。
# QColor
- :用于选择颜色。
- 定义:
from PyQt6.QtGui import QColor QColor(int, int, int, alpha: int = 255) # 前三个参数,代表 RGB 三个通道的值,取值范围为 0~255 # alpha 参数,表示不透明度。最大为 255 ,表示完全不透明
- 例:
>>> color = QColor(0, 0, 255) # 选择颜色 >>> color.name() # 返回颜色的十六进制值 '#0000ff' >>> color.isValid() # 判断是否为有效的颜色值 True
- 可以单独读取 red、green、blue、alpha 通道,或者调用
setxxx()
进行赋值。>>> color.alpha() 255 >>> color.setAlpha(0)
# QFont
- :用于选择字体。
- 例:
>>> from PyQt6.QtGui import QFont >>> QFont('微软雅黑', 12) # 输入字体名称、字号 <PyQt6.QtGui.QFont object at 0x0000023BE20F9358>
# QIcon
- :用于显示图标。
from PyQt6.QtGui import QIcon icon = QIcon(r'./1.jpg') button = QPushButton('test', parent=window) button.setIcon(icon)
# QPixmap
- :用于显示图片。
- 例:
from PyQt6.QtGui import QPixmap pixmap = QPixmap(r'./1.jpg') label = QLabel(window) label.setPixmap(pixmap) # 用图片填充 label ,作为背景图 window.resize(pixmap.width(), pixmap.height())
# QtWidgets
# QWidget
:用于创建普通窗口。
QWidget是所有窗口的基类,是 QMainWindow、QDialog、QFrame 的父类。
定义:
from PyQt6.QtWidgets import QWidget window = QWidget(parent: QWidget= None) # 功能:创建一个窗口 # 如果不指定 parent 父控件,则它会成为主窗口
一般控件的通用方法:
window.show() -> None # 功能:显示该控件 # 父控件在第一次显示时会初始化布局,将它的各个子控件也显示出来 # 如果在父控件初始化之后,才加入子控件,则需要主动调用子控件的 show() 方法 window.hide() -> Bool # 功能:隐藏该控件的显示。但并没有销毁,还可以再次 show() window.close() -> Bool # 功能:关闭该控件。 # 如果成功关闭,或者该控件本来就没有显示,则返回 True # close() 与 hide() 都会让控件不再显示。但 close() 还可能销毁控件的对象、数据 window.setEnabled(bool) -> None # 功能:设置控件是否可以被用户操作,即接收用户的鼠标点击、键盘输入 # 这会影响到它的所有子控件 window.setToolTip(str) -> None # 功能:设置提示语 # 当鼠标悬停在一个控件上方时,会显示其提示语 # 可以设置字体,例如: QToolTip.setFont(QFont('微软雅黑', 12)) # 可以使用 HTML 语法,例如: window.setToolTip('This is a <font color="red">tip</font>.') window.setFocus() -> None # 功能:使控件得到屏幕焦点 window.isActiveWindow() -> bool # 功能:判断控件是否获得了屏幕焦点 window.isVisible() -> bool # 功能:判断控件是否正在显示 # 如果控件刚刚创建,尚未调用 show() ,则没有显示 # 如果控件调用了 close() ,则没有显示
QWidget 的特有方法:
window.setWindowTitle(str) -> None # 功能:设置窗口的标题 window.setWindowIcon(QIcon) -> None # 功能:设置窗口的图标 window.isMaximized() -> bool # 功能:判断窗口当前的显示,是否最大化 # 窗口最大化时,不一定是全屏。比如限制了窗口最大尺寸时,不能填满屏幕 window.isMaximized() -> bool # 功能:判断窗口当前的显示,是否最小化
# QMainWindow
QMainWindow 是 QWidget 的子类,增加了菜单栏、工具栏、状态栏,因此更擅长担任主窗口。
定义:
from PyQt6.QtWidgets import QMainWindow window = QMainWindow(parent: QWidget= None)
菜单栏,是在窗口的顶部,显示一行动作按钮。
- 菜单栏(menu bar)可以包含一组菜单(menu),每个菜单可以包含一组动作按钮。
- 创建菜单栏:
menubar = window.menuBar() # 第一次调用将创建菜单栏,重复调用将返回菜单栏这个单例对象的引用
- 往菜单栏中,添加菜单:
menubar.addMenu(str) -> QMenu # 输入名字,添加一个菜单 menubar.addMenu(QIcon, str) -> QMenu # 输入图标和名字,添加一个菜单 menubar.addMenu(QMenu) -> QMenu # 输入一个 QMenu 对象,添加一个菜单
- 可以在一个菜单中,嵌套另一个菜单,称为子菜单。例:
file_menu = menubar.addMenu('File') sub_menu = file_menu.addMenu('Open Recent File...')
- 每个菜单,可以包含一组动作按钮:
QMenu.addAction(str) -> QAction QMenu.addAction(QIcon, str) -> QAction QMenu.addAction(QAction) -> QAction
例:添加一个动作按钮,会被鼠标单击触发
from PyQt6.QtGui import QAction exit_action = QAction('Exit', window) exit_action.setShortcut('Ctrl+Q') # 设置快捷键 exit_action.setStatusTip('Exit the application.') # 设置显示在状态栏的提示 exit_action.triggered.connect(app.quit) # 绑定到一个槽函数。当用户点击该按钮时,就会调用槽函数 menubar = window.menuBar() file_menu = menubar.addMenu('File') # 创建菜单 file_menu.addAction(exit_action) # 添加动作
例:添加一个动作按钮,可以勾选
def debug_mode(state): # 触发该槽函数时,会传入一个 state 参数,表示当前按钮是否被勾选 if state: window.statusBar().showMessage('Debug mode is enabled') else: window.statusBar().showMessage('Debug mode is disabled') debug_action = QAction('Debug Mode', window, checkable=True) debug_action.setChecked(False) # 设置按钮的初始状态,是否被勾选 debug_action.triggered.connect(debug_mode) menubar = window.menuBar() fileMenu = menubar.addMenu('File') fileMenu.addAction(debug_action)
工具栏,是在菜单栏下方显示一行常用的动作按钮,方便用户点击。
window.exit_tool = window.addToolBar('Exit') window.exit_tool.addAction(exit_action)
状态栏,是在窗口的底部显示一行文字,供用户查看。
- 例:
statuebar = window.statusBar() # 功能:第一次调用将创建状态栏,重复调用将返回状态栏这个单例对象的引用 statuebar.showMessage(str, msecs: int = 0) # 功能:在状态栏显示一行字符串 # msecs 参数表示显示多少毫秒。默认为 0 ,表示永久显示
- 例:
上下文菜单,是单击鼠标右键会显示的一个菜单。
- 不止是 QMainWindow , QWidget 窗口都支持显示上下文菜单。
- 定义上下文菜单,需要重载 contextMenuEvent() 方法。如下:
from PyQt6.QtWidgets import QApplication, QMainWindow, QMenu import sys app = QApplication(sys.argv) class MyWindow(QMainWindow): def contextMenuEvent(self, event): # 创建一个菜单,以 self 作为父容器 contextMenu = QMenu(self) # 在菜单中,添加动作按钮 open_action = contextMenu.addAction('open') exit_action = contextMenu.addAction('exit') exit_action.triggered.connect(app.exit) # 在鼠标的当前坐标处,显示菜单 # 当用户点击任意动作按钮,就会关闭菜单的显示,并返回该按钮的引用 clicked_action = contextMenu.exec(self.mapToGlobal(event.pos())) # 如果没有绑定槽函数,也可通过以下代码,检查用户点击了哪个按钮,然后执行相应的操作 # if clicked_action == open_action: # pass # elif clicked_action == exit_action: # app.quit() window = MyWindow() window.show() sys.exit(app.exec())
# QLabel
- :标签,即一个只读的字符串。
- 定义:
from PyQt6.QtWidgets import QLabel QLabel(text: str, parent: QWidget = None)
- 例:
label = QLabel('hello', window) # 这里如果不指定 parent=window ,则该 label 不会显示在主窗口中 window.show()
# QLineEdit
- :单行输入框,用于输入一行字符串。
- 定义:
from PyQt6.QtWidgets import QLineEdit QLineEdit(parent: QWidget = None) QLineEdit(contents: str, parent: QWidget = None) # 如果输入 contents 参数,则会作为输入框的初始内容
- 例:
>>> lineEdit = QLineEdit(window) >>> lineEdit.show() >>> lineEdit.setText('hello') # 设置输入框的内容 >>> lineEdit.text() # 获取输入框的内容 'hello' >>> lineEdit.setMaxLength(10) # 限制输入内容的最大长度,超过则无法输入 >>> lineEdit.setReadOnly(True) # 使输入框变成只读。不能编辑,但可以选中、复制 >>> lineEdit.setEchoMode(QLineEdit.EchoMode.Password) # 使输入框的内容,显示成 * >>> lineEdit.clear() # 清空输入框
- 常用信号:
lineEdit.textChanged.connect(...) # 当输入框的内容改变时(不管是被用户改变,还是被程序改变) lineEdit.editingFinished.connect(...) # 当用户输入结束时(比如按下回车、焦点从输入框移动到其它控件) lineEdit.selectionChanged.connect(...) # 当用户的选中范围改变时(如果没有选中一段字符串,则不会触发)
# QTextEdit
:多行输入框,用于输入多行字符串。
定义:
from PyQt6.QtWidgets import QTextEdit QTextEdit(parent: QWidget = None) QTextEdit(text: str, parent: QWidget = None)
例:
>>> textEdit = QTextEdit(window) >>> textEdit.show() >>> textEdit.setPlainText('hello') # 在纯文本模式下,设置输入框的内容 >>> textEdit.toPlainText() # 在纯文本模式下,获取输入框的内容 'hello' >>> textEdit.clear() # 清空输入框
- 当输入的字符串行数过多时,会自动显示一个垂直滚动条。
QTextEdit 支持 HTML、Markdown 语法,从而显示富文本。
>>> textEdit.setHtml('<h1>标题一</h1>') # 在 HTML 模式下,设置输入框的内容 >>> textEdit.toHtml() # 在 HTML 模式下,获取输入框的内容 '<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.0//EN' ...' >>> textEdit.toPlainText() # 在屏幕上,会显示这段文本,而不是 HTML 源代码 '标题一'
>>> textEdit.setMarkdown('# 标题一') # 在 Markdown 模式下,设置输入框的内容 >>> textEdit.toMarkdown() # 在 Markdown 模式下,获取输入框的内容 '# 标题一\n\n' >>> textEdit.toPlainText() '标题一'
关于插入文本:
textEdit.append('hello') # 在末尾附加内容(这会新增一行)
from PyQt6.QtGui import QTextCursor textEdit.moveCursor(QTextCursor.Start) # 将光标移动到最前面 textEdit.moveCursor(QTextCursor.End) # 将光标移动到最后面 textEdit.insertPlainText('hello') # 在光标处插入纯文本 textEdit.insertHtml('<h1>标题一</h1>') # 在光标处插入 html
# 关于按钮
# QPushButton
:普通按钮。
定义:
from PyQt6.QtWidgets import QPushButton QPushButton(parent: QWidget = None) # 只指定父控件,则显示一个空白按钮 QPushButton(text: str, parent: QWidget = None) # 输入一个要显示的字符串,再指定父控件 QPushButton(icon: QIcon, text: str, parent: QWidget = None) # 输入图标、字符串、父控件
例:
>>> button = QPushButton('Quit', window) >>> button.text() # 返回按钮中的字符串 'Quit' >>> button.clicked.connect(app.quit) # 将按钮按下的信号,绑定到一个槽函数
# QCheckBox
:勾选按钮。有 "选中"、"未选中" 两种状态。
定义:
from PyQt6.QtWidgets import QCheckBox QCheckBox(parent: QWidget = None) QCheckBox(text: str, parent: QWidget = None)
例:
def changeState(self, state): if state == Qt.Checked: print('on') else: print('off') >>> checkBox = QCheckBox('debug', w) >>> checkBox.stateChanged.connect(changeState) # 将状态改变的信号,绑定到一个槽函数 >>> checkBox.isChecked() # 判断按钮是否被选中了 True >>> checkBox.setChecked(True) # 设置状态 >>> checkBox.toggle() # 切换一次状态
例:让普通按钮,保持在 "按下" 或 "未按下" 状态,像一个勾选按钮
def changeState(pressed): if pressed: print('on') else: print('off') button = QPushButton('debug', window) button.setCheckable(True) # 使普通按钮可以保持状态 button.clicked[bool].connect(changeState) # 绑定信号 button.toggle() # 切换一次状态
# QRadioButton
- :单选按钮。
# QComboBox
- :下拉列表。
- 定义:
from PyQt6.QtWidgets import QComboBox QComboBox(parent: QWidget = None)
- 例:
>>> combo = QComboBox(window) >>> combo.show() >>> combo.addItem('one') # 添加一个选项,并设置该选项显示的字符串 >>> combo.addItem('two') >>> combo.currentText() # 获取用户当前选中的选项 'one'
# QScrollBar
- :滚动条,用于拖动显示区域。
# QSlider
:滑块,用于调整某个数值。
定义:
from PyQt6.QtWidgets import QSlider QSlider(parent: QWidget = None) QSlider(orientation, parent: QWidget = None)
- Qt.Orientation 表示显示方向,有两种取值:
from PyQt6.QtCore import Qt Qt.Orientation.Horizontal # 垂直方向 Qt.Orientation.Vertical # 水平方向
- Qt.Orientation 表示显示方向,有两种取值:
例:
>>> slider = QSlider(window) >>> slider.show() >>> slider.setValue(100) # 设置数值,取值范围为 0~99 ,超出范围则自动赋值为最小值、最大值 >>> slider.value() # 获取数值 99
常用信号:
slider.valueChanged.connect(print) # 当滑块的数值改变时,会触发该信号,给槽函数传入当前的数值 slider.sliderPressed.connect(print) # 当用户单击滑块时(只能使用鼠标左键,而鼠标右键无效) slider.sliderReleased.connect(print) # 当用户松开鼠标左键时
# 关于对话框
- 对话框是一种小型窗口,通常只会短暂显示。
- QDialog 是 QtWidget 的子类,是各种对话框窗口的父类。
- 创建一个对话框时,会立即显示它,不会等到调用 show() 方法。
- 显示一个对话框时,会阻塞当前线程,直到对话框被关闭。
# QInputDialog
- 输入对话框,用于输入一行字符串。
- 例:
>>> from PyQt6.QtWidgets import QInputDialog >>> text, ret = QInputDialog.getText(window, 'Input Dialog', 'Please input your name:') >>> text, ret ('hello', True)
- 显示该对话框时,用户可以点击 OK 或 Cancel 按钮,或右上角的 Close 按钮。
- 如果用户点击了 OK 按钮,则返回的 ret 为 True ,并且 text 存储了用户输入的文本。
- 如果用户点击了 Cancel 或 Close 按钮,则返回的 ret 为 False ,并且 text 总是为空。
- 显示该对话框时,用户可以点击 OK 或 Cancel 按钮,或右上角的 Close 按钮。
# QColorDialog
- 颜色对话框,用于选择一个颜色。
- 例:
>>> from PyQt6.QtWidgets import QColorDialog >>> color = QColorDialog.getColor(parent=window, title='Select Color') >>> color <PyQt6.QtGui.QColor object at 0x000001486A345E40> >>> color.name() '#ffffff'
# QFileDialog
- :文件对话框,用于选择本机的文件或目录。
- 选择一个文件:
>>> from PyQt6.QtWidgets import QFileDialog >>> filename, _filter = QFileDialog.getOpenFileName(window, 'Open file', '.', '*.py') # 输入参数:父控件、对话框标题、打开的目录、筛选的文件名 >>> filename, _filter # 返回的 filename 和 _filter ,都是 str 型 ('D:/test/1.py', '*.py')
- 选择多个文件:
>>> filenames, _filter = QFileDialog.getOpenFileNames(window, 'Open files', '.', '*.py') >>> filenames, _filter # 返回的 filenames 是一个字符串列表 (['D:/test/1.py', 'D:/test/2.py'], '*.py')
- 选择文件的保存路径:
>>> QFileDialog.getSaveFileName(window, 'Save to', '.', '*.py') ('D:/test/3.py', '*.py')
- 选择一个目录:
>>> QFileDialog.getExistingDirectory(window, 'Open Directory', '.') 'D:/test'
# QMessageBox
:消息框,用于通知用户某个消息。
提问框,有 Yes、No 两个选项。
>>> from PyQt6.QtWidgets import QMessageBox >>> reply = QMessageBox.question(window, '标题', '内容') >>> reply == QMessageBox.StandardButton.Yes True >>> reply == QMessageBox.StandardButton.No # 如果用户点击 No 或 Close 按钮,则返回值为 No False
提示框,只有一个 OK 选项。
>>> reply = QMessageBox.information(window, '标题', '内容') >>> reply == QMessageBox.StandardButton.Ok # 不管用户点击 Ok 还是 Close 按钮,返回值都为 Ok True
警告框,只有一个 OK 选项。
>>> reply = QMessageBox.warning(w, '标题', '内容')
错误框:只有一个 OK 选项。
>>> reply = QMessageBox.critical(w, '标题', '内容')
# 关于时间
- 相关 widget :
QDateEdit(parent: QWidget = None) # 功能:显示单行输入框,只能输入年月日格式的字符串 QTimeEdit(parent: QWidget = None) # 功能:显示单行输入框,只能输入 24 小时格式的字符串 QDateTimeEdit(parent: QWidget = None) # 功能:显示单行输入框,只能输入年月日 + 24 小时格式的字符串 QCalendarWidget(parent: QWidget = None) # 功能:显示一个日历,用户通过鼠标单击,即可选择一个日期
# QProgressBar
- :进度条。
- 定义:
from PyQt6.QtWidgets import QProgressBar QProgressBar(parent: QWidget = None)
- 例:
progressBar = QProgressBar(window) progressBar.setValue(80) # 设置进度值。取值范围为 0~100 。如果输入为 float 类型,则小数部分会被舍去 # progressBar.value() # 返回当前的进度值 window.show()
# 样式
# 坐标
例:获取控件的坐标
>>> pos = window.pos() # 返回控件的坐标 >>> pos # 取值为一个 QPoint 对象 PyQt6.QtCore.QPoint(10, 10) >>> pos.x() # 横坐标 10 >>> pos.y() # 纵坐标 10
- 主窗口的坐标,是其左上角,相对于屏幕左上角,的相对坐标。
- 普通控件的坐标,是其左上角,相对于父控件左上角,的相对坐标。
- 主窗口可能移动到屏幕的显示区域之外,普通控件可能移动到父控件的显示区域之外,因此坐标可能为负数。
例:转换坐标系:
>>> window.mapToGlobal(QPoint(0, 0)) # 输入一个相对坐标(相当于当前控件),转换成全局坐标 PyQt6.QtCore.QPoint(10, 41) # 全局坐标的原点,并不是 (0,0) 开始,因为计算机屏幕存在边框 >>> window.mapFromGlobal(QPoint(0, 0)) # 输入一个全局坐标,转换成相对坐标(相当于当前控件) PyQt6.QtCore.QPoint(-10, -41)
例:移动控件到指定坐标
>>> window.move(10, 10) # 输入横坐标 x、纵坐标 y ,取值为 int 类型,单位为 pixel
>>> from PyQt6.QtCore import QPoint >>> window.move(QPoint(10, 10)) # 也可以输入一个 QPoint 对象
# 尺寸
例:获取控件的尺寸
>>> size = window.size() PyQt6.QtCore.QSize(200, 100) >>> size >>> size.width() 200 >>> size.height() 100
- 对于窗口类型的控件,height 不是指整个窗口的高度,而是指标题栏下方区域的高度。
例:修改控件的尺寸
>>> window.resize(400, 200) # 输入宽度 width 、高度 height ,取值为 int 类型,单位为 pixel
>>> from PyQt6.QtCore import QSize >>> window.resize(QSize(400, 200)) # 也可以输入一个 QSize 对象
- 对于窗口类型的控件,最小宽度 width 为 120 ,因为标题栏需要显示三个按钮。
- 控件的尺寸,不能设置为 0 或负数,否则控件会关闭显示。
- 控件的尺寸,不能超过屏幕的可用显示范围。即使用 resize() 设置了更大的尺寸也不会生效,即使用户用鼠标拖动窗口边框也不能变得更大。
其它方法:
# 根据控件需要显示的内容,自动调整尺寸 window.adjustSize() -> None # 设置最大尺寸 window.setMaximumHeight(int) -> None window.setMaximumWidth(int) -> None window.setMaximumSize(int, int) -> None window.setMaximumSize(QSize) -> None # 设置最小尺寸 window.setMinimumSize(int, int) -> None window.setMinimumSize(QSize) -> None window.setMinimumHeight(int) -> None window.setMinimumWidth(int) -> None # 设置固定尺寸。此时用户将鼠标移动到窗口边框处,不能调整尺寸。只能由程序来调整尺寸 window.setFixedSize(int, int) -> None window.setFixedSize(QSize) -> None window.setFixedHeight(int) -> None window.setFixedWidth(int) -> None
调用控件的
geometry()
方法,可以同时获取坐标、尺寸。>>> geometry = window.geometry() >>> geometry PyQt6.QtCore.QRect(0, 0, 400, 200) >>> geometry.x() 0 >>> geometry.y() 0 >>> geometry.width() 400 >>> geometry.height() 200
window.setGeometry(0, 0, 400, 200)
# Layout
布局(Layout),是指考虑一个窗口中,包含哪些 widget ,以及它们的坐标、尺寸。
Qt 提供了一些布局方法,用于自动调整 widget 的坐标、尺寸。
QHBoxLayout # 水平布局(Horizontal Layout),使多个控件在水平方向均匀排列 QVBoxLayout # 垂直布局(Vertical Layout),使多个控件在垂直方向均匀排列 QGridLayout # 网格布局,将显示空间分成多行多列,然后将控件放在某个格子内,或者占据多个格子 QFormLayout # 表格布局 QFrame # 框架,是一个透明的矩形框。通常给一个 QFrame 设置布局方法,然后容纳多个控件,方便统一管理
- 自动布局通常比手动布局更好,因为可以在窗口放大、缩小时,按比例缩放各个 widget 。
- 一个布局,可以容纳多个 widget 。
- 一个布局,可以作为一个元素,嵌套放入另一个布局中。
例:使用水平布局、垂直布局
from PyQt6.QtWidgets import QApplication, QWidget, QPushButton, QHBoxLayout, QVBoxLayout import sys app = QApplication(sys.argv) window = QWidget() window.show() hbox = QHBoxLayout() # 创建一个水平布局 hbox.addStretch(1) # 添加一个伸展因子。它像一个透明的弹簧,会自动挤占空白空间。取值越大,弹力越大 hbox.addWidget(QPushButton('one')) # 添加一个控件。这些控件会按添加的先后顺序排列 hbox.addWidget(QPushButton('two')) hbox.addStretch(1) # 两侧各有一个 stretch ,就会将按钮挤到中间 window.setLayout(hbox) # 让 window 采用 hbox 布局,此时会显示该布局的所有控件,此后不能更换 window 的布局 vbox = QVBoxLayout() # 创建一个垂直布局 vbox.addStretch(1) vbox.addWidget(QPushButton('three')) hbox.addLayout(vbox) # 添加一个嵌套的布局
- QMainWindow 预先划分了布局,在工具栏与状态栏之间,存在一个 central 区域。因此不能调用
setLayout()
方法,只能调用setCentralWidget()
方法,将一个控件放置到 central 区域。window = QWidget() window.setLayout(hbox) main_window = QMainWindow() main_window.setCentralWidget(window) window.show()
- 如果想从布局中删除一个控件,可以调用其
close()
方法。
- QMainWindow 预先划分了布局,在工具栏与状态栏之间,存在一个 central 区域。因此不能调用
例:使用网格布局
from PyQt6.QtWidgets import QApplication, QWidget, QPushButton, QGridLayout import sys app = QApplication(sys.argv) window = QWidget() window.show() grid = QGridLayout() window.setLayout(grid) # grid.setSpacing(10) # 设置每个格子的间距。而每个格子的尺寸,取决于此处的控件的尺寸 grid.addWidget(QPushButton('one'), 0, 0) # 添加一个控件,放在第 0 行、第 0 列 grid.addWidget(QPushButton('one'), 0, 1) grid.addWidget(QPushButton('two'), 1, 1, 2, 2) # 添加一个控件,占据从第 1 行、第 1 列,到第 2 行、第 2 列之间的格子
- QGridLayout 会将显示空间分成多行多列。具体有多少行、多少列,取决于用户添加了多少个控件。
- QGridLayout 会自动添加 stretch ,使得其中的控件,位置居中。而 QFormLayout 会使得其中的控件,位置趋于左上角。
例:使用网格布局,显示一个计算器
positions = [(i, j) for i in range(5) for j in range(4)] keys = [ ['Clear', 'Back', '', 'Close'], ['7', '8', '9', '/'], ['4', '5', '6', '*'], ['1', '2', '3', '-'], ['0', '.', '=', '+'], ] for row_num,row in enumerate(keys): for column_num,key in enumerate(row): if key == '': continue button = QPushButton(key) grid.addWidget(button, row_num, column_num)
# StyleSheet
- Qt 支持设置各个 widget 的样式(stylesheet),语法与 CSS 相似。
- 官方文档 (opens new window)
- 例:
app.setStyleSheet(""" QWidget{ # 作用于 QWidget 类的所有对象 background-image: url(img/1.jpg); } QWidget:window { # 作用于 QWidget 类,名为 window 的那个对象 background-color: rgb(255, 255, 0); color: rgb(255, 85, 0); } """)
- 子控件默认会继承父控件的 stylesheet 。
# 其它工具
# pyuic
- Qt Designer 生成的 .ui 文件,如何在 Python 中使用?
- 先用 pyuic 工具,将 .ui 文件,转换成 .py 文件。如下:
pyuic6 mainwindow.ui -o mainwindow_ui.py
- 然后在 mainwindow.py 中导入 mainwindow_ui.py :
import sys from PyQt6.QtWidgets import QApplication, QMainWindow from mainwindow_ui import Ui_MainWindow class MyWindow(QMainWindow, Ui_MainWindow): # 多继承 def __init__(self): super().__init__() self.setupUi(self) app = QApplication(sys.argv) window = MyWindow() window.show() sys.exit(app.exec())
- 先用 pyuic 工具,将 .ui 文件,转换成 .py 文件。如下:
# pyrcc
Qt Designer 中如何导入图片?
- 创建一个 Label ,然后修改其 pixmap 属性,用一张图片填充该 Label 。
- 不能直接使用磁盘中的图片文件。需要先创建一个 .qrc 文件,用于记录当前 GUI 软件使用的各个资源文件的磁盘路径,采用 XML 语法。例如:然后在 .ui 文件中,引用 .qrc 文件中的图片:
<RCC> <qresource prefix="resource"> <!-- 一个前缀,可以被多个资源文件共享 --> <file>img/1.jpg</file> <!-- 一个资源文件的磁盘路径(相对路径) --> </qresource> </RCC>
<pixmap resource="resource.qrc">:resource/img/1.jpg</pixmap>
.qrc 文件,如何在 Python 中使用?
- 可用 pyrcc 工具,将 .qrc 文件转换成 .py 文件。这会将各个图片的二进制内容,存储在 .py 文件中。
- 例如使用 PyQt5 时,执行:
pyrcc5 resource.qrc -o resource.py
- 使用 PyQt6 时,该工具改名为 pyside6-rcc ,需要安装
pip install PySide6
。
- 例如使用 PyQt5 时,执行:
- 然后可在 Python 代码中,引用 .qrc 文件中的图片:
import resource QPixmap(r':resource/img/1.jpg')
- 用 pyinstaller 打包时,需要指定各个 resource 文件的路径,从而加入打包:
pyinstaller mainwindow.py -w -i resource/img/1.jpg
- 可用 pyrcc 工具,将 .qrc 文件转换成 .py 文件。这会将各个图片的二进制内容,存储在 .py 文件中。