Qt for Python:PySide6 入门指南(中篇)

Qt for Python:PySide6 入门指南(中篇)
Qt for Python:PySide6 入门指南(上篇)

本文详细介绍 Qt Widgets 开发。

一、基础示例

import sys from PySide6.QtWidgets import QApplication, QLabel app = QApplication(sys.argv) label = QLabel("Hello World!") label.show() app.exec()

运行效果:

在这里插入图片描述

对于使用 PySide6 的 Widget 应用程序,我们必须先从 PySide6.QtWidgets 模块中导入相应的类,导入完成后,需要创建一个 QApplication 实例。

由于 Qt 可以接收来自命令行的参数,我们可以将任何参数传递给 QApplication 对象,但通常不需要传递任何参数,因此可以保持默认,或者使用以下方法:

app = QApplication([])

在创建 QApplication 对象之后,我们创建了一个 QLabel 对象,QLabel 是一个可以显示文本(简单文本或富文本,如 HTML)或者图像的控件:

# This HTML approach will be valid too! label = QLabel("<font color=red size=40>Hello World!</font>")

创建 QLabel 对象之后,调用了它的 show() 方法。

最后,我们调用 app.exec() 进入 Qt 主循环并开始执行 Qt 代码。

在这里插入图片描述

二、使用 .ui 文件

在 python 代码中使用 pyside6-designer 设计好的 .ui 文件:

在这里插入图片描述


在这里插入图片描述

1、生成 python 类

与 UI 文件交互的标准方式是从中生成一个 Python 类,这得益于 pyside6-uic 工具,在控制台上运行以下命令:

pyside6-uic mainwindow.ui -o ui_mainwindow.py 
在这里插入图片描述
在这里插入图片描述

导入生成的文件ui_mainwindow.py

from ui_mainwindow import Ui_MainWindow 

示例代码如下:

import sys from PySide6.QtWidgets import QApplication, QMainWindow from PySide6.QtCore import QFile from ui_mainwindow import Ui_MainWindow classMainWindow(QMainWindow):def__init__(self):super(MainWindow, self).__init__() self.ui = Ui_MainWindow() self.ui.setupUi(self)if __name__ =="__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec())

运行效果:

在这里插入图片描述

新的基本类 MainWindow 只包含两行新代码,用于从 UI 文件中加载生成的 Python 类:

self.ui = Ui_MainWindow() self.ui.setupUi(self)

2、直接加载

要直接加载 UI 文件,我们需要从 QtUiTools 模块引入一个类:

from PySide6.QtUiTools import QUiLoader 

QUiLoader 让我们可以动态加载 .ui 文件并立即使用它:

ui_file = QFile("mainwindow.ui") ui_file.open(QFile.ReadOnly) loader = QUiLoader() window = loader.load(ui_file) window.show()

示例代码如下:

# File: main.pyimport sys from PySide6.QtUiTools import QUiLoader from PySide6.QtWidgets import QApplication from PySide6.QtCore import QFile, QIODevice if __name__ =="__main__": app = QApplication(sys.argv) ui_file_name ="mainwindow.ui" ui_file = QFile(ui_file_name)ifnot ui_file.open(QIODevice.ReadOnly):print(f"Cannot open {ui_file_name}: {ui_file.errorString()}") sys.exit(-1) loader = QUiLoader() window = loader.load(ui_file) ui_file.close()ifnot window:print(loader.errorString()) sys.exit(-1) window.show() sys.exit(app.exec())

运行效果:

在这里插入图片描述

三、控件样式

1、setStyleSheet 方法

Qt Widgets 会根据不同的操作系统使用默认主题。然而,我们也可以为每个控件自定义样式,例如:

import sys from PySide6.QtCore import Qt from PySide6.QtWidgets import QApplication, QLabel if __name__ =="__main__": app = QApplication() w = QLabel("This is a placeholder text") w.setAlignment(Qt.AlignCenter) w.show() sys.exit(app.exec())

执行这段代码后,我们会看到一个简单的 QLabel 位于中心,并带有占位文本:

在这里插入图片描述

我们可以使用类似 CSS 的语法来为它设置样式,例如 background-colorfont-family

import sys from PySide6.QtCore import Qt from PySide6.QtWidgets import QApplication, QLabel if __name__ =="__main__": app = QApplication() w = QLabel("This is a placeholder text") w.setAlignment(Qt.AlignCenter) w.setStyleSheet(""" background-color: #262626; color: #FFFFFF; font-family: Titillium; font-size: 18px; """) w.show() sys.exit(app.exec())
在这里插入图片描述

2、QSS 文件

更简单的替代方法是使用 Qt 样式表,它是一个或多个 .qss 文件,用于定义 UI 元素的样式。qss 文件与 CSS 文件非常相似,但我们需要指定 Widget 控件,并可选择性地指定对象的名称:

QLabel{background-color: red;}QLabel#title{font-size: 20px;}

以上代码中,第一个样式为所有 QLabel 对象定义了背景颜色,而后一个样式仅为标题对象设置样式。

定义好样式文件后,使用 QApplication.setStyleSheet(str) 函数来应用它:

if __name__ =="__main__": app = QApplication() w = Widget() w.show()withopen("style.qss","r")as f: _style = f.read() app.setStyleSheet(_style) sys.exit(app.exec())
在这里插入图片描述

拥有一个通用的 qss 文件可以将代码的样式部分解耦,而不必将其混杂在整体功能中,并且我们可以轻松地启用或禁用它。

来看这个新的示例,包含更多的控件:

classWidget(QWidget):def__init__(self, parent=None):super(Widget, self).__init__(parent) menu_widget = QListWidget()for i inrange(10): item = QListWidgetItem(f"Item {i}") item.setTextAlignment(Qt.AlignCenter) menu_widget.addItem(item) text_widget = QLabel(_placeholder) button = QPushButton("Something") content_layout = QVBoxLayout() content_layout.addWidget(text_widget) content_layout.addWidget(button) main_widget = QWidget() main_widget.setLayout(content_layout) layout = QHBoxLayout() layout.addWidget(menu_widget,1) layout.addWidget(main_widget,4) self.setLayout(layout)

这显示了一个两栏的控件,左边是一个 QListWidget,右边是一个 QLabel 和一个 QPushButton,代码运行效果如下:

在这里插入图片描述

style.qss 文件添加内容就可以修改它的外观效果:

QListWidget{color: #FFFFFF;background-color: #33373B;}QListWidget::item{height: 50px;}QListWidget::item:selected{background-color: #2ABf9E;}QLabel{background-color: #FFFFFF;qproperty-alignment: AlignCenter;}QPushButton{background-color: #2ABf9E;padding: 20px;font-size: 18px;}
在这里插入图片描述

四、信号与槽

1、示例

信号和槽是 Qt 的一个功能,允许图形控件与其他图形控件或 Python 代码进行通信。接下来我们创建一个 Qt 应用,它有一个按钮控件,每次点击它时,会将设定好的消息记录到 Python 控制台。

首先导入必要的 PySide6 类和 Python 的 sys 模块:

import sys from PySide6.QtWidgets import QApplication, QPushButton from PySide6.QtCore import Slot 

创建一个将消息记录到控制台的 Python 函数:

# Greetings@Slot()defsay_hello():print("Button clicked, Hello!")

现在,如前面的例子所示,我们必须创建 QApplication 来运行 PySide6 代码:

# Create the Qt Application app = QApplication(sys.argv)

创建一个可点击的按钮,它是一个 QPushButton 实例:

# Create a button button = QPushButton("Click me")

在显示按钮之前,我们必须将其连接到之前定义的 say_hello() 函数。有两种方法可以做到这一点:使用老式方法或更符合 Python 风格的新方法,在这里,我们使用新方法。

QPushButton 有一个预定义的信号叫做 clicked,每次按钮被点击时都会触发,我们把这个信号连接到 say_hello() 函数:

# Connect the button to the function button.clicked.connect(say_hello)

最后,我们显示按钮并启动 Qt 主循环:

# Show the button button.show()# Run the main Qt loop app.exec()

运行效果:

在这里插入图片描述

完整代码如下:

import sys from PySide6.QtWidgets import QApplication, QPushButton from PySide6.QtCore import Slot # Greetings@Slot()defsay_hello():print("Button clicked, Hello!")# Create the Qt Application app = QApplication(sys.argv)# Create a button button = QPushButton("Click me")# Connect the button to the function button.clicked.connect(say_hello)# Show the button button.show()# Run the main Qt loop app.exec()

2、通信机制

由于 Qt 的特性,QObjects 需要一种通信方式,这就是该机制成为 Qt 核心功能的原因。

可以把信号和槽理解为我们与家里灯光的互动方式:当我们控制灯的开关(信号)时,得到的结果可能是灯泡被打开或关闭(槽)。

在开发界面时,我们可以通过点击按钮的效果得到一个实际的例子:点击是信号,而槽则是按钮被点击时发生的操作,比如关闭窗口、保存文档等。

所有继承自 QObject 或其子类(如 QWidget)的类都可以包含信号和槽。一个对象在它状态发生变化时会发出信号,该变化可能对其他对象有意义的,它并不关心或知道是否有任何对象接收了它发出的信号。

槽可以用于接收信号,但它们也是普通的成员函数,就像一个对象不知道是否有接收它的信号的其他对象一样,槽也不知道是否有信号与它连接。

我们可以将任意数量的信号连接到一个槽上,同时一个信号也可以连接到多个槽上,甚至可以将一个信号直接连接到另一个信号。(这将使第二个信号在第一个信号发出时立即被触发。)

Qt 的控件有许多预定义的信号和槽,例如,QAbstractButton(Qt 中按钮的基类)有一个 clicked() 信号,QLineEdit(单行输入框)有一个名为 clear() 的槽。因此,可以通过在 QLineEdit 右侧放置一个 QToolButton,并将其 clicked() 信号连接到 clear() 槽来实现带有清除按钮的文本输入框,这是通过信号的 connect() 方法完成的:

button = QToolButton() line_edit = QLineEdit() button.clicked.connect(line_edit.clear)

connect() 返回一个 Connection 对象,可以使用 disconnect() 方法来断开连接。

除了连接到槽,信号也可以连接到自由函数:

import sys from PySide6.QtWidgets import QApplication, QPushButton deffunction():print("The 'function' has been called!") app = QApplication() button = QPushButton("Call function") button.clicked.connect(function) button.show() sys.exit(app.exec())

connect() 函数接受一个可选的 ConnectionType 参数,该参数指定与线程和事件循环相关的行为。

连接可以通过代码明确指定,或者对于控件表单,在 Qt Widgets Designer 的信号槽编辑器中设计。

3、Signal 类

在 Python 中编写类时,信号被声明为 Signal 类的类级变量。一个基于 QWidget 的按钮发出 clicked() 信号的示例如下所示:

from PySide6.QtCore import Qt, Signal from PySide6.QtWidgets import QWidget classButton(QWidget): clicked = Signal(Qt.MouseButton)...defmousePressEvent(self, event): self.clicked.emit(event.button())

Signal 的构造函数接受一个由 Python 类型和 C 类型组成的元组或列表:

signal1 = Signal(int)# Python types signal2 = Signal(QUrl)# Qt Types signal3 = Signal(int,str,int)# more than one type signal4 = Signal((float,),(QDate,))# optional types

除此之外,它还可以接收一个名为 name 的命名参数来定义信号名称,如果未传入任何值,新信号将与分配给它的变量同名:

# TODO signal5 = Signal(int, name='rangeChanged')# ... rangeChanged.emit(...)

Signal 的另一个有用选项是 arguments name,对于 QML 应用程序来说,可以通过名称引用发出的值,这非常有用:

# TODO sumResult = Signal(int, arguments=['sum'])# ... Connections { target:... function onSumResult(sum){// do something with'sum'}

4、Slot 类

QObject 派生类中的槽应该使用装饰器 @Slot 来标注,同样,要定义一个签名,只需像 Signal 类一样传递类型即可。

@Slot(str)defslot_function(self, s):...

Slot() 也接受 nameresult 关键字。result 关键字定义将返回的类型,可以是 C 类型或 Python 类型。name 关键字的行为与 Signal() 中的相同。如果 name 没有传值,则新槽的名称将与被装饰的函数名称相同。

建议对所有通过信号连接使用的方法添加 @Slot 装饰器。不添加会导致运行时开销,因为在创建连接时,该方法会被添加到 QMetaObject 中。这对于使用 QML 注册的 QObject 类尤其重要,缺少装饰器可能会引入 bug。

可以通过设置日志类别 qt.pyside.libpyside 的激活警告来诊断缺失的装饰器。例如,可以通过设置环境变量来实现:

exportQT_LOGGING_RULES="qt.pyside.libpyside.warning=true"

5、线程相关

在多线程应用程序中,信号可能由属于不同于接收者的线程的发送者发出。对于非 Slot 类型的接收者,代码将在发送者的线程上下文中执行。

然而,对于使用 @Slot 装饰的 QObject 派生类的方法,这通常有所不同,因为它们与特定线程相关,这取决于 connect() 方法的最后一个参数,该参数的类型为 PySide6.QtCore.Qt.ConnectionType

当传入 Qt.ConnectionType.AutoConnection(默认值)或 Qt.ConnectionType.QueuedConnection 时,接收者代码将在接收者对象的线程上下文中执行,这对于将后台线程的结果传递到需要使用主线程的 GUI 类非常有用。

6、重载信号和槽

实际上,可以使用相同名称的信号和槽,但参数类型列表不同,这是 Qt 5 的遗留特性,不推荐在新代码中使用。在 Qt 6 中,不同类型的信号具有不同的名称。

下面的示例使用两个处理程序来展示信号和槽的不同功能:

import sys from PySide6.QtWidgets import QApplication, QPushButton from PySide6.QtCore import QObject, Signal, Slot classCommunicate(QObject):# create two new signals on the fly: one will handle# int type, the other will handle strings speak = Signal((int,),(str,))def__init__(self, parent=None):super().__init__(parent) self.speak[int].connect(self.say_something) self.speak[str].connect(self.say_something)# define a new slot that receives a C 'int' or a 'str'# and has 'say_something' as its name@Slot(int)@Slot(str)defsay_something(self, arg):ifisinstance(arg,int):print("This is a number:", arg)elifisinstance(arg,str):print("This is a string:", arg)if __name__ =="__main__": app = QApplication(sys.argv) someone = Communicate()# emit 'speak' signal with different arguments.# we have to specify the str as int is the default someone.speak.emit(10) someone.speak[str].emit("Hello everybody!")

运行效果:

This is a number: 10 This is a string: Hello everybody! 
在这里插入图片描述

7、指定信号和槽

信号和槽也可以通过 SIGNAL() 和/或 SLOT() 函数传递的 C++ 方法签名字符串来指定:

from PySide6.QtCore import SIGNAL, SLOT button.connect(SIGNAL("clicked(Qt::MouseButton)"), action_handler, SLOT("action1(Qt::MouseButton)"))

通常不推荐这么做,仅在少数情况下需要,例如信号只能通过 QMetaObject 访问时(QAxObjectQAxWidgetQDBusInterfaceQWizardPage::registerField())。

wizard.registerField("text", line_edit,"text", SIGNAL("textChanged(QString)"))

在检查 QMetaObject 时,可以通过查询 QMetaMethod.methodSignature() 找到签名字符串:

mo = widget.metaObject()for m inrange(mo.methodOffset(), mo.methodCount()):print(mo.method(m).methodSignature())

注意,槽应使用 @Slot 进行装饰。

参考资料

  • https://doc.qt.io/qtforpython-6/tutorials/index.html
  • https://doc.qt.io/qt-5/stylesheet-reference.html

Read more

Git 远程操作全攻略:从基础到实战

Git 远程操作全攻略:从基础到实战

🌈 个人主页:Zfox_ 🔥 系列专栏:Git 企业级应用 目录 * 一:🔥 理解分布式版本控制系统 * 二:🔥 远程仓库 * 🦋 新建远程仓库 * 🦋 克隆远程仓库 * 🦋 向远程仓库推送 * 🦋 拉取远程仓库 * 三:🔥 配置Git * 🦋 忽略特殊⽂件 * 🦋 给命令配置别名 * 四:🔥 标签管理 * 🦋 理解标签 * 🦋 创建标签 * 🦋 操作标签 * 五:🔥 多⼈协作 * 🦋 多⼈协作⼀ * 🦋 多⼈协作⼆ * 🎀 远程分⽀删除后,本地gitbranch-a依然能看到的解决办法 * 六:🔥 共勉 一:🔥 理解分布式版本控制系统 🦈 我们⽬前所说的所有内容(⼯作区,暂存区,版本库 等等),都是在本地!也就是在你的笔记本或者计算机上。⽽我们的Git其实是分布式版本控制系统!什么意思呢? 可以简单理解为,我们每个⼈

By Ne0inhk

开源又实用!CAM++系统为何值得你立刻尝试

开源又实用!CAM++系统为何值得你立刻尝试 1. 这不是另一个语音识别工具,而是一个真正能落地的说话人验证方案 你有没有遇到过这样的场景:需要确认一段录音是不是某位同事说的?想快速判断客服通话中两个声音是否来自同一人?或者在安防系统里,需要从一段监控音频中验证说话人身份?市面上很多语音识别工具只告诉你“说了什么”,但CAM++解决的是更关键的问题——“谁说的”。 CAM++不是语音转文字(ASR),也不是语音合成(TTS),它专注一个被长期低估却极其重要的能力:说话人验证(Speaker Verification)。简单说,它不关心内容,只认声音本身。就像指纹或虹膜识别一样,它把人的声纹变成一串可计算、可比对的数字特征。 更难得的是,这个系统完全开源、开箱即用、中文优化、部署极简。不需要GPU服务器,一台普通开发机就能跑;不需要写代码,点点鼠标就能完成专业级声纹分析;不需要调参经验,预设阈值开箱即准。它不像学术模型那样只停留在论文里,也不像商业API那样藏着高昂费用和隐私风险——它就安静地运行在你的本地机器上,数据不出门,结果自己掌控。 如果你正在寻找一个真正能放进工作流

By Ne0inhk
【工创赛2025-智能物流搬运塔吊方案开源(2分15秒)】西安理工大学工程训练中心

【工创赛2025-智能物流搬运塔吊方案开源(2分15秒)】西安理工大学工程训练中心

一、前言        时光荏苒,岁月如梭。三年的本科竞赛生涯随着工训赛的结束告一段落。竞赛路途中,受到了诸多大佬的帮助和鼓励。为了将这份开源精神传递下去,本团队全体成员一致决定无偿开源本项目机械设计图纸、PCB设计、电控代码、视觉代码及镜像文件、参赛文档以及其他有关设计资料。        请注意,本项目开源文件完全免费,内容遵循CC 4.0 BY-NC-SA版权协议,转载请给出适当的署名,不可用作商业用途,严禁倒卖,若广大网友发现以上行为,请第一时间与我取得联系。        在此,由衷感谢西安理工大学工程训练中心的各位老师对我们竞赛项目的悉心指导与鼎力支持。         这里放一张二代小车同堂的照片作为纪念 二、关于开源项目        运行视频:[开源]2025工训赛智能物流搬运,初赛第八,2分26秒_哔哩哔哩_bilibili        本项目参与了2025年中国大学生工程实践与创新能力大赛全国总决赛,初赛成绩仅1个二环,其余均为一环,总时间2分26秒。决赛由于准备不足以及现场不可预料的因素,成绩不算理想,最后总成绩为全国特等奖。

By Ne0inhk