PySide6学习笔记

Qt for Python Quick start

PySide6 官方文档:https://doc.qt.io/qtforpython/index.html

安装:pip install pyside6

hello_world.py 示例程序

# -*- coding: utf-8 -*-
import sys
import random
from PySide6 import QtCore, QtWidgets, QtGui

class MyWidget(QtWidgets.QWidget):
    # extends QWidget and includes a QPushButton and QLabel
    def __init__(self):
        super().__init__()

        self.hello = ["Hallo Welt", "Hei maailma", "Hola Mundo", "Привет мир"," 你好世界 "]

        self.button = QtWidgets.QPushButton("Click me!")
        self.text = QtWidgets.QLabel("Hello World",
                                     alignment=QtCore.Qt.AlignCenter)

        self.layout = QtWidgets.QVBoxLayout(self)
        self.layout.addWidget(self.text)
        self.layout.addWidget(self.button)

        self.button.clicked.connect(self.magic)

    @QtCore.Slot()
    def magic(self):
        # 随机从列表中选择一个问候语
        self.text.setText(random.choice(self.hello))

if __name__ == "__main__":
    app = QtWidgets.QApplication([])

    # instantiate MyWidget and show it
    widget = MyWidget()
    widget.resize(800, 600)
    widget.show()

    sys.exit(app.exec())

错误提示:

>python hello_world.py
qt.qpa.plugin: Could not find the Qt platform plugin "windows" in ""
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fi
x this problem.

C:\Miniconda3\Lib\site-packages\PySide6\plugins\platforms 添加到环境变量 QT_QPA_PLATFORM_PLUGIN_PATH

基础知识

参考: https://blog.csdn.net/baidu_36499789/article/details/113835688

Qt, QML, Widgets 有什么区别?

Qt

作为一个框架,Qt 有许多组件。这些组件按组件和模块分布。例如,qtbase 组件是包含许多模块(QtCore, QtGui, QtWidgets, QtNetwork 等)的基本组件。所有这些模块都包含许多可以直接使用的类,比如你可以在 QtCore 模块里找到像 QFile, QTime, QByteArray 这些类。

通过这些类,你 可以创建没有用户界面的应用程序,并创建命令行应用程序、处理文件、网络连接、正则表达式、文本编码等。

另一方面,你 可以使用 QtWidgets 模块中的类创建图形应用程序,它也称为 Widgets

还有许多其他 Qt 模块,像 QtMultimedia, QtCharts, Qt3D 等,这些模块都具有特定的功能。在这些模块中,有一个叫做 QtDeclarative 的模块,你可以在其中找到 QML 声明语言的实现。

QML 语言类似于 CSS 和 JSON,它是为了能够声明性地设计 UI 应用程序而创造的。它同时还允许使用 JavaScript 处理某些命令部分,并允许使用其他组件将代码扩展和连接到 C ++。

Widgets(一种实现 GUI 的方法)

像前面所说,QtWidgets 模块提供预定义的 Widgets(小部件),你可以将这些 Widgets 添加到图形应用程序中,如按钮、标签、框、菜单等。

QML(另一种实现 GUI 的方法)

与 Widgets 相比,QML 提供了创建用户界面的替代方法。它最初是为了移动应用程序开发而设计的。与 Qt Quick 模块一起使用,它可以实现点击、拖放、动画、状态、过渡、抽屉菜单等用户与移动设备之间的交互。

QML/Quick 应用程序中的元素侧重于提供一个更动态的应用程序底层结构,这个底层结构提供了基于某些行为的不同属性。

尽管 QML 是为了移动设备设计的,但你也可以使用它创建桌面应用程序。

此外,你可以使用标准 JavaScript 来增强应用程序,把它与 C++ 结合可以成为很不错的底层结构。

IDE:QtCreator(并不是必须的!)

参考:https://doc.qt.io/qtcreator/

安装:https://www.qt.io/zh-cn/development-tools,下载最新版本 Qt Creator IDE 安装。

常见文件格式

Python 文件 .py

用户界面定义文件 .ui

如果你使用 Qt Designer,你可以在编辑器里可视化地创建交互界面。定义交互界面的文件会用 XML 语言表示为一个组件树。下面是 .ui 文件开头的样例:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>400</width>
    <height>300</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralWidget">
……

使用 pyside6-uic 工具可以将这些 .ui 文件转换为 Python 代码,让你可以在主函数中引用这些代码。所以在你部署的应用程序中包含 .ui 文件并不是必需的。

资源集合文件 .qrc

资源集合文件是一个伴随着你的应用程序的二进制文件列表。作为一个基于 XML 的文件,它的结构是这样的:

<!DOCTYPE RCC><RCC version="1.0">
<qresource>
    <file>images/quit.png</file>
    <file>font/myfont.ttf</file>
</qresource>
</RCC>

使用 pyside6-rcc 工具将这些 .qrc 文件转换为 Python 代码,这样你就不需要在你部署的应用程序中包含 .qrc 文件。

Qt 建模语言文件 .qml

用 QML 编写的图形应用程序与 Qt Widget 应用程序无关,所以 QML 项目通常是用 Python 文件来加载 QML 文件,或者可以将在 Python 中定义的元素引入到要使用的 QML。

你可以手动编写 .qml 文件,也可以使用嵌入在 Qt Creator 中的 QML Designer 等工具。此外,还有一些商业工具(比如 Qt Design Studio)可以让你加载来自其他设计软件的设计。

下面是 .qml 文件的示例代码。这段代码会显示一个浅灰色长方形,上面写着“Hello World!”。

import QtQuick 2.0

Rectangle {
    id: page
    width: 320;
    height: 480
    color: "lightgray"

    Text {
        id: helloText
        text: "Hello world!"
        y: 30
        anchors.horizontalCenter: page.horizontalCenter
        font.pointSize: 24;
        font.bold: true
    }
}

Qt Creator Python 项目文件 .pyproject

Qt Creator 加载和处理基于 Python 的项目,需要一个特殊的文件。因为基于 C++ 的项目可以用 .qmake 或者 CMakeLists.txt 文件来处理,但是 Python 项目不行。

该文件是基于 JSON 的文件,可以添加更多选项。下面是 .pyproject 文件的示例:

{"files": ["library/server.py", "library/client.py", "logger.py", ……]
}

Qt Widgets 基础教程

创建第一个 QtWidgets 程序

hello_world2.py

# -*- coding: utf-8 -*-
import sys
# 要用 PySide6 创建一个窗口程序,你必须先从 PySide6.QtWidgets 模块中引用需要使用的类。from PySide6.QtWidgets import QApplication, QLabel

# 引用后,创建一个 QApplication 实例。因为 Qt 可以从命令行接收参数,你可以向 QApplication 对象传递任意参数。一般情况下我们不需要传递参数,或者也可以像下面这样写
# app = QApplication([])
app = QApplication(sys.argv)

# 创建完 QApplication 对象后,我们还创建了 QLabel 对象。# QLabel 是一个可以显示文本和图像的容器。文本可以是简单文本,也可以是富文本,像 HTML 一样
label = QLabel("Hello World!")
label.show()
label2 = QLabel("<font color=red size=40>Hello World!</font>")
label2.show()

# 最后,我们调用 app.exec_() 进入主循环,开始执行代码。事实上,只有执行到了这,标签才被显示
sys.exit(app.exec())

创建一个简单按钮

信号和槽是 Qt 的一个特色,用来让你的图形组件与其他图形组件或 Python 代码交流。在例程中,我们将创建一个按钮:每当点击这个按钮,输出信息“Button clicked, Hello!”到 Python 控制台。

button.py

# -*- coding: utf-8 -*-
import sys
from PySide6.QtWidgets import QApplication, QPushButton
from PySide6.QtCore import Slot

# 创建一个函数,用来输出信息到控制台
# @Slot() 是一个装饰器,用来将一个函数定义为槽函数。@Slot()
def say_hello():
    print("Button clicked, Hello!")

app = QApplication(sys.argv)

# 创建一个 QPushButton 实例,它是一个可点击的按钮。在创建时传入的字符串会显示在按钮上
# Create a button
button = QPushButton("Click me")

# 在显示按钮之前,我们需要将按钮与之前定义的 say_hello() 函数连接。# QPushButton 预定义了一个叫 clicked 的信号,每当按钮被按下就会触发该信号。# 我们要把这个信号和 say_hello() 函数连接
button.clicked.connect(say_hello)

# 最后,我们显示这个按钮,并进入主循环
# Show the button
button.show()
# Run the main Qt loop
app.exec()

创建带输入框和按钮的对话框程序

dialog.py 展示如何用 QDialog 创建一个简单的对话框程序。这个程序可以让用户在 QLineEdit 组件中填入自己的名字,然后当点击 QPushButton 组件时程序会跟你打招呼。

# -*- coding: utf-8 -*-
"""
Created on Sat Nov 27 17:40:14 2021

@author: yanglei
"""

import sys
from PySide6.QtWidgets import (QLineEdit, QPushButton, QApplication,
    QVBoxLayout, QDialog)

# 创建一个对话框窗口
# 定义了一个 QDialog 的子类,并将它命名为 Form。class Form(QDialog):
    # 执行了 init() 方法,它可以调用父组件 QDialog 的初始化方法。def __init__(self, parent=None):
        super(Form, self).__init__(parent)
        self.setWindowTitle("My Form")

        # 创建两个组件:一个 QLineEdit,用来让用户填写他的名字;# 一个 QPushButton,用来输出 QLineEdit 中的内容
        self.edit = QLineEdit("Write my name here..")
        self.button = QPushButton("Show Greetings")

        # 创建布局并添加组件
        # Qt 中的布局支持可以帮助你排列程序中的组件。# 在这个例程中,我们用 QVBoxLayout 来对这两个组件进行垂直布局。layout = QVBoxLayout()
        layout.addWidget(self.edit)
        layout.addWidget(self.button)

        # 应用布局
        self.setLayout(layout)

        # 连接 greetings 槽和按钮单击信号
        self.button.clicked.connect(self.greetings)

    # 创建函数并连接按钮
    # 向用户打招呼
    def greetings(self):
        # 这个函数用来在控制台中输出 QLineEdit 中填入的内容。# 我们使用 QLineEdit.text() 方法获取文本。print(f"Hello {self.edit.text()}")

if __name__ == '__main__':
    # 创建 Qt 应用程序
    app = QApplication(sys.argv)
    # 创建并显示 Form
    form = Form()
    form.show()
    # 运行 Qt 主循环
    sys.exit(app.exec())

使用 QTableWidget 组件显示数据

如果你想要在表格里显示数据,可以直接使用 QTableWidget 组件来完成。本节教程将完成一个显示颜色列表的程序。

需要注意,使用 QTableWidget 不是在表格中显示信息的唯一方法。你也可以创建数据列表并用 QTableView 将其显示出来

table.py

# -*- coding: utf-8 -*-
"""
Created on Sat Nov 27 17:53:22 2021

@author: yanglei
"""

import sys
from PySide6.QtGui import QColor
from PySide6.QtWidgets import (QApplication, QTableWidget, QTableWidgetItem)

# 创建列表来存储各种颜色对应的八进制码
colors = [("Red", "#FF0000"),
          ("Green", "#00FF00"),
          ("Blue", "#0000FF"),
          ("Black", "#000000"),
          ("White", "#FFFFFF"),
          ("Electric Green", "#41CD52"),
          ("Dark Blue", "#222840"),
          ("Yellow", "#F9E56d")]

# 定义用来将八进制码转换为 RGB 值的函数
def get_rgb_from_hex(code):
    code_hex = code.replace("#", "")
    rgb = tuple(int(code_hex[i:i+2], 16) for i in (0, 2, 4))
    return QColor.fromRgb(rgb[0], rgb[1], rgb[2])

app = QApplication()

# 创建表格,设置行数、列数、表头
table = QTableWidget()
table.setRowCount(len(colors))
table.setColumnCount(3)
# 使用 setHorizontalHeaderLabels() 方法设置列名
table.setHorizontalHeaderLabels(["Name", "Hex Code", "Color"])

# 循环列表,创建 QTableWidgetItems 对象,并按照 x, y 坐标将其加入表格
for i, (name, code) in enumerate(colors):
    item_name = QTableWidgetItem(name)
    item_code = QTableWidgetItem(code)
    item_color = QTableWidgetItem()
    item_color.setBackground(get_rgb_from_hex(code))
    table.setItem(i, 0, item_name)
    table.setItem(i, 1, item_code)
    table.setItem(i, 2, item_color)

table.show()
sys.exit(app.exec())

使用 QUiLoader 和 pyside6-uic 导入 ui 文件

用 Qt Designer 来创建一个基于 Qt Widgets 的图形界面。Qt Designer 是一个图形化的 UI 设计工具,可以在 pyside6 的目录下找到它 (C:\Miniconda3\Lib\site-packages\PySide6\designer.exe),同时在 Qt Creator IDE 里也可以找到它。Qt Designer 的使用在官网的 Using Qt Designer 教程里详细介绍。

可以用 pyside6-designer 命令启动 Qt Designer。

Qt Designer 设计的界面被保存为 .ui 文件,这个文件使用的是基于 XML 的格式。在编译项目时可以使用 pyside6-uic 将其转换为 Python 或 C++ 代码。

要在 Qt Designer 里创建一个新的窗口,点击 文件 - 新建……,选择“Main Window”作为模板。将其保存为 mainwindow.ui。在窗口的中央加入一个 QPushButton 组件。

可以使用 QtUiTools 模块里的 QUiLoader 类来直接加载 UI 文件

mainwindow.py

# -*- coding: utf-8 -*-
import sys
from PySide6.QtUiTools import QUiLoader
from PySide6.QtWidgets import QApplication
from PySide6.QtCore import QFile, QIODevice


if __name__ == "__main__":
    app = QApplication(sys.argv)

    # QUiLoader 类可以让你动态地载入 UI 文件
    ui_file_name = "mainwindow.ui"
    ui_file = QFile(ui_file_name)
    if not 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()
    if not window:
        print(loader.errorString())
        sys.exit(-1)
    window.show()

    sys.exit(app.exec())

使用 UI 文件的标准方式是将其转换为一个 Python 的类,这可以用 pyside6-uic 实现。在命令行里运行该指令:

pyside6-uic mainwindow.ui > ui_mainwindow.py

会生成文件 ui_mainwindow.py, 定义了界面类 Ui_MainWindow

mainwindow.py 引用界面类的方式:

# -*- coding: utf-8 -*-
import sys
from PySide6.QtWidgets import QApplication, QMainWindow
from ui_mainwindow import Ui_MainWindow

class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        # 类里只包含了两句用来从 UI 文件中加载 Ui_MainWindow 的代码
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

if __name__ == "__main__":
    app = QApplication(sys.argv)

    window = MainWindow()
    window.show()

    sys.exit(app.exec())

在 spyer 内部批量将 ui 文件生成 py 文件:

在 ui 文件所在目录编写 ui2py.py 脚本并运行

ui2py.py

# -*- coding: utf-8 -*-
import os

# 遍历当前目录,将 Qt Designer 生成的 ui 文件转换为 python 文件,前缀为 ui_

for dir_path, dir_names, file_names in os.walk(os.getcwd()):
    for file_name in file_names:
        if file_name[-3:] == '.ui':
            file_path = os.path.join(dir_path, file_name)
            py_file_name = 'ui_' + file_name[:-3] +'.py'
            cmd = 'pyside6-uic' + file_name + '>' + py_file_name
            os.system(cmd)

spyder 中的报错:Please destroy the QApplication singleton before creating a new QApplication instance.

第二次运行 Pyside6 app 时报错:

RuntimeError: Please destroy the QApplication singleton before creating a new QApplication instance.

app = QApplication([]) 替换为:

    app = QApplication.instance()
    if app == None:
        app = QApplication([])

参考:https://forum.qt.io/topic/110418/how-to-destroy-a-singleton-and-then-create-a-new-one/12

用 Nuitka 打包

参考:https://github.com/Nuitka/Nuitka

安装:conda install nuitka

安装 MinGW64,并将 C:\mingw64\bin 加到系统 path 环境变量

打包:

python -m nuitka --mingw64 --windows-disable-console --standalone  --output-dir=out --show-progress --show-memory  --plugin-enable=pyside6  --windows-icon-from-ico=favicon.ico mainwindow.py

参数:

部分常用命令

--mingw64 #默认为已经安装的 vs2017 去编译,否则就按指定的比如 mingw(官方建议)
--standalone 独立环境,这是必须的(否则拷给别人无法使用)
--windows-disable-console 没有 CMD 控制窗口
--output-dir=out 生成 exe 到 out 文件夹下面去
--show-progress 显示编译的进度,很直观
--show-memory 显示内存的占用
--include-qt-plugins=sensible,styles 打包后 PyQt 的样式就不会变了
--plugin-enable=qt-plugins 需要加载的 PyQt 插件
--plugin-enable=tk-inter 打包 tkinter 模块的刚需
--plugin-enable=numpy 打包 numpy,pandas,matplotlib 模块的刚需
--plugin-enable=torch 打包 pytorch 的刚需
--plugin-enable=tensorflow 打包 tensorflow 的刚需
--windows-icon-from-ico= 你的。ico 软件的图标
--windows-company-name=Windows 下软件公司信息
--windows-product-name=Windows 下软件名称
--windows-file-version=Windows 下软件的信息
--windows-product-version=Windows 下软件的产品信息
--windows-file-description=Windows 下软件的作用描述
--windows-uac-admin=Windows 下用户可以使用管理员权限来安装
--linux-onefile-icon=Linux 下的图标位置
--onefile 像 pyinstaller 一样打包成单个 exe 文件
--include-package= 复制比如 numpy,PyQt5 这些带文件夹的叫包或者轮子
--include-module= 复制比如 when.py 这些以。py 结尾的叫模块

说明:使用 onefile 参数感觉是使用了 UPX 类似的技术,程序打开变慢,应该在解压缩,这时候没必要再用 UPX 压缩 exe 了,没啥用。所以 不建议用 onefile 参数,可以用 zip 压缩文件夹。

关于 Nuitka

Nuitka 虽然会将 Python 代码转为 C ++,但会保留函数名等定义,从而使得逆向工程变得容易,参考这里:Does Nuitka Remove Or Obscure Names of Variables, Functions, Classes, Etc. From Your Python Code?

如果要保护你的代码,可以考虑先混淆再用 Nuitka 编译成 exe

评论(没有评论)