前言
利用QTableWidget构建一个抽奖小程序,主要用于平时一些硬性指标工作分配给不同小组时随机抽签.random程序存在一定的伪随机,利用多次random,取最高值,主要是想抹平伪随机带来的数据误差,小程序大概长这个样子,主要有两个界面,包括主程序界面跟统计界面
- 主程序
- go功能为摇奖功能
- 清空列表为清空表格内数据
- 导入为导入名单,文本支持txt,一行一个名字
- 导出为导出摇奖结果
- 统计是对摇奖结果的统计
- 统计界面
统计结果包括手气最佳跟手气最差,需要其他逻辑的可以在统计处理里面增加
过程
主界面编写
利用qtdesigner画好界面,并为不同的按钮关联槽函数
class LuckyBallot(QWidget, Ui_LuckyBallot):
def __init__(self, parent=None):
super(LuckyBallot, self).__init__(parent=parent)
self.setupUi(self)
self.init_table_widget()
self.pushButton_clear.clicked.connect(self.on_pushButton_clear_cliced)
self.tableWidget.cellChanged[int, int].connect(self.on_cell_changed)
self.update_ui()
init_table_widget用于初始化整个tablewidget,默认新增一行,光标落在第一行第一列,其余单元格不可编辑
def init_table_widget(self):
'''初始化table widget, 默认新增一行,焦点落在第一行第一列'''
self.insert_new_row(0)
def insert_new_row(self, row):
"""新增一行数据,rows为行号"""
self.tableWidget.insertRow(row)
self.create_null_row(row)
self.tableWidget.setFocus()
self.tableWidget.setCurrentCell(row, 0)
self.tableWidget.editItem(self.tableWidget.item(row, 0))
单独提取出inser_new_row出来,row为行号,后续有其他功能调用
def create_null_row(self, row):
"""创建一空行,row为行号,name为第一行第一列的内容,空行默认为空"""
self.create_row(row, "", "", "")
def create_content(self, rows, name, count, rate):
name_item = QTableWidgetItem(name)
count_item = QTableWidgetItem(count)
rate_item = QTableWidgetItem(rate)
del_btn = QPushButton("删除", self.tableWidget)
del_btn.setStyleSheet('''
QPushButton{background:#58c3c3;}QPushButton:hover{background:#7d2720;}
''')
hLayout = QHBoxLayout()
widget = QWidget(self.tableWidget)
hLayout.addWidget(del_btn)
hLayout.setContentsMargins(0, 0, 0, 0)
hLayout.setAlignment(Qt.AlignmentFlag.AlignHCenter)
widget.setLayout(hLayout)
self.tableWidget.setItem(rows, 0, name_item)
self.tableWidget.setItem(rows, 1, count_item)
self.tableWidget.setItem(rows, 2, rate_item)
self.tableWidget.setCellWidget(rows, 3, widget)
del_btn.clicked.connect(self.del_row)
self.tableWidget.item(rows, 1).setFlags(Qt.ItemFlag.ItemIsEnabled)
self.tableWidget.item(rows, 2).setFlags(Qt.ItemFlag.ItemIsEnabled)
def create_row(self, rows, name: str, count: str, rate: str):
"""创建新行"""
# 由于cellchanged信号在写入时会被触发,在数据录入是阻塞该信号
self.tableWidget.blockSignals(True)
self.create_content(rows, name, count, rate)
# 释放阻塞,监听信号
self.tableWidget.blockSignals(False)
本程序会监控tablewidget的cellchanged信号,而tablewidget在处理该信号是会被多次触发,利用信号的阻塞,避免该bug产生
def del_row(self):
current_row = self.tableWidget.currentRow()
self.tableWidget.removeRow(current_row)
每一行内容都附带一个删除的按钮,使用起来较为方便
Go功能开发
go功能是摇奖功能,收集多次random的结果,摇中次数最多的位手气最佳,次数最少的为手气最差.
@pyqtSlot()
def on_pushButton_go_clicked(self):
"""摇骰子函数,多次random,取最高命中的名字"""
rows = self.tableWidget.rowCount()
if rows <= 1:
self.update_ui()
return
if not self.is_go_enable:
for row in range(rows):
self.tableWidget.setItem(row, 1, QTableWidgetItem("0"))
self.tableWidget.setItem(row, 2, QTableWidgetItem("0"))
self.is_go_enable = True
else:
for row in range(rows):
if self.tableWidget.item(row, 0).text().strip() == "":
self.tableWidget.removeRow(row)
rows = self.tableWidget.rowCount()
go_count = rows * 20
row_dict = {i: 0 for i in range(rows)}
for go in range(go_count):
rand = random.randint(0, rows - 1)
row_dict[rand] = row_dict.get(rand) + 1
for row, counts in row_dict.items():
rate = str(round(counts / go_count, 4) * 100) + "%"
name = self.tableWidget.item(row, 0)
self.create_row(row, name, str(counts), rate)
self.is_go_enable = False
max_row = max(row_dict, key=row_dict.get)
min_row = min(row_dict, key=row_dict.get)
self.lucky_stat.set_stat({"name": self.tableWidget.item(max_row, 0).text(),
"counts": self.tableWidget.item(max_row, 1).text(),
"rate": self.tableWidget.item(max_row, 2).text(),
"total": str(len(row_dict)),
"stat_time": QDateTime.currentDateTime().toString('yyyy-MM-dd hh:mm:ss')
})
self.unlucky_stat.set_stat({"name": self.tableWidget.item(min_row, 0).text(),
"counts": self.tableWidget.item(min_row, 1).text(),
"rate": self.tableWidget.item(min_row, 2).text(),
"total": str(len(row_dict)),
"stat_time": QDateTime.currentDateTime().toString('yyyy-MM-dd hh:mm:ss')
})
self.update_ui()
摇奖过后收集摇奖信息,将其记录在lucky_stat和unlucky_stat中,lucky_stat是一个独立的类,定义如下
class Stat:
name = ""
counts = ""
rate = ""
total = ""
stat_time = ""
def __init__(self, stat_dict: dict):
self.set_stat(stat_dict)
def set_stat(self, stat_dict: dict):
self.name = stat_dict.get("name", "")
self.counts = stat_dict.get("counts", "")
self.rate = stat_dict.get("rate", "")
self.total = stat_dict.get("total", "")
self.stat_time = stat_dict.get("stat_time", "")
清空列表及表格内容变动监控
清空列表用于删除表内数据,同时新增一行,用于记录新的表数据,简单调用QTableWidget的函数可实现删除行的功能,但需要注意,remove(row)在执行一次后,整个表格的行数会跟着变动,因此,多次循环删除第一条记录,可保证整个表格数据的3清空
def on_pushButton_clear_cliced(self):
"""清空表格数据"""
if QMessageBox.StandardButton.Yes == QMessageBox.warning(self, "清空数据", "是否清空数据",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No):
while self.tableWidget.rowCount() > 0:
self.tableWidget.removeRow(0)
self.insert_new_row(0)
监控表格内容变动情况
def on_cell_changed(self, row, col):
rows = self.tableWidget.rowCount()
if rows == (row + 1):
self.insert_new_row(rows)
导入及导出
简单的导入导出功能,调用QFileDialog
@pyqtSlot()
def on_pushButton_import_clicked(self):
file, _ = QFileDialog.getOpenFileName(self, "文件导入", "./", "文本类型(*.txt *.csv)")
if file == "" or not os.path.exists(file):
self.update_ui()
return
file_lines = []
with open(file, "r") as fp:
file_lines = fp.readlines()
if self.tableWidget.rowCount() > 1:
self.on_pushButton_clear_cliced()
# todo: 增加进度条,以提示当前进度
self.tableWidget.blockSignals(True)
rows = 0
for line in file_lines:
if line.strip() == "":
continue
self.tableWidget.insertRow(rows)
self.create_content(rows, line.strip(), "", "")
rows += 1
self.tableWidget.blockSignals(False)
self.update_ui()
@pyqtSlot()
def on_pushButton_explore_clicked(self):
"""内容导出功能"""
file_path, _ = QFileDialog.getSaveFileName(self, "导出文件", "./", "文本类型(*.txt *.csv)")
if file_path == "":
return
with open(file_path, "w") as fp:
rows = self.tableWidget.rowCount()
for row in range(rows):
name = self.tableWidget.item(row, 0).text()
counts = self.tableWidget.item(row, 1).text()
rate = self.tableWidget.item(row, 2).text()
fp.write(f"{name},{counts},{rate}\n")
QMessageBox.information(self, "导出", "导出成功")
统计及页面更新
统计结果,更新页面
@pyqtSlot()
def on_pushButton_stat_clicked(self):
stat_dlg = LuckyBallotStat(self.lucky_stat, self.unlucky_stat, self)
stat_dlg.show()
def update_ui(self):
self.pushButton_explore.setEnabled(self.is_go_enable)
self.pushButton_import.setEnabled(self.is_go_enable)
self.pushButton_clear.setEnabled(self.is_go_enable)
if not self.is_go_enable:
self.pushButton_go.setText("解锁")
self.tableWidget.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
else:
self.pushButton_go.setText("GO!!!")
self.tableWidget.setEditTriggers(QAbstractItemView.EditTrigger.CurrentChanged)
row = self.tableWidget.rowCount()
self.tableWidget.setFocus()
self.tableWidget.setCurrentCell(row - 1, 0)
self.tableWidget.editItem(self.tableWidget.item(row - 1, 0))
后记
程序的逻辑比较简单,主要还是在页面的控制上,想要在导入上增加进度条,没成功,进度条不显示,还得增加分页功能,后续在做吧 完成程序如下
import os
import random
import sys
import time
from PyQt6.QtCore import Qt, pyqtSlot, QFile, QRect, QDateTime
from PyQt6.QtWidgets import QApplication, QWidget, QTableWidgetItem, QPushButton, QHBoxLayout, QMessageBox, QFileDialog, \
QProgressBar, QDialog, QAbstractItemView
from ui_luckyBallot import Ui_LuckyBallot
from ui_luckyBallotStat import Ui_Dialog_Stat
class Stat:
name = ""
counts = ""
rate = ""
total = ""
stat_time = ""
def __init__(self, stat_dict: dict):
self.set_stat(stat_dict)
def set_stat(self, stat_dict: dict):
self.name = stat_dict.get("name", "")
self.counts = stat_dict.get("counts", "")
self.rate = stat_dict.get("rate", "")
self.total = stat_dict.get("total", "")
self.stat_time = stat_dict.get("stat_time", "")
class LuckyBallotStat(QDialog, Ui_Dialog_Stat):
def __init__(self, lucky: Stat, unlucky: Stat, parent=None):
super(LuckyBallotStat, self).__init__(parent=parent)
# self.tableWidget.setColumnCount(2)
self.setupUi(self)
self.lucky = lucky
self.unlucky = unlucky
self.setupData()
def setupData(self):
"""填充数据"""
self.tableWidget.insertRow(0)
self.tableWidget.insertRow(1)
self.tableWidget.setItem(0, 0, QTableWidgetItem("手气最佳"))
self.tableWidget.setItem(0, 1, QTableWidgetItem(self.lucky.name))
self.tableWidget.setItem(0, 2, QTableWidgetItem(self.lucky.counts))
self.tableWidget.setItem(0, 3, QTableWidgetItem(self.lucky.rate))
self.tableWidget.setItem(1, 0, QTableWidgetItem("手气最差"))
self.tableWidget.setItem(1, 1, QTableWidgetItem(self.unlucky.name))
self.tableWidget.setItem(1, 2, QTableWidgetItem(self.unlucky.counts))
self.tableWidget.setItem(1, 3, QTableWidgetItem(self.unlucky.rate))
self.label_total.setText(self.lucky.total)
self.label_time.setText(self.lucky.stat_time)
class LuckyBallot(QWidget, Ui_LuckyBallot):
is_go_enable = True
lucky_stat = Stat(dict())
unlucky_stat = Stat(dict())
def __init__(self, parent=None):
super(LuckyBallot, self).__init__(parent=parent)
self.setupUi(self)
self.init_table_widget()
self.pushButton_clear.clicked.connect(self.on_pushButton_clear_cliced)
self.tableWidget.cellChanged[int, int].connect(self.on_cell_changed)
self.update_ui()
def init_table_widget(self):
'''初始化table widget, 默认新增一行,焦点落在第一行第一列'''
self.insert_new_row(0)
def insert_new_row(self, row):
"""新增一行数据,rows为行号"""
self.tableWidget.insertRow(row)
self.create_null_row(row)
self.tableWidget.setFocus()
self.tableWidget.setCurrentCell(row, 0)
self.tableWidget.editItem(self.tableWidget.item(row, 0))
def create_null_row(self, row):
"""创建一空行,row为行号,name为第一行第一列的内容,空行默认为空"""
self.create_row(row, "", "", "")
def create_content(self, rows, name, count, rate):
name_item = QTableWidgetItem(name)
count_item = QTableWidgetItem(count)
rate_item = QTableWidgetItem(rate)
del_btn = QPushButton("删除", self.tableWidget)
del_btn.setStyleSheet('''
QPushButton{background:#58c3c3;}QPushButton:hover{background:#7d2720;}
''')
hLayout = QHBoxLayout()
widget = QWidget(self.tableWidget)
hLayout.addWidget(del_btn)
hLayout.setContentsMargins(0, 0, 0, 0)
hLayout.setAlignment(Qt.AlignmentFlag.AlignHCenter)
widget.setLayout(hLayout)
self.tableWidget.setItem(rows, 0, name_item)
self.tableWidget.setItem(rows, 1, count_item)
self.tableWidget.setItem(rows, 2, rate_item)
self.tableWidget.setCellWidget(rows, 3, widget)
del_btn.clicked.connect(self.del_row)
self.tableWidget.item(rows, 1).setFlags(Qt.ItemFlag.ItemIsEnabled)
self.tableWidget.item(rows, 2).setFlags(Qt.ItemFlag.ItemIsEnabled)
def create_row(self, rows, name: str, count: str, rate: str):
"""创建新行"""
# 由于cellchanged信号在写入时会被触发,在数据录入是阻塞该信号
self.tableWidget.blockSignals(True)
self.create_content(rows, name, count, rate)
# 释放阻塞,监听信号
self.tableWidget.blockSignals(False)
def del_row(self):
current_row = self.tableWidget.currentRow()
self.tableWidget.removeRow(current_row)
@pyqtSlot()
def on_pushButton_go_clicked(self):
"""摇骰子函数,多次random,取最高命中的名字"""
rows = self.tableWidget.rowCount()
if rows <= 1:
self.update_ui()
return
if not self.is_go_enable:
for row in range(rows):
self.tableWidget.setItem(row, 1, QTableWidgetItem("0"))
self.tableWidget.setItem(row, 2, QTableWidgetItem("0"))
self.is_go_enable = True
else:
for row in range(rows):
if self.tableWidget.item(row, 0).text().strip() == "":
self.tableWidget.removeRow(row)
rows = self.tableWidget.rowCount()
go_count = rows * 20
row_dict = {i: 0 for i in range(rows)}
for go in range(go_count):
rand = random.randint(0, rows - 1)
row_dict[rand] = row_dict.get(rand) + 1
for row, counts in row_dict.items():
rate = str(round(counts / go_count, 4) * 100) + "%"
name = self.tableWidget.item(row, 0)
self.create_row(row, name, str(counts), rate)
self.is_go_enable = False
max_row = max(row_dict, key=row_dict.get)
min_row = min(row_dict, key=row_dict.get)
self.lucky_stat.set_stat({"name": self.tableWidget.item(max_row, 0).text(),
"counts": self.tableWidget.item(max_row, 1).text(),
"rate": self.tableWidget.item(max_row, 2).text(),
"total": str(len(row_dict)),
"stat_time": QDateTime.currentDateTime().toString('yyyy-MM-dd hh:mm:ss')
})
self.unlucky_stat.set_stat({"name": self.tableWidget.item(min_row, 0).text(),
"counts": self.tableWidget.item(min_row, 1).text(),
"rate": self.tableWidget.item(min_row, 2).text(),
"total": str(len(row_dict)),
"stat_time": QDateTime.currentDateTime().toString('yyyy-MM-dd hh:mm:ss')
})
self.update_ui()
def on_pushButton_clear_cliced(self):
"""清空表格数据"""
if QMessageBox.StandardButton.Yes == QMessageBox.warning(self, "清空数据", "是否清空数据",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No):
while self.tableWidget.rowCount() > 0:
self.tableWidget.removeRow(0)
self.insert_new_row(0)
def on_cell_changed(self, row, col):
rows = self.tableWidget.rowCount()
if rows == (row + 1):
self.insert_new_row(rows)
@pyqtSlot()
def on_pushButton_import_clicked(self):
file, _ = QFileDialog.getOpenFileName(self, "文件导入", "./", "文本类型(*.txt *.csv)")
if file == "" or not os.path.exists(file):
self.update_ui()
return
file_lines = []
with open(file, "r") as fp:
file_lines = fp.readlines()
if self.tableWidget.rowCount() > 1:
self.on_pushButton_clear_cliced()
# todo: 增加进度条,以提示当前进度
self.tableWidget.blockSignals(True)
rows = 0
for line in file_lines:
if line.strip() == "":
continue
self.tableWidget.insertRow(rows)
self.create_content(rows, line.strip(), "", "")
rows += 1
self.tableWidget.blockSignals(False)
self.update_ui()
@pyqtSlot()
def on_pushButton_explore_clicked(self):
"""内容导出功能"""
file_path, _ = QFileDialog.getSaveFileName(self, "导出文件", "./", "文本类型(*.txt *.csv)")
if file_path == "":
return
with open(file_path, "w") as fp:
rows = self.tableWidget.rowCount()
for row in range(rows):
name = self.tableWidget.item(row, 0).text()
counts = self.tableWidget.item(row, 1).text()
rate = self.tableWidget.item(row, 2).text()
fp.write(f"{name},{counts},{rate}\n")
QMessageBox.information(self, "导出", "导出成功")
@pyqtSlot()
def on_pushButton_stat_clicked(self):
stat_dlg = LuckyBallotStat(self.lucky_stat, self.unlucky_stat, self)
stat_dlg.show()
def update_ui(self):
self.pushButton_explore.setEnabled(self.is_go_enable)
self.pushButton_import.setEnabled(self.is_go_enable)
self.pushButton_clear.setEnabled(self.is_go_enable)
if not self.is_go_enable:
self.pushButton_go.setText("解锁")
self.tableWidget.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
else:
self.pushButton_go.setText("GO!!!")
self.tableWidget.setEditTriggers(QAbstractItemView.EditTrigger.CurrentChanged)
row = self.tableWidget.rowCount()
self.tableWidget.setFocus()
self.tableWidget.setCurrentCell(row - 1, 0)
self.tableWidget.editItem(self.tableWidget.item(row - 1, 0))
def main():
app = QApplication(sys.argv)
app.setStyle("Fusion")
# l = Lucky()
l = LuckyBallot()
# l = ImportBar()
l.show()
sys.exit(app.exec())
if __name__ == '__main__':
main()