[Python]利用PyQt6的QTableWidget搭建简单抽奖小程序

545 阅读4分钟

前言

利用QTableWidget构建一个抽奖小程序,主要用于平时一些硬性指标工作分配给不同小组时随机抽签.random程序存在一定的伪随机,利用多次random,取最高值,主要是想抹平伪随机带来的数据误差,小程序大概长这个样子,主要有两个界面,包括主程序界面跟统计界面

  • 主程序
    • go功能为摇奖功能
    • 清空列表为清空表格内数据
    • 导入为导入名单,文本支持txt,一行一个名字
    • 导出为导出摇奖结果
    • 统计是对摇奖结果的统计 image.png
  • 统计界面

统计结果包括手气最佳跟手气最差,需要其他逻辑的可以在统计处理里面增加

image.png

过程

主界面编写

利用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()