「BUAA Python Programming」Final Assignment : GUI Task Scheduler

Posted by saltyfishyjk on 2022-08-09
Words 2.4k and Reading Time 9 Minutes
Viewed Times

「BUAA Python Programming」Final Assignment : GUI Task Scheduler

Part 0 项目概述

任务需求

基本需求

  1. 支持任务添加系统。用户可以创建新任务,包括标题、内容、截止日期、重要性等;同时,任务允许修改和删除。✔
  2. 显示每日任务。支持显示每天需要完成的任务。✔
  3. 确认任务的完成。当一项任务完成后,用户可以通过勾选复选框等方法标记该任务已完成。✔
  4. 日历系统。提供日历,用户可以通过日历查看一个月中每个日期的任务安排。✔
  5. 任务状态的区分。区分未开始、进行中、已完成和已逾期四种任务状态。✔
  6. 任务安排。通过任务列表,系统自动组织空闲时间安排任务。✔
  7. 用户登陆系统。通过架设在服务器的数据库支持用户登陆系统,并通过数据联网支持用户在任何设备上登陆均可同步自己的所有任务。✔

可选要求

  1. 待办事项系统根据时间过滤和显示任务。✔
  2. 任务类别划分。支持体育、学习、工作和其他四种任务类别。✔
  3. 数据收集与分析。通过架设在服务器的数据库,系统可以根据历史任务完成数据进行数据分析。✔
  4. 支持日常任务。对于日常任务,只需设置一次,就会自动出现在每天指定时间的任务列表中。✔
  5. 加权任务安排。支持重要性等属性量化,通过设置任务清单和任务的重要性,可以自动安排任务计划,同时可以分配空闲时间用于休息。✔

项目基本信息

项目开发

项目仓库

PyToDo(GitHub.com)

项目贡献者

@saltyfishyjk, @iszry, @BUAAcser(排名不分先后)

开源项目

PyToDo使用MIT License

发行版

目前已发行PyToDo-win64-v0.1.1-beta,其他操作系统用户可以使用源码自行打包。

Release PyToDo-win64-v0.1.1-beta · saltyfishyjk/PyToDo (github.com)

环境与依赖

环境

python=3.9

依赖包
1
2
3
4
5
6
7
cx_Freeze==6.11.1
matplotlib==3.5.1
PyMySQL==1.0.2
PyQt5==5.15.7
PyQt6==6.3.1
PySide6==6.3.1
requests==2.27.1

重要参考文献

记录部分较全面的参考文献,在后续Part中还会为具体内容补充参考文献。

PyQt/PySide/Qt Designer

PyQt和PySide是重要的python GUI库,视觉效果较为美观。

  1. Qt for Python:Qt(python)官方文档
  2. PyQt6中文教程:较精简的中文PyQt6GitBook,适合入门使用。
  3. PyQt5中文教程:较精简的中文PyQt5GitBook,适合入门使用。
  4. Qt Designer ui代码转python代码:使用Qt Designer得到的.ui文件生成.py代码
  5. Qt Designer页面介绍:介绍Qt Designer软件页面与基本信息
  6. PyQt5不同窗口之间的值传递:介绍较基本的窗口之间值传递方法
  7. 300行代码用PyQt实现登录注册界面,并用数据库保存信息:一个300行的demo,实现较为完整,在GitHub有开源仓库
  8. PyQt5开发核心机制:信号(Signal)和槽(Slot)的认识:重要参考文章,全面介绍了PyQt5中如何通过信号和槽进行信息传递(包括自定义类的对象)

注意:PyQt5,PyQt6以及PySide最好不要混搭使用,否则可能出现难以解决的bug。

PyMySQL/MySQL

MySQL是目前主流的数据库,PyMySQL为python提供了便捷使用MySQL的相关接口。

  1. 阿里云服务器文档:本项目数据库部署在阿里云服务器,官网提供了较多文章便于参考。

Part 1 PyQt/PySide

由于项目的复杂性,下面的介绍可能会出现PyQt/PySide混用的情况,这是不推荐的,但是,其基本原理相似,所以不做统一。

QFont

字体

e.g.

1
2
from PySide6 import QtGui
font = QtGui.QFont()

setFamily

设置字体家族

e.g.

1
font.setFamily("Consolas") # 设置字体为Consolas

setPointSize

设置字体大小(以长度磅为单位,一半用于印刷领域)

e.g.

1
font.setPointSize(12)

QPushButton

点击按钮

setText()

设置按钮显示文本

setToolTip

设置鼠标悬浮提示文本


QLineEdit()

单行文本框

text()

获取输入文本


QTextEdit()

多行文本框(支持html)

toPlainText()

获取输入的多行文本


QComboBox

下拉选择框

currentText()

返回当前选项内容

setCurrentIndex()

设置显示的选项(从0开始)


QCheckBox

复选框,参考PyQt5基本控件详解之QCheckBox

isChecked()

检查复选框是否被选中


QDateTimeEdit

年月日编辑框,参考PyQt5基本控件详解之QDateTimeEdit

time()

返回编辑的时间

对象解析

笔者实现了一个小轮子,将QDateTimeEdit.dateTime()对象解析为2022/08/15/22/22的格式

1
2
3
4
5
6
7
8
9
# IN : QTimeDate
def qtime_to_timestr(qtime):
year = qtime.date().year()
month = qtime.date().month()
day = qtime.date().day()
hour = qtime.time().hour()
min = qtime.time().minute()
s = str(year) + '/' + str(month) + '/' + str(day) + '/' + str(hour) + '/' + str(min)
return s

信号的传递

这篇博客PyQt5开发核心机制:信号(Signal)和槽(Slot)的认识介绍的较为详细。

Part 2 PyMySQL

数据库的连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# FUNC : connect to database on ALI CLOUD
# IN : NULL
# RET : NULL
def connect_database():
global db, cursor
# connect to database in ALI CLOUD server test_database
db = pymysql.connect(host='8.130.21.170',
user='root',
password='password',
database='official_database')
#database='test_database')
# make a cursor object
cursor = db.cursor()
global database
database = 'official_database'

字符串转义

对于字符串,如果不转义直接插入MySQL数据库,可能发生SQL语句错误或数据丢失等问题,因此需要转义。

PyMySQL中有专门的转义方法。对于本项目使用的PyMySQL版本(v1.0.2>1.0.0),使用如下方法导入:

1
from pymysql.converters import escape_string

对于字符串s,用如下方法得到转义后的字符串:

1
s = escape_string(s)

对于从数据库读取和保存到python变量的数据库数据,不必进行反转义,这项工作由pymysql完成。

参考资料:python pymysql转义方法escape_string使用说明及报错解决方法

Part 3 Qt Designer

Qt Designer是一款可视化GUI设计软件,可以通过拖动等操作清晰简单地绘制UI,同时较好地支持python,可以导出python代码。

软件使用简介

Qt Designer页面介绍:介绍Qt Designer软件页面与基本信息

.ui -> .py代码导出

demo.ui为例,在.ui文件目录下使用cmd,运行以下代码,得到demo.python

1
pyuic5 -o demo.py demo.ui

Part 4 pyinstaller打包可执行程序

经过前三个Part的设计,我们已经在开发环境中开发了一个不错的GUI程序,接下来面临的问题是打包获得可执行程序。我们使用的解决方案是pyinstaller,以下给出使用方法。

生成spec

经过摸索和实践,对于较为复杂的多文件项目(比如我们的项目),使用spec文件打包是不错的选择。下面以程序入口在主文件夹下的main.py为例。

在主文件夹下运行:

1
pyi-makespec main.py

会得到main.spec文件。

编辑spec

使用VSCode等编辑器打开main.spec文件,发现如下结构(其中内容为笔者的spec文件):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# -*- mode: python ; coding: utf-8 -*-


block_cipher = None


a = Analysis(
['main.py', 'mycalendar.py', 'database.py', 'gettime.py', 'home.py', 'login.py',
'preresources_rc.py', 'setup.py', 'signup.py', 'task.py', 'mymatrix.py', 'NewTask.py',
'ui_calendar.py', 'user.py', 'ui_matrix.py', 'ui_newtask.py', 'ui_pic.py',
'D:\\GitHub_WorkSpace\\BUAA-Python\\PyToDo\\PyToDo\\modules\\__init__.py',
'D:\\GitHub_WorkSpace\\BUAA-Python\\PyToDo\\PyToDo\\modules\\app_functions.py',
'D:\\GitHub_WorkSpace\\BUAA-Python\\PyToDo\\PyToDo\\modules\\app_settings.py',
'D:\\GitHub_WorkSpace\\BUAA-Python\\PyToDo\\PyToDo\\modules\\resources_rc.py',
'D:\\GitHub_WorkSpace\\BUAA-Python\\PyToDo\\PyToDo\\modules\\ui_functions.py',
'D:\\GitHub_WorkSpace\\BUAA-Python\\PyToDo\\PyToDo\\modules\\ui_main.py',
'D:\\GitHub_WorkSpace\\BUAA-Python\\PyToDo\\PyToDo\\widgets\\__init__.py'
],
pathex=[],
binaries=[],
datas=[('D:\\GitHub_WorkSpace\\BUAA-Python\\PyToDo\\PyToDo\\images', 'images'),
('D:\\GitHub_WorkSpace\\BUAA-Python\\PyToDo\\PyToDo\\img', 'img'),
('D:\\GitHub_WorkSpace\\BUAA-Python\\PyToDo\\PyToDo\\log', 'log'),
('D:\\GitHub_WorkSpace\\BUAA-Python\\PyToDo\\PyToDo\\themes', 'themes')],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='main',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='main',
)
  • a列表需要填写所有py文件,和main.py一个目录下的可以直接写文件名,其他的要写绝对目录(注意\\)。
  • datas列表的元素是tuple元组,第一个参数是python中的非py的data文件,第二个参数是data在exe中保存该资源的文件夹名,要求和项目中的文件夹名相同(读者可以根据仓库结构深入了解)。
  • console是布尔值,表示exe运行后是否要显示终端。

通过spec打包exe

编写好上述spec文件后,运行下述命令制作exe:

1
pyinstaller main.spec

值得注意的,这里笔者没有添加-d等参数,这也是在实践中摸索出来的,原理暂时未知能用就行

Part 5 其他

在项目中会遇到各种奇怪的问题,这里进行汇总和整理。

自己的模块和包中有相同名字的情况

最简单可靠的办法:使用refactor修改自己的包名不要头铁嗯刚

字符串转布尔

这里说的情况是字符串True转布尔True,字符串False转布尔False。应当用的方法是eval(s),而非bool()bool作用在字符串效果为判断是否为空。

参考文章:在 Python 中将字符串转换为布尔值

Typora设置文本居中

Typora原生主题不支持文本居中,所以使用html设置居中。

1
<div align = "center">居中的文本</div>

参考文章:Typora设置文本居中

为项目设置requirements.txt

使用requirements.txt可以方便地告诉使用者项目的环境配置信息。

参考文章:requirements.txt的创建及使用

PyQt - Must construct a QApplication before a QWidget

笔者在NewTask.py接入项目主题的时候报这个错误,最终确认原因是PyQt5和6不兼容。


This is copyright.