PyQt5试验:基于QTableWidget的应用
前言:
大家好,这里是Seon塞翁。本文内容为基于 QtableWidget 控件,结合时间管理四象限思想,制作一个简易的任务优先度计算器。 需先对 Python 语言和 PyQt5 库有一定了解,关于 PyQt5 的快速入门可阅读《PyQt5初学试验记录》(←点击)系列文章。
如今,是一个全类别大爆发的时代,我们每天都需要面对数不清的新生事物,或是要学习的各种知识,或是计划了的各项工作,尤为让人头疼的便是,自己该先做什么?市面上已有各式各样的时间管理 or 工作规划 APP,为苦于选择困难症和拖延症的人群,提供了理清思路的方法。
笔者在日常的学习工作中,亦是对将要处理的成批事务应该先做什么,而感到苦恼。在了解时间管理四象限的思想后,方才有了思路。下面,笔者将介绍一个用于安排任务的方法。
(总之我就是拿东西试了一下 QTableWidget 的功能)时间管理四象限,如上图所示。以重要与紧急两种标签,将事务分为四类,可以令用户很明确地意识到紧急且重要的事情,是需要先做的。并且在事务项较多时,利于把控全局,规划各项事务的执行顺序。
根据这个概念,笔者在 EXCEL 中建立了一个小模型,依照不同的紧急、重要程度,设置参考的系数区间,另外再增加一个时间系数。根据用户对任务的判断填写3个系数,以此计算优先值。这里笔者所用的优先值算法是:紧急X0.6 + 重要X0.4 + 时间X0.1。即更紧急的事占更大的权重,时间作为一个辅助区分的调整值。
计算出优先值后再排序,即可得知执行任务的优先顺序了。(嗯,终于把写文章调到最优先了呢!)
好,接下来用 pyqt5 把这玩意儿做出来吧!(明明在 EXCEL 里操作更快)
要用魔法对付魔法,故要用表格实现表格,本次采用的是 QTableWidget 控件。首先在 Qt Designer 中拖拖拽拽把界面做出来,上面一排按钮为基本功能,下面的框即是核心控件 。
QTableWidget 继承自 QTableView,区别在于:儿子不够独立自主,只认识爸爸教的标准的数据模型;而爸爸则见多识广,可以使用奇形怪状的数据。所以这里选择教育不成熟的儿子(也就是说 QTableView 用着麻烦吧)。
首先修改 __init__
方法,添加 cols
和 rows
变量表示表格的列数、行数,在后续增删改操作中会使用到。接着设置每一列的列宽自适应 QTableWidget 的大小,因为除了任务项的内容是长度不确定的字符,其他列都是数值,即宽度基本可以不变,所以设置仅首列可手动调整列宽。
def __init__(self):
super(ReLearnForm, self).__init__()
self.setupUi(self)
self.cols = self.tableWidget.columnCount()
self.rows = 0 # 任务数
self.tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) # 自适应列宽
self.tableWidget.horizontalHeader().setSectionResizeMode(0, QHeaderView.Interactive) # 仅首列可手动调整
下面开始实现各个按钮的功能:
1、Read me
点击打开一个自定义的说明书窗口,这里的 ReadForm()
为一个 Dialog 类。
def read(self):
self.d = ReadForm()
self.d.show()
self.d.setWindowTitle("参数说明")
2、+ (增加任务)
用 rows
记录当前的任务行数。
def add_row(self):
print('添加一行任务记录')
self.rows += 1
self.tableWidget.setRowCount(self.rows)
3、- (删除任务)
在删除行之前,需先获取当前选择的行;
在__init__
方法中添加 row_flag
变量用于记录被选中的行索引(第一行为 0),初值(未选择时)为 -1。通过 itemSelectionChanged
绑定单元格选择信号和自定义槽函数;
self.row_flag = -1 # 当前被选中的行索引
self.result = []
self.tableWidget.itemSelectionChanged.connect(self.chioce) # 单元格选择改变
自定义槽函数 choice()
实现对 row_flag
的修改;
def chioce(self): # 修改被选中的行索引
self.row_flag = self.tableWidget.currentRow()
print(f'选中第{self.row_flag + 1}行')
编写删除行的方法,删除后同时修改 rows
,并重置 row_flag
。
def del_row(self):
print('删除一行任务记录')
if self.row_flag == -1:
QMessageBox.about(self,'提醒','未选择要删除的行!')
else:
self.tableWidget.removeRow(self.row_flag) # 删除指定行
self.rows -= 1
self.row_flag = -1
4、X (清空任务)
将控件中的行数设为 0 即可实现清空。
def clean_rows(self):
print('清空任务记录')
reply = QMessageBox.question(self, '提示', '请确认是否要清空!', QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
self.rows = 0
self.tableWidget.setRowCount(self.rows)
5、RUN (计算)
在说明撤销操作之前,先来讲解 RUN 按钮所实现的功能;
在 __init__
方法中添加 result
变量用于暂存计算结果;
self.result = []
实现计算优先度的方法:① 按行遍历,获取任务名称、紧急程度、重要程度、时间系数4列的输入值,依照前文所述的规则计算得到优先度 p (四舍五入保留2位小数),② 将 p 值添加进单元格并设为可选择、不可编辑,将该单元格放进同一行的第五列(索引4),即优先度一列中,③ 最后将每一行的结果存入列表,根据优先度进行降序,再通过 result
变量保存。
def prior_value(self):
print('计算优先度')
# print(f"共{self.row_nums}行{self.cols}列数据")
list_para = []
try:
for r in range(self.rows):
my_item = self.tableWidget.item(r, 0).text() # 任务名称
urgent = int(self.tableWidget.item(r, 1).text()) # 紧急程度
important = int(self.tableWidget.item(r, 2).text()) # 重要程度
t = int(self.tableWidget.item(r, 3).text()) # 时间系数
p = round(urgent*0.6 + important*0.4 + t*0.1,2) # 优先度
item_p = QTableWidgetItem(str(p)) # 将优先度值添加为单元格
item_p.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) # 设置为可选择、不可编辑
self.tableWidget.setItem(r, 4, item_p)
list_para.append([my_item, urgent, important, t, p])
self.result = sorted(list_para, key=lambda x: x[4], reverse=True) # 根据优先度将行元素列表降序
self.sort_move(self.result)
except:
pass
此时计算出了结果,但仍未对单元格进行调整,故将排序后的结果列表 result
传入 sort_move()
方法进一步处理:从 result
中获取各列的值重写单元格,其中 now
为排序后的当前行索引。QTableWidget 不具备统一调整居中的功能,还需再次遍历所有单元格设置居中。
def sort_move(self, list_para_sorted): # 根据排序结果重写单元格
print('执行了sort')
for now in range(len(list_para_sorted)):
item_it = QTableWidgetItem(str(list_para_sorted[now][0]))
item_u = QTableWidgetItem(str(list_para_sorted[now][1]))
item_im = QTableWidgetItem(str(list_para_sorted[now][2]))
item_t = QTableWidgetItem(str(list_para_sorted[now][3]))
item_p = QTableWidgetItem(str(list_para_sorted[now][4]))
item_p.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) # 设置为可选择、不可编辑
print('得到了items')
self.tableWidget.setItem(now, 0, item_it)
self.tableWidget.setItem(now, 1, item_u)
self.tableWidget.setItem(now, 2, item_im)
self.tableWidget.setItem(now, 3, item_t)
self.tableWidget.setItem(now, 4, item_p)
print('设置了items')
for i in range(int(self.rows)): # 设置所有单元格文本居中
for j in range(int(self.cols)):
self.tableWidget.item(i, j).setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
最终结果如下,至此核心功能就已经实现了。但为了让这个(破玩意儿看起来更牛批)工具更完善,继续实现撤销删除和保存结果至 EXCEL 的功能。
6、← (左箭头 - 撤销)
从 result
变量中读入最新记录,先恢复行,再利用 sort_move()
方法恢复内容,这也是将 sort_move()
单独写成一个方法而不整合到 prior_value
方法的原因。
def return_result(self):
print('撤销操作')
print(self.result)
if self.result:
self.rows = len(self.result) # 恢复任务数
self.tableWidget.setRowCount(self.rows) # 恢复行
self.sort_move(self.result) # 恢复内容
注意: 删除无内容行的不会回复,即需当前 result
变量有内容才可以恢复。
7、↓ (下箭头 - 保存)
(真是个鸡肋的功能)现在是 pandas 时间!若 result
不为空,则将其逐行写入 DataFrame 后保存至 EXCEL 文件,然后调用 os.startfile('.xlsx')
打开文件。
def save(self):
print('保存结果')
if self.result:
# [my_item, urgent, important, t, p]
df = pd.DataFrame(columns=['任务项', '紧急程度', '重要程度', '时间系数', '优先度'])
for i in range(len(self.result)):
df.loc[i] = self.result[i]
df.to_excel('优先度结果表.xlsx', index = False)
os.startfile('优先度结果表.xlsx')
else:
QMessageBox.about(self,'提醒','未产生计算结果!')
最后,打包成可执行程序,图标是笔者绘制的一只在思考的鸽子(咕咕,就算安排了任务我也不会按时做的)。关于打包程序可以参考《PyQt5初学试验记录(三):Pyinstaller打包小结》。
完成了这个小工具后,相信大家已对 QTableWidget 控件的特性有了一定的了解,把它运用到自己的小玩意儿中吧!
谢谢阅读!(源码已上传至gitub:https://github.com/SeonPan/ReLearn)