0%

临近开学的瞎折腾——软件开发(PyQt5)

本来打算剩余的几天寒假是好好看会儿高数的,结果吧,可能在家里实在是一点书都看不下去,昨晚看了俩小时高数骨头差点散架,学习这种事还是回学校再说吧。

倒是有一点记忆尤甚,空间中的点$(x,y,z)$到$x$轴的距离是多少,我居然脑子有坑算了向径的模,在和向径的$\alpha$角来了个正弦操作,晚上散步才想起来,这个距离不就是$y^2+z^2$嘛。。。

无聊到玩手机,还不如玩会儿带你闹,这几天的收获也较为丰富。

  • 自己没事瞎折腾,整理了windows窗口,任务栏,还安装了Mac系统下的dock栏,用(看)着很爽,但是,突然发现电脑的时间被我整没了,也就是我打开电脑看不了时间,很无语。于是就自己用所学知识(python+QT+pyinstaller)写了个图形化的小软件,可能这是我电脑上第一次运行自己安装的软件吧,当然这个过程遇到了一堆坑。

爬虫的东西准备放下一篇博客了。

前言传记

电脑总体的折腾效果如下,这个dock真的比windows的任务栏舒服多了。

然后安排了一些新软件

  • 快速启动 Wox
  • 快速查找 Everything
  • 及时预览 Quciklook

更多强大实惠的软件look这里:

https://camuseblog.top/2019-02-10-/software/


软件开发记

回归正题,很早之前接触了QT也写过一些小程序,还解决了上学期的课设,感觉基本的问题应该能驾驭了,既然把自己电脑的时间折腾没了,就自己写一个时钟小程序吧。

思路很简单,调时间的库,把返回的时间截取为字符串,在窗口(QTextEdit)中显示就行了,然后用pyinstaller打包一下,结果到处是坑23333。

不过在各种谷歌和stackoverflow的帮助下还是解决了,没有谷歌和stackoverflow我可能真的生活不能自理。

软件效果如下,点击show time就可以显示时间啦

控件线程的坑

QTextEdit这个控件如果想实现刷新功能,就是显示完上一时刻的时间后,清空控件,然后显示下一时刻的时间,得进行如下操作,仅仅clear()是不够的。

有时候需要处理一些跟界面无关的但非常耗时的事情,这些事情跟界面在同一个线程中,由于时间太长,导致界面无法响应,处于“假死”状态。例如:在应用程序中保存文件到硬盘上,从开始保存直到文件保存完毕,程序不响应用户的任何操作,窗口也不会重新绘制,从而处于“无法响应”状态,这是一个非常糟糕的体验 。

在这种情况下,有一种方法是使用多线程,即在子线程中处理文件保存,主线程负责界面相关。

而如果不想使用多线程,最简单的办法就是在文件保存过程中调用QtWidgets.QApplication.processEvents(),该函数的作用是让程序处理那些还没有处理的事件,然后再把使用权返回给调用者。

1
2
3
# 刷新界面命令:
QtWidgets.QApplication.processEvents()
self.ui.results_window.clear()

字体的坑

导入北京时间后,截取字符串一切OK,可就是显示不到QT的窗口中去(QTextEdit)。其实也不是,当时怀疑是转义的问题,我当时用字符串测试了一下,22,33,44这些数字都能显示上去,唯独11这个数字显示不上去???得亏是11点写的这个程序,不然真的又是一个BUG。为什么呢?

其实原因很吐血,我随便换了个别的字体,就又能正常显示了,无语。可能原因:QTextEdit的字体我设置的是palatino,这个字体无法显示11这个字符串。

(虽然我知道可能不是字体的原因,但每个程序员都能碰到一些脑仁疼无法解释的玄学BUG)


pyinstaller的坑

很早之前听说过这个库,能把.py文件打包为可执行的.exe文件,我试了一下,又是一堆坑。

比如,GUI界面的程序打包姿势和命令行的程序打包姿势是不一样的,而且尤其是使用了PyQt5的情况下。

正确打包姿势为:

1
pyinstaller -F -w -i logo.ico clock.py

如果使用了QTdesigner工具的话,add-data把外部的ui文件包含进来,不然的话:整个过程没有错误没有警告,但是程序就是不对让你找bug到怀疑人生。

而且,GUI文件是不需要输出命令行console的,所以后面使用-w取消console的输出,不然软件运行会有个大黑窗口。

pyinstaller虽然是python的第三方库,也能pip安装,但是千万别import pyinstaller,没有这个操作,pyinstaller就是在cmd里面直接用的。(我以为是我pip错了包的原因,又瞎搜了半天,无语)。

而且,pyinstaller之后会生成dist文件,需要把程序执行所需要的图片,数据,copy一份到dist文件夹里面,和.exe文件在同一个目录。

经过了无数的坑,终于开发出了自己的.exe程序,自己留着用呗。但是这个是个很好的开始啊,比如给这个软件扩充功能,能显示地区,温度等信息,或者当个记事本,记录一些待解决事件,这的确是个好的开始,但是这些功能等着以后闲的没事在扩充吧。


时钟程序如下:那些png文件什么的,自己寻找一个吧。那个.ui文件我是使用的QTdesigner,布局的话自己画一个。

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import sys
# 这里我们进行了一些必要模块的导入。最基础的widget组件
from PyQt5.QtWidgets import QMainWindow, qApp, QApplication, \
QVBoxLayout, QToolTip, QMessageBox, QAction
from PyQt5 import uic, QtWidgets, QtGui
# 显示图标
from PyQt5.QtGui import QIcon
# 显示提示文字
from PyQt5.QtGui import QFont
# 退出程序
from PyQt5.QtCore import QCoreApplication, Qt
from datetime import datetime, timedelta, timezone
import time
import PyQt5.QtGui

Ui_MainWindow, QtBaseClass = uic.loadUiType("mainWindow.ui")


class MyApp(QMainWindow):
def __init__(self):
super().__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
# 状态栏
self.statusBar().showMessage('Ready')
# 我们调用sender()方法来判断哪一个按钮是我们按下的。
# self.ui.pushButton_3.clicked.connect(self.calculate_tax)
self.ui.pushButton.clicked.connect(self.calculate_time)
# 鼠标位于按钮上显示的提示信息
# self.ui.pushButton.setToolTip('task of calculate Button')
# 这里我们设置了我们窗口的标题。这个标题显示在标题栏中。
self.setWindowTitle('CLOCK')
# 设置窗口透明度
self.setWindowOpacity(0.8)

# window_pale = QtGui.QPalette()
# window_pale.setBrush(self.backgroundRole(),QtGui.QBrush(QtGui.QPixmap()))
# self.setPalette(window_pale)
# 设置图片和窗口一样大
palette = QtGui.QPalette()
pix = QtGui.QPixmap('test.jpg')
pix = pix.scaled(self.width(),self.height())
palette.setBrush(QtGui.QPalette.Background,QtGui.QBrush(pix))
self.setPalette(palette)

# QIcon对象接收一个我们要显示的图片路径作为参数。
self.setWindowIcon(QIcon('icon.png'))
QToolTip.setFont(QFont('SansSerif', 10))
# 鼠标位于整个界面内是显示的提示信息
self.setToolTip('This is my Clock')
# 点击即退出
# self.ui.pushButton_2.clicked.connect(QCoreApplication.instance().quit)

# 工具栏
exit_action = QAction(QIcon('exit.png'), 'Exit', self)
exit_action.setShortcut('Ctrl+Q')
exit_action.triggered.connect(qApp.quit)
self.ui.toolbar = self.addToolBar('Exit')
self.ui.toolbar.addAction(exit_action)

# 菜单栏
exit_action1 = QAction(QIcon('exit.png'), '&Exit', self)
exit_action1.setShortcut('Ctrl+Q')
exit_action1.setStatusTip('Exit application')
exit_action1.triggered.connect(qApp.quit)
menu_bar = self.menuBar()
file_menu = menu_bar.addMenu('&退出')
file_menu.addAction(exit_action1)

# 主要事件的处理函数,点击Esc键退出
def keyPressEvent(self, e):
if e.key() == Qt.Key_Escape:
self.close()

# 关闭事件,练习消息盒
def closeEvent(self, event):
reply = QMessageBox.question(self, 'Message', \
"Are you sure to quit?", \
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
event.accept()
else:
event.ignore()

# 将事件连接到一个槽中
def calculate_time(self):
time_number = 0
self.ui.results_window.clear()
# 我只想让他显示20秒就退出
while time_number < 20:
utc_dt = datetime.utcnow().replace(tzinfo=timezone.utc)
bj_dt = utc_dt.astimezone(timezone(timedelta(hours=8)))
bj_dt = str(bj_dt)
bj_dt = bj_dt[0:19]
self.ui.results_window.setText(' '+bj_dt)

QtWidgets.QApplication.processEvents()
time.sleep(1)

self.ui.results_window.clear()
time_number = time_number + 1

QtWidgets.QApplication.processEvents()
self.ui.results_window.clear()
self.ui.results_window.setText(' I don\'t wanna to show time,try button again~')


if __name__ == "__main__":
# 所有的PyQt5应用必须创建一个应用(Application)对象
app = QApplication(sys.argv)
# Qwidget组件是PyQt5中所有用户界面类的基础类
window = MyApp()
# show()**方法在屏幕上显示出widget**
window.show()
# 一个widget对象在这里第一次被在内存中创建,并且之后在屏幕上显示。
# 当我们调用应用的exec_()方法时,应用进入了主循环
sys.exit(app.exec_())


成品


归结原因

近期系统的学了一下PyQt5的知识后,总结了踩坑的原因:干活之前不读文档,吓折腾只会浪费更多时间。

感谢上学期间打赏我的朋友们。赛博乞讨:我,秦始皇,打钱。

欢迎订阅我的文章