Python 桌面端 : 30分钟桌面端入门 + 分享我的开箱即用脚手架

8,115 阅读7分钟

一. 前言

这段时间一直在尝试做一个 Python 桌面端的项目 ,到了现在基本上已经可以发布了 。

陆陆续续折腾了1-2个月了 ,算是把 Python 桌面应用的点都体验到了,这里整理出来 ,用于后续的其它尝试。

二. 环境准备

2.1 搭建 Python 开发环境

  • 简易版的就是本地安装 Python ,然后在代码工具里面使用即可
  • 但是如果是多版本多应用的开发 ,最好使用虚拟环境

这里主要记录虚拟环境的用法 ,Python 虚拟开发环境是一种隔离的环境 ,允许开发者 为每个项目独立安装特定的 Python 版本和依赖包,避免全局依赖冲突和版本问题。

虚拟环境一般常见有几种 : Python 内置 venv / virtualenv / pipenv / conda.

conda 虚拟环境的安装使用

  • S1 : 首先进入 Download Now | Anaconda 下载工具
  • S2 : 正常流程安装 Anaconda (安装完成界面最后两项不用选)
  • S3 : 配置环境变量 ,主要有四个 (当前安装路径下)
    • D:[当前安装路径下]\anaconda
    • D:[当前安装路径下]\anaconda\Scripts
    • D:[当前安装路径下]\anaconda\Library\mingw-w64\bin
    • D:[当前安装路径下]\anaconda\Library\bin
  • S4 : conda配置python环境
    • 查询安装结果 : conda --version
    • 查询详情 :conda info
    • 创建环境 : conda create --name simple python=3.11.9
    • 查看环境 : conda info --envs
    • 激活环境 : conda activate simple
    • 查询当前依赖 : conda list
    • 退出环境 : conda deactivate
    • 删除所有依赖 : conda remove -n simple --all
  • S5 : Python 应用使用 虚拟环境
    • (其他的工具怎么用可以百度 ,这里主要看 VSCode
    • VSCode 中 CTRL+ P 👉 select interpreter
    • 选择对应的环境即可

image.png

三. 功能组件

3.1 安装组件

项目环境准备好了后 ,就要开始安装组件了 ,我个人项目中主要使用了如下组件 :

  • python 桌面应用组件套 :
    • PyQt6 :Python 里面很好用的桌面库 ,支持 GPLv3 开源协议 ,支持商业许可
    • PyQt6-WebEngine : 用于在桌面组件里面添加网页
    • PyQt6-Frameless-Window : 第三方库,提供无边框窗口的实现
    • PyQt6-Fluent-Widgets : 基于微软 Fluent Design 设计语言的 PyQt6 界面组件库
    • PyQt6-tools : 用于构建项目 (最大支持 Python 3.11
  • DAS 层相关套装 :
    • peewee : 轻量级的 Python ORM(对象关系映射)库
    • dbutils : 一组工具模块,用于处理与数据库相关的常见任务 ,连接池等
    • SQLite : 一个轻量级的嵌入式关系型数据库管理系统 , Python 自带
  • 其他组件 :
    • Tushare : 量化工具,基本上常见的股票接口都有,部分接口免费

    • cachetools : 缓存工具

    • matplotlib : 2D 数据可视化库,用于创建图表、图形和绘图

    • pystand : 打包组件

// 先进入 Conda 环境进行操作
conda init 
conda activate antMonitor

// 出现如下说明环境进入成功 : (可能需要先推出 Terminal)
(antMonitor) PS D:\code\python\ant-monitor> 


// 检测一下当前依赖 : 
conda list

// 安装依赖 (这里可能存在版本依赖问题 ,所以为了更好的效果 ,我指定了版本)
pip install PyQt6==6.7.1
pip install PyQt6-Qt6==6.7.2
pip install PyQt6-WebEngine==6.7.0
pip install PyQt6-WebEngine-Qt6==6.7.3
pip install PyQt6-Frameless-Window==0.4.3
pip install PyQt6-Fluent-Widgets -i https://pypi.org/simple/
pip install matplotlib
pip install peewee
pip install dbutils
pip install tushare
pip install schedule
pip install cachetools
pip install pystand


// 如果存在依赖冲突 ,可以删除依赖 
pip uninstall PyQt6
pip uninstall pyqt6-qt6
pip uninstall PyQt6-WebEngine
pip install --force-reinstall pyqt6==6.7.1

- 查询当前的依赖版本 : pip list

3.2 PyQT 配置文件准备

如果多 PyQT 的各项功能不清楚的 ,可以先看看这篇 :

静态文件配置 - resources.qrc:

<RCC>
    <qresource prefix="/images">
        <file>views/common/images/logo.png</file>
    </qresource>

    <qresource prefix="/page">
        <file>views/qss/dark/gallery_interface.qss</file>
        <file>views/qss/dark/home_interface.qss</file>
        <file>views/qss/dark/icon_interface.qss</file>
        <file>views/qss/dark/link_card.qss</file>
        <file>views/qss/dark/sample_card.qss</file>
        <file>views/qss/dark/setting_interface.qss</file>
        <file>views/qss/dark/view_interface.qss</file>
        <file>views/qss/dark/navigation_view_interface.qss</file>

        <file>views/qss/light/gallery_interface.qss</file>
        <file>views/qss/light/home_interface.qss</file>
        <file>views/qss/light/icon_interface.qss</file>
        <file>views/qss/light/link_card.qss</file>
        <file>views/qss/light/sample_card.qss</file>
        <file>views/qss/light/setting_interface.qss</file>
        <file>views/qss/light/view_interface.qss</file>
        <file>views/qss/light/navigation_view_interface.qss</file>
    </qresource>
</RCC>

  • 这里进行一次手动编译 ,由于 PyQT6 本身没有编译工具 ,这里需要手动改下(把下面的地方改成 PyQT6 即可)
# 手动生成 resources_rc 文件 
rcc -g python -o resources_rc.py resources.qrc

image.png

四. 代码流程

4.1 基础组件

  • QFluentWidgets : 基于 C++ Qt/PyQt/PySide 的 Fluent Design 风格组件库
  • PyQT6 : Python 桌面端组件

关联文章 :

PyQT 界面布局 ,常用的布局技巧都在这里了

PyQT 的美化 : 人们总是会被好看的事物所吸引

PyQt : 图表也能这么秀 ,无缝集成 ECharts

4.2 项目结构


├─common : 公共模块
│  ├───enum : 业务中使用的枚举
│  └───utils
├─config
├─logs
├─service  : service 层逻辑
│  ├───das :数据交互层
├─views
│  ├───common
│  │      ├──html  : WebEngine 中使用的页面
│  │      └──images : 业务中使用的图片
│  ├───qss
│  │      ├──dark : 暗色主题
│  │      └──light : 白色主题

  • 个人主业是 Java ,所以 构建的项目难免带有一些 Java 的风格

4.3 代码细节

主入口 : main.py + app.py

from qfluentwidgets import (
    NavigationItemPosition,
    FluentWindow,
    SubtitleLabel,
    setFont,
    SplashScreen,
    SystemThemeListener,
)
from qfluentwidgets import FluentIcon as FIF
from PyQt6.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QFrame,
)
from PyQt6.QtGui import QIcon
from PyQt6.QtCore import QSize

from views.common_setting_view import CommonSettingView
from views.demo_chat_view import DemoChatView
from views.base_gallery_view import GalleryInterface

from config.log_config import AntLogger
from views.common.signal_bus import signalBus
from views.common.translator import Translator
from views.common.config import ZH_SUPPORT_URL, EN_SUPPORT_URL, cfg
from views.demo_common_view import DemoCommonView


class Widget(QFrame):
    def __init__(self, text: str, parent=None):
        super().__init__(parent=parent)
        # 创建一个带副标题样式的标签,显示传入的文本
        self.label = SubtitleLabel(text, self)
        self.hBoxLayout = QHBoxLayout(self)  # 使用水平布局管理器

        # 设置标签字体大小
        setFont(self.label, 24)
        # 将标签添加到水平布局,并设置伸缩因子为 1
        self.hBoxLayout.addWidget(self.label, 1)

        # 必须给子界面设置全局唯一的对象名,以便后续查找
        self.setObjectName(text.replace(" ", "-"))


class MainWindow(FluentWindow):
    """
    主窗口类,继承自 FluentWindow。
    实现了应用的主界面,包括导航栏、多个子页面以及主题监听器等功能。
    """

    def __init__(self):
        super().__init__()
        self.initWindow()  # 初始化窗口属性

        # 创建系统主题监听器,用于检测和响应主题变化
        self.themeListener = SystemThemeListener(self)

        # 初始化各个子页面
        """
        核心修改点一 : 这里添加你的页面
        """
        self.homeView = DemoCommonView(self)
        self.chatView = DemoChatView(self)
        self.settingView = CommonSettingView(self)

        # 启用亚克力效果
        self.navigationInterface.setAcrylicEnabled(True)
        self.connectSignalToSlot()  # 连接信号与槽

        # 初始化导航栏及其内容
        self.initNavigation()

        # 结束启动画面
        self.splashScreen.finish()

        # 开始监听系统主题变化
        self.themeListener.start()

    def initNavigation(self):
        """初始化导航栏"""
        pos = NavigationItemPosition.SCROLL  # 设置导航项位置为可滚动区域

        """
        核心修改点二 : 把页面添加到导航栏
        """
        # 添加首页视图
        self.addSubInterface(self.homeView, FIF.SPEED_HIGH, "首页", pos)
        # 添加聊天演示视图
        self.addSubInterface(self.chatView, FIF.CAFE, "表格原理Demo", pos)
        # 在导航栏中添加分隔符
        self.navigationInterface.addSeparator()
        # 添加设置视图
        self.addSubInterface(
            self.settingView,
            FIF.SETTING,
            self.tr("Settings"),
            NavigationItemPosition.BOTTOM,  # 设置为固定在导航栏底部
        )

    def initWindow(self):
        """初始化窗口属性"""
        self.resize(1200, 1000)  # 设置窗口大小
        self.setMinimumWidth(1000)  # 设置窗口最小宽度
        self.setWindowIcon(
            QIcon(":/images/views/common/images/logo.png")
        )  # 设置窗口图标
        self.setWindowTitle("Demo-桌面端小程序基础框架")  # 设置窗口标题

        # 根据配置启用 Mica 效果(Windows 专属的模糊效果)
        self.setMicaEffectEnabled(cfg.get(cfg.micaEnabled))

        # 创建启动画面
        self.splashScreen = SplashScreen(self.windowIcon(), self)
        self.splashScreen.setIconSize(QSize(106, 106))  # 设置启动画面图标大小
        self.splashScreen.raise_()  # 将启动画面置于最前

        # 窗口居中显示
        desktop = QApplication.screens()[0].availableGeometry()
        w, h = desktop.width(), desktop.height()
        self.move(w // 2 - self.width() // 2, h // 2 - self.height() // 2)
        self.show()  # 显示窗口
        QApplication.processEvents()  # 强制刷新界面

    def resizeEvent(self, e):
        """处理窗口大小变化事件"""
        super().resizeEvent(e)
        if hasattr(self, "splashScreen"):  # 如果启动画面存在,则调整其大小
            self.splashScreen.resize(self.size())

    def closeEvent(self, e):
        """处理窗口关闭事件"""
        self.themeListener.terminate()  # 停止主题监听器
        self.themeListener.deleteLater()  # 删除监听器对象
        super().closeEvent(e)

    def connectSignalToSlot(self):
        """连接信号与槽"""
        # 连接信号:启用/禁用 Mica 效果
        signalBus.micaEnableChanged.connect(self.setMicaEffectEnabled)
        # 连接信号:切换到示例卡片
        signalBus.switchToSampleCard.connect(self.switchToSample)

    def switchToSample(self, routeKey, index, data):
        """
        切换到示例界面,并滚动到指定的卡片。

        参数:
        - routeKey: 子界面的唯一标识符(用于查找目标界面)
        - index: 要滚动到的卡片索引
        - data: 需要传递给目标界面的附加数据
        """
        # 找到当前窗口中所有基于 GalleryInterface 的子界面
        interfaces = self.findChildren(GalleryInterface)
        for w in interfaces:
            if w.objectName() == routeKey:  # 检查对象名是否匹配
                w.setExchangeData(data)  # 设置需要传递的数据
                self.stackedWidget.setCurrentWidget(w, False)  # 切换到目标界面
                w.scrollToCard(index)  # 滚动到指定索引的卡片


核心类 :抽象框架类 : template_page_view.py

这个类是整个脚手架中最核心的类,通过这个类可以快速的构建各种内部组件:

  • 使用方式如下 :
# 添加一个 Table
self.addTableTemplate("当前股票", self.getStockTableInfo, self.getBaseParam, 1)

# 添加一个 EChat
self.addEchatTemplate(
    "大盘风险", "demo_chat", self.exchangeMainStockRisk, self.getBaseParam, 1
)

image.png

五.结果展示

动画.gif

六. 项目地址

gitee.com/antblack/an…

  • 注意是 Demo-001-SimpleFramework 分支哦!!!

总结

这段时间总算是把这个搞完了 ,这一套弄会了, 后面再写类似的基本上就不需要单独写什么了。

  • 代码里面很多都是 GPT 帮忙写的 ,所以质量还在优化中,仅供学习和快速上手。

  • 另外个人开源的组件也在最后测试阶段了, 欢迎关注 ❗❗

  • 打包使用的是 PyStand ,东西比较多 ,准备单独放一篇,过两天就发!!

最后的最后 ❤️❤️❤️👇👇👇