PyQt5桌面应用开发(14):数据库+ModelView+QCharts

news2025/1/21 4:56:39

本文目录

  • PyQt5桌面应用系列
  • 下一个玩具
  • 报表一:Markdown文档的列表显示
    • Widget的树、表、列报表
    • Qt中的MVC框架
      • 模型
      • 视图
      • 代理
  • 报表二:Markdown文档的长度图形
  • 数据:Markdown文档和Sqlite数据库
  • 代码
  • 总结

PyQt5桌面应用系列

  • PyQt5桌面应用开发(1):需求分析
  • PyQt5桌面应用开发(2):事件循环
  • PyQt5桌面应用开发(3):并行设计
  • PyQt5桌面应用开发(4):界面设计
  • PyQt5桌面应用开发(5):对话框
  • PyQt5桌面应用开发(6):文件对话框
  • PyQt5桌面应用开发(7):文本编辑+语法高亮与行号
  • PyQt5桌面应用开发(8):从QInputDialog转进到函数参数传递
  • PyQt5桌面应用开发(9):经典布局QMainWindow
  • PyQt5桌面应用开发(10):界面布局基本支持
  • PyQt5桌面应用开发(11):摸鱼也要讲基本法,两个字,16
  • PyQt5桌面应用开发(12):QFile与线程安全
  • PyQt5桌面应用开发(13):QGraphicsView框架
  • PyQt5桌面应用开发(14):数据库+ModelView+QCharts

下一个玩具

已经写了13篇PyQt5的文章,把基础的需求分析、信号与事件、界面布局、并行运行、对话框、文件IO、QGraphicsView框架都浅尝辄止地写了一点点,项目有什么需要还是要再仔细地阅读文档。

剩下就三个比较感兴趣的话题:

  • 数据库
  • Model/View框架
  • Charts

这三个话题中数据库本身就是比较大的,类似于QGraphicsView框架这样,背后需要的知识很专业化。Model/View框架和Charts都是比较常大路货。也不没什么难度。数据库呢,如果只是很表面很表面的涉及,其实接口也大差不差。

要一次性写三个比较大的话题实际上也很难权衡。不过这一系列的帖子本身也就是提供一个引子,大概了解一下有这么些东西,要应用的时候知道怎么找就可以。反而是趣味性和一定意义上的实用性我更加关注。

所以这里的开发也来一个实际的需求。目前所有关于PyQt5的写作,都放在一个目录下,并且维护了一个开源的GitCode仓库
,我们的需求是把所有的MarkDown文档都放在一个数据库里面,然后可以通过一个界面来显示这些文档,并且把文档的长度做一个图形。

这个需求很清楚:

  1. 报表:Markdown文档的列表显示;
  2. 报表:Markdown文档的长度图形;
  3. 数据:Markdown文档的名称、长度、创建时间,存储与Sqlite数据文件。

在这里插入图片描述

报表一:Markdown文档的列表显示

Widget的树、表、列报表

在PyQt5桌面应用开发(4):界面设计
中,我们蜻蜓点水的介绍了表、树和列三种很重要报表形式,在后面的PyQt5桌面应用开发(9):经典布局QMainWindow
中,我们又做了一个非常拙劣的树性视图,用的是QTreeWidget。

这三个报表形式,是三个最基础的概念。

  • 树:树形结构,每个节点都可以有子节点,子节点可以有子节点,以此类推,最终形成一个树形结构;
  • 表:二维结构的数据,每一行都是一个记录,每一列都是一个字段;
  • 列:一维结构的数据,每一行都是一个记录,每一列都是一个字段。

其实在Qt中,这三个概念都是按照所谓Model/View的框架来设计和实现的。

Qt中的MVC框架

Qt包含了一系列条目-视图的类,采用Model/View框架来管理数据和数据的展示方式。将数据管理和展示功能分离,使得开发者又更大的自由度来设计独特的显示方式,并且提供一个同意的模型界面来使用各种不同的数据来源。这是一种典型的系统体系架构,功能分离。

在《设计模式》一书中,作者写到:

MVC由三种类型的对象构成。模型是应用中的对象,视图是屏幕显示,控制器定义了用户界面与用户的交互。在MVC之前,用户界面设计常常把这三类对象混合在一次。通过MVC的解耦,实现了更好的灵活性和重用。

MV框架

  • 模型:与数据源通信,为体系中其它构件提供接口。数据源的不同是模型所需要处理的变化;
  • 视图:从模型获得模型索引,每个索引指向一份数据。条目索引是模型与视图的主要接口,视图通过索引来获取实际的数据。视图需要管理显示的细节;
  • 代理: 代理是模型与视图之间的中间层,用于过滤或修改模型中的数据。代理可以用于排序、过滤、修改数据等。

一般来说,模型/视图类可以分为上述三组:模型、视图和代理。每个组件都由抽象类定义,这些抽象类提供了公共接口,并且在某些情况下提供了功能的默认实现。抽象类的目的是为了提供完整的功能集,这些功能集是由其他组件期望的;当然也允许编写专门的组件。

模型、视图和代理之间通过信号与槽来通信。

  • 模型的信号告知视图数据的变化;
  • 视图的信号告知模型用户再用户界面上的操作;
  • 代理的信号用于在编辑过程中告知模型和视图编辑器的状态。

模型

所有的条目(item)模型都是基于QAbstractItemModel类的。这个类定义了一些纯虚函数,用于访问数据。这个类足够灵活,能够处理树、列和表类的数据。值得注意的时,这个类提供给视图和代理来访问数据,但是树并不必然存储在模型中,也可以存储在其他地方,比如数据库、文件、其他类、网络等等。

其子类有:

  • QAbstractListModel
  • QAbstractTableModel
  • QStringListModel
  • QStandardItemModel
  • QFileSystemModel
  • QSqlQueryModel
  • QSqlTableModel
  • QSqlRelationalTableModel

应用中,可以直接使用上述模型,也能够自由选择适当的类来实现自己的模型。

视图

QListView、QTableView和QTreeView则是完全不同的实现。虽然所有这三个类都是QAbstractItemView的子类。继承View来实现自己的特性也是很常规的做法。

代理

QAbstractItemDelegate是代理的基类,它定义了一些纯虚函数,用于编辑数据。实现这个类的由QStyledItemDelegate和QItemDelegate。推荐使用前者。

报表二:Markdown文档的长度图形

说起绘图,Python有太多优秀的选择。但是PyQt5的QtCharts也还算能用。

大概的过程:

  • 建立一个QChart;
  • 建立一个QChartView;
  • 建立一个QXXXSeries;
  • 将QXXXXSeries添加到QChart中;
  • 将QChart添加到QChartView中;
  • 在QXXXXSeries中增加数据集合;
  • 建立零个到两个QXXXAxis;
  • 将QXXXAxis添加到QChart中;

大概有点点啰嗦,但是也没有过于啰嗦;最终的基础效果也能接受,但也没那么好;经过QSS的美化,效果还是不错的。

def make_summary_chart(tv: QTableView):
    tv_model = tv.model()
    dock_chart_widget = QDockWidget("Summary")
    cv = QChartView()
    dock_chart_widget.setWidget(cv)

    chart = QChart()
    chart.setTitle("<b style='color:darkgray;'>My Writings: File Length Summary Chart</b>")
    chart.setAnimationOptions(QChart.SeriesAnimations)

    cv.setChart(chart)

    barset = QBarSet("File Length")
    count = tv_model.rowCount()

    barset.append([tv_model.data(tv_model.index(i, 2)) for i in range(0, count)])

    files = [tv_model.data(tv_model.index(i, 1)) for i in range(0, count)]
    files = [f.strip(".md") for f in files]

    series = QHorizontalBarSeries()
    series.append(barset)

    chart.addSeries(series)
    chart.legend().hide()

    axis_y = QBarCategoryAxis()
    axis_y.append(files)
    chart.addAxis(axis_y, Qt.AlignLeft)
    series.attachAxis(axis_y)

    axis_x = QValueAxis()
    chart.addAxis(axis_x, Qt.AlignBottom)
    series.attachAxis(axis_x)

    return dock_chart_widget

数据:Markdown文档和Sqlite数据库

这个数据什么的就太简单了。

QSqlDatebase是Qt中的数据库类,它可以连接各种数据库,比如Sqlite、MySQL、PostgreSQL等等。这里我们使用Sqlite数据库。链接过程也超级直观。

最终就是自己写起SQL,用QSqlQuery来exec_(这个名字因为重名才改成加_)执行,得到的结果用first,next来移动cursor,value(column)
来获取值。

def make_sqlite_table():
    """
    Please run
        > python -c "import tbl_sqlite as ts;ts.make_sqlite_table();"
    to create the sqlite database
    """
    db = QSqlDatabase.addDatabase("qsqlite".upper())
    db.setDatabaseName("writting_summary.db")

    if not db.open():
        raise IOError(f"{db.databaseName} open error")
    query = QSqlQuery(db)
    if not query.exec_("""
        create table if not exists writing_items (
            id integer primary key autoincrement, 
            file_name text not null unique, 
            length integer, 
            created_time timestamp
            )""".strip()):
        raise IOError(f"create table error: {query.lastError().text()}")

    for fn, file_size, lmt in list_all_markdown():
        # check if the file already exist
        query.exec_(f"""
            select * from writing_items where file_name='{fn}'
        """.strip())
        # already exist... stop
        if query.first():
            if not query.exec_(f"""
                    update writing_items set length={file_size}, created_time='{lmt}' where file_name='{fn}'
                    """.strip()):
                raise IOError(f"update error: {query.lastError().text()}")
        else:
            # insert a row
            if not query.exec_(f"""
                    insert into writing_items(file_name, length, created_time) 
                    values('{fn}', {file_size}, '{lmt}')
                    """.strip()):
                raise IOError(f"insert error: {query.lastError().text()}")

    db.close()


def list_all_markdown():
    for item in glob.glob("*.md"):
        file = pathlib.Path(item)
        state = file.stat()

        yield file.name, state.st_size, datetime.fromtimestamp(state.st_ctime)

代码

完整代码:

import glob
import pathlib
import sys
from datetime import datetime

from PyQt5.QtChart import QChartView, QChart, QBarSet, QValueAxis, QBarCategoryAxis, QHorizontalBarSeries
from PyQt5.QtCore import Qt
from PyQt5.QtSql import QSqlTableModel, QSqlDatabase, QSqlQuery
from PyQt5.QtWidgets import QApplication, QMainWindow, QTableView, QDockWidget


def make_sqlite_table():
    """
    Please run
        > python -c "import tbl_sqlite as ts;ts.make_sqlite_table();"
    to create the sqlite database
    """
    db = QSqlDatabase.addDatabase("qsqlite".upper())
    db.setDatabaseName("writting_summary.db")

    if not db.open():
        raise IOError(f"{db.databaseName} open error")
    query = QSqlQuery(db)
    if not query.exec_("""
        create table if not exists writing_items (
            id integer primary key autoincrement, 
            file_name text not null unique, 
            length integer, 
            created_time timestamp
            )""".strip()):
        raise IOError(f"create table error: {query.lastError().text()}")

    for fn, file_size, lmt in list_all_markdown():
        # check if the file already exist
        query.exec_(f"""
            select * from writing_items where file_name='{fn}'
        """.strip())
        # already exist... stop
        if query.first():
            if not query.exec_(f"""
                    update writing_items set length={file_size}, created_time='{lmt}' where file_name='{fn}'
                    """.strip()):
                raise IOError(f"update error: {query.lastError().text()}")
        else:
            # insert a row
            if not query.exec_(f"""
                    insert into writing_items(file_name, length, created_time) 
                    values('{fn}', {file_size}, '{lmt}')
                    """.strip()):
                raise IOError(f"insert error: {query.lastError().text()}")

    db.close()


def list_all_markdown():
    for item in glob.glob("*.md"):
        file = pathlib.Path(item)
        state = file.stat()

        yield file.name, state.st_size, datetime.fromtimestamp(state.st_ctime)


def make_table_view_on_database():
    make_sqlite_table()
    tv = QTableView()

    db = QSqlDatabase.addDatabase("qsqlite".upper())
    db.setDatabaseName("writting_summary.db")

    if not db.open():
        raise IOError(f"{db.databaseName} open error")

    tv.setModel(QSqlTableModel(tv, db))

    tv.model().setTable("writing_items")
    tv.model().select()
    tv.model().setHeaderData(1, Qt.Horizontal, "File Name")
    tv.model().setHeaderData(2, Qt.Horizontal, "Length")
    tv.model().setHeaderData(3, Qt.Horizontal, "Created Time")

    [tv.resizeColumnToContents(i) for i in range(0, tv.model().columnCount())]
    tv.setSortingEnabled(True)
    tv.showGrid()
    tv.setAlternatingRowColors(True)

    return tv


def make_summary_chart(tv: QTableView):
    tv_model = tv.model()
    dock_chart_widget = QDockWidget("Summary")
    cv = QChartView()
    dock_chart_widget.setWidget(cv)

    chart = QChart()
    chart.setTitle("<b style='color:darkgray;'>My Writings: File Length Summary Chart</b>")
    chart.setAnimationOptions(QChart.SeriesAnimations)

    cv.setChart(chart)

    barset = QBarSet("File Length")
    count = tv_model.rowCount()

    barset.append([tv_model.data(tv_model.index(i, 2)) for i in range(0, count)])

    files = [tv_model.data(tv_model.index(i, 1)) for i in range(0, count)]
    files = [f.strip(".md") for f in files]

    series = QHorizontalBarSeries()
    series.append(barset)

    chart.addSeries(series)
    chart.legend().hide()

    axis_y = QBarCategoryAxis()
    axis_y.append(files)
    chart.addAxis(axis_y, Qt.AlignLeft)
    series.attachAxis(axis_y)

    axis_x = QValueAxis()
    chart.addAxis(axis_x, Qt.AlignBottom)
    series.attachAxis(axis_x)

    return dock_chart_widget


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = QMainWindow()

    window.setCentralWidget(make_table_view_on_database())
    window.addDockWidget(Qt.RightDockWidgetArea, make_summary_chart(window.centralWidget()))

    window.resize(1440, 1000)
    window.show()
    sys.exit(app.exec_())

总结

  1. QSqlTableModel可以很好的管理数据库表格;
  2. MVC模型通过功能分离,提供了更好的灵活性和重用;
  3. QChartView来显示QChart,具体的图标是根据Series来决定的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/522719.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FE_Vue学习笔记 框架的执行流程详解

1 分析脚手架结构 &#xff08;1&#xff09;CLI就是 command line interface 的缩写。Vue CLI官网&#xff1a;Vue CLI &#xff08;2&#xff09;安装过程&#xff1a; &#xff08;PS&#xff1a; 提前安装过node.js了&#xff0c;没有安装的可以打开这个&#xff1a;Downl…

Flutter - 搭建引擎调试环境(iOS)

文章目录 前言开发环境安装依赖环境1. python32. depot_tools 获取引擎项目1. 创建engine空目录2. 创建.gclient文件并配置3. 指定引擎版本4. 同步引擎源码 搭建调试环境1. 构建编译1.1 生成构建所需文件1.1.1 主机端1.1.2 iOS端 1.2 完成构建编译 2. Xcode调试2.1 设置本地引擎…

Qt扫盲-QAreaSeries理论总结

QAreaSeries理论总结 一、概述二、使用1. 创建QAreaSeries 对象2. 填充数据3. 设置区域4. 将面积图 与绘图设备关联5. 将绘图设备与 GUI窗口关联 一、概述 QAreaSeries类以面积图的形式显示数据。QAreaSeries用于显示定量数据。它基于QLineSeries 类&#xff0c;边界线条之间的…

二、Docker在Linux下的安装

文章目录 Docker在Linux下的安装Docker安装的前提环境安装1. 卸载旧的版本2. 安装yum-utils包3. 设置镜像的仓库4. 安装Docker相关的内容 启动Docker运行hello-world查看下载的hello-world镜像 了解卸载Docker Docker在Linux下的安装 Docker安装的前提环境 CentOS Docker 安装…

【LLM系列之PaLM】PaLM: Scaling Language Modeling with Pathways

论文题目&#xff1a;《Scaling Instruction-Finetuned Language Models》 论文链接&#xff1a;https://arxiv.org/abs/2204.02311 github链接1&#xff1a;https://github.com/lucidrains/PaLM-pytorch/tree/main; github链接2:https://github.com/conceptofmind/PaLM huggin…

每天一个提高效率的Matlab编程小技巧(1)-dbstop if error

相信在matlab调试程序的时候都遇到过这种情况&#xff1a;运行程序时命令行报错&#xff0c;而且出错的位置在我们自己定义的函数里&#xff0c;比如下面这个例子&#xff1a; 主函数main.m: a[1 2 3]; b[4 5]; csum_squares(a,b); 子函数sum_squares.m function csum_squa…

AI孙燕姿 ?AI东雪莲 !—— 本地部署DDSP-SVC一键包,智能音频切片,本地训练,模型推理,为你喜欢的角色训练AI语音模型小教程

目录 感谢B站UP羽毛布团 演示视频 稻香——东雪莲 虚拟——东雪莲 反方向的钟——东雪莲 晴天龙卷风——东雪莲 DDSP-SVC 3.0 (D3SP) 是什么&#xff1f; 下载资源&#xff1a; 解压整合包 准备数据集 智能音频切片 数据集准备 填写训练设置和超参数 开始训练 推…

这个抓包工具太强了,科来网络分析系统强烈推荐

一直以来抓包工具&#xff0c;都推荐和使用wireshark&#xff0c;简单好用。最近发现一款更强大好用的网络分析工具&#xff0c;科来网络分析系统。且技术交流版是完全免费的&#xff0c;无需注册激活。这里强烈推荐和分享给大家。这可是个网络报文分析和监控神器。有多强大&am…

【CSS系列】第七章 · CSS盒子模型,看这一篇就够了

写在前面 Hello大家好&#xff0c; 我是【麟-小白】&#xff0c;一位软件工程专业的学生&#xff0c;喜好计算机知识。希望大家能够一起学习进步呀&#xff01;本人是一名在读大学生&#xff0c;专业水平有限&#xff0c;如发现错误或不足之处&#xff0c;请多多指正&#xff0…

Protobuf-net3.2.8中的protogen.exe之使用

目录 protobuf是个好东西 遇到问题 顺便研究一下命令行程序如何调试 protobuf是个好东西 protobuf是一个轻量级的数据格式&#xff0c;相比json&#xff0c;它的数据量为json的1/3&#xff0c;且存储方式为2进制&#xff0c;并进行了压缩&#xff0c;序列化和反序列化更快&…

效率与性能并存——离不开 Visual Studio Code 的前端开发与我

文章目录 &#x1f4cb;前言&#x1f3af;题外话&#xff1a;我与 VSCode 的那些事&#x1f3af;VSCode 的强大之处&#x1f9e9;VSCode 的诞生&#x1f9e9;VSCode 的一些功能 &#x1f3af;优与劣&#xff08;简单小结&#xff09;&#x1f4dd;最后 &#x1f4cb;前言 许久…

JVM 原理简介

JVM一直是java知识里面进阶阶段的重要部分&#xff0c;如果希望在java领域研究的更深入&#xff0c;则JVM则是如论如何也避开不了的话题&#xff0c;本系列试图通过简洁易读的方式&#xff0c;讲解JVM必要的知识点。 运行流程 我们都知道java一直宣传的口号是&#xff1a;一次编…

股票K线基础知识1

K线图 K线图是反映价格在某一时间周期内波动情况的图表&#xff0c;它由开盘价、收盘价、最高价、最低价四个要素构成&#xff0c;若当日收盘价高于开盘价&#xff0c;这表明价格处于上涨状态&#xff0c;此时K线图多用红色表示&#xff1b;若当日收盘价低于开盘价&#xff0c…

(转载)从0开始学matlab(第1天)—变量和数组

MATLAB 程序的基本数据单元是数组。一个数组是以行和列组织起来的数据集合&#xff0c;并且拥有一个数组名。数组中的单个数据是可以被访问的&#xff0c;访问的方法是数组名后带一个括号&#xff0c;括号内是这个数据所对应行标和列标。标量在 MATLAB 中也被当作数组来处理——…

JavaScript实现输入文字,指定输出遍数的代码

以下为实现输入文字&#xff0c;指定输出遍数的程序代码和运行截图 目录 前言 一、实现输入文字&#xff0c;指定输出遍数 1.1 运行流程及思想 1.2 代码段 1.3 JavaScript语句代码 1.4 运行截图 前言 1.若有选择&#xff0c;您可以在目录里进行快速查找&#xff1b; 2.…

Prometheus+Alertmanager+webhook-dingtalk实现钉钉告警

文章目录 一、前提准备及规划二、安装及启动2.1 Prometheus安装启动2.2 Node_export安装启动2.3 Alertmanager安装启动2.4 Webhook-dingtalk安装启动 三、配置及测试3.1 Webhook-dingtalk配置钉钉webhook地址3.2 Alertmanager配置钉钉告警3.3 Prometheus集成Alertmanager及告警…

基于Docker的深度学习环境部署以及WSL和linux镜像问题

基于Docker的深度学习环境部署 1. 什么是Docker&#xff1f;2. 深度学习环境的基本要求3. Docker的基本操作3.1 在Windows上安装Docker3.2 在Ubuntu上安装Docker3.3 拉取一个pytorch的镜像3.4 部署自己的项目3.5 导出配置好项目的新镜像 4. 分享新镜像4.1 将镜像导出为tar分享给…

android应用的一种图标隐藏

在Android10之前&#xff0c;应用程序通过调用PackageManager.setComponentEnabledSetting(componentName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP)函数来实现图标隐藏。 但是在android10之后&#xff0c;所有具有四大组件和需要申请…

C语言函数

C语言函数 一 函数的分类举例&#xff1a;*比较两个整数的大小**交换两个整数的值*&#xff08;传地址&#xff09; 二 参数实参形参 三 练习1.写一个函数判断一个数是不是素数2.写一个函数判断这一年是不是闰年3.写一个函数实现一个整型有序数组的二分查找4.写一个函数&#x…

两种方法教你在postman设置请求里带动态token

问题描述 在使用postman调试接口时&#xff0c;遇到一些需要在请求里加上token的接口&#xff0c;若token出现变化&#xff0c;需要手动修改接口的token值&#xff0c;带来重复的工作量&#xff0c;翻看postman使用手册后&#xff0c;我发现了两种方法可以解决这个问题。 01 …