背景

相信有很多的测试同学,在日常的工作中都会需要去写一些辅助测试的小工具或者脚本,我们除了保证工具的可用性之外,有时还需要做一些图形界面上的开发以便在公司或者小组内推广。本文旨在以实战的形式,完成一个简单的账号管理GUI程序,实现完整的增、删、改、查功能项,带大家了解如何系统的开发一个账号管理GUI程序。

最终效果



使用框架

PyQt5 + SQLite3

代码设计

  • UI代码和操作数据库的代码分开为两个文件,FirstApp类和Tools类。
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from Tools import *

class FirstApp(QMainWindow):

    def __init__(self):
        super(FirstApp, self).__init__()

        # 创建数据库
        Tools.createDb()
        # 创建ui布局
        self.initMainUi()
        # 初始化表格
        self.initTable()
    
    def initTable(self):
        pass
    
    def initTable(self):
        pass
          
#程序主入口
if __name__ == '__main__':
    app=QApplication(sys.argv)
    firstapp = FirstApp()
    firstapp.show()
    sys.exit(app.exec_())
  • 启动程序时,检查同级路径下是否有SQLite数据库文件,如果没有则创建它,并插入示例数据,方法为Tools.createDb()
import sqlite3
import os

class Tools():

    @staticmethod
    def createDb():

        # 如果路径下没有db文件,重新创建并插入示例数据
        if os.path.exists('mydata.db') == False:
            connect = sqlite3.connect('mydata.db')
            c = connect.cursor()
            c.execute('''create table mydata(
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                Website varchar(1000),
                username varchar(1000),
                passwd varchar(1000)
            );
            ''')
            connect.commit()
            c.execute("insert into mydata values(1,'qq','qq','test123')")
            connect.commit()
            connect.close()
  • 创建ui布局,主程序布局为一个table控件+三个按钮控件,用栅格布局方式排列,如下图,主界面QMainWindows中包含着一层QWidget,QWidget中使用栅格布局GridLayout,GridLayout中为y一个表格控件 + 三个按钮控件。
#布局
    def initMainUi(self):
        
        # 设置主界面的大小,标题及图标
        self.resize(450, 270)
        self.setWindowTitle('FirstApp')
        self.setWindowIcon(QIcon('icon.png'))
        
        # 第二层的QWidget控件
        self.qwidget = QWidget()
        
        # 栅格布局
        grid = QGridLayout()
        
        # 创建表格控件和按钮控件
        self.tablewidget = QTableWidget()
        self.addButton = QPushButton('新增')
        self.editButton = QPushButton('修改')
        self.delButton = QPushButton('删除')

        # 设置控件在栅格中的位置
        grid.addWidget(self.tablewidget,1,1,1,3)
        grid.addWidget(self.addButton,2,1)
        grid.addWidget(self.editButton,2,2)
        grid.addWidget(self.delButton,2,3)
        
        # 添加栅格布局到qwidget
        self.qwidget.setLayout(grid)
        
        # 设置qwidget到主界面中
        self.setCentralWidget(self.qwidget)

        #给按钮绑定点击方法
        self.addButton.clicked.connect(self.addDef)
        self.editButton.clicked.connect(self.editDef)
        self.delButton.clicked.connect(self.delDef)
  • 初始化表格
def initTable(self):

        # 设置表格的列数为4
        self.tablewidget.setColumnCount(4)

        # 水平和垂直方向设置为正好填满表格
        self.tablewidget.horizontalHeader().setStretchLastSection(True)
        self.tablewidget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

        # 设置表格的表头,并设置为不可编辑
        headerlabels = ['序号','网站', '账号', '密码']
        self.tablewidget.setHorizontalHeaderLabels(headerlabels)
        self.tablewidget.setEditTriggers(QAbstractItemView.NoEditTriggers)

        # 隐藏id列,不显示数据的id也就是主键,这里的主键只用来删除和修改数据时使用
        self.tablewidget.setColumnHidden(0, True);

        # 不显示单元格
        self.tablewidget.setShowGrid(False)

        # 设置表格选择行为为 只能一行一行选择
        self.tablewidget.setSelectionBehavior(QAbstractItemView.SelectRows)

        # 初始化表格数据
        self.flushTable()
  • 刷新表格数据
def flushTable(self):

        # 从数据表中获取数据
        data_list = Tools.getData()

        # 设置表格的行数,和数据的数量相关
        self.tablewidget.setRowCount(len(data_list))

        # 设置表格的数据
        for index in range(len(data_list)):
            self.tablewidget.setItem(index,0,QTableWidgetItem(str(data_list[index][0])))
            self.tablewidget.setItem(index,1,QTableWidgetItem(data_list[index][1]))
            self.tablewidget.setItem(index,2,QTableWidgetItem(data_list[index][2]))
            self.tablewidget.setItem(index,3,QTableWidgetItem(data_list[index][3]))
@staticmethod
    def getData():

        conn = sqlite3.connect('mydata.db')
        c = conn.cursor()
        cursor = c.execute('select * from mydata')

        data_list = []
        for row in cursor:
            temp_list = []
            temp_list.append(row[0])
            temp_list.append(row[1])
            temp_list.append(row[2])
            temp_list.append(row[3])
            data_list.append(temp_list)
        conn.close()
        return data_list
  • 新增按钮的功能设置,对话框的布局如下:
def addDef(self):

        # 新增的窗口,因为新增和修改共用一个对话框,所以需要在showDialog中参入参数表示这次点击的是新增按钮还是修改按钮
        self.showDialog(1)
    
    def showDialog(self, status ,website='', username='', passwd=''):

        self.dialog = QDialog(self)
        self.dialog.setWindowIcon(QIcon('icon.png'))
        if status == 1:
            self.dialog.setWindowTitle('新增')
        else:
            self.dialog.setWindowTitle('修改')

        # 创建一个group盒子
        group = QGroupBox(self.dialog)

        # 标签和输入框
        lb1 = QLabel('网站:', group)
        self.ed1 = QLineEdit(group)
        self.ed1.setText(website)
        lb2 = QLabel('账号:', group)
        self.ed2 = QLineEdit(group)
        self.ed2.setText(username)
        lb3 = QLabel('密码:', group)
        self.ed3 = QLineEdit(group)
        self.ed3.setText(passwd)

        # 创建确定和取消的按钮
        ok_button = QPushButton('确定', self.dialog)
        cancel_button = QPushButton('取消', self.dialog)

        # 创建一个垂直布局,将标签和按钮控件都添加到垂直布局里
        group_layout = QVBoxLayout()
        group_item = [lb1, self.ed1, lb2, self.ed2, lb3, self.ed3]
        for item in group_item:
            group_layout.addWidget(item)

        # 将垂直布局添加到groupbox中
        group.setLayout(group_layout)
        group.setFixedSize(group.sizeHint())

        # 创建一个水平布局,并将两个按钮添加到布局中
        button_layout = QHBoxLayout()
        button_layout.addWidget(ok_button)
        button_layout.addWidget(cancel_button)

        # 创建一个最外层的dialog垂直布局,将盒子和按钮布局加到这个布局中
        dialog_layout = QVBoxLayout()
        dialog_layout.addWidget(group)
        dialog_layout.addLayout(button_layout)

        # 设置这个对话框的布局
        self.dialog.setLayout(dialog_layout)
        self.dialog.setFixedSize(self.dialog.sizeHint())

        # 按传入的状态绑定确定按钮的功能
        if status == 1:
            ok_button.clicked.connect(self.addDialogAccept)
        else:
            ok_button.clicked.connect(self.editDialogAccept)

        # 默认选中ok按钮
        ok_button.setDefault(True)

        # 绑定取消按钮的功能
        cancel_button.clicked.connect(self.dialog.reject)

        self.dialog.exec_()
        return False
    
   # 新增对话框的ok按钮
   def addDialogAccept(self):

        # 如果每个输入项都不为空的表示输入正确
        if self.ed1.text() != '' and self.ed2.text() != '' and self.ed3.text() != '':
            # 关闭窗口
            self.dialog.close()
            # 在数据库中新增字段
            Tools.new_data(self.ed1.text(), self.ed2.text(), self.ed3.text())
            # 刷新表格
            self.flushTable()
            # 提示新增成功
            self.showHint('新增成功')
        else:
            self.showHint('必填项不能为空')
            
    # 提示对话框
    def showHint(self, message):

        hint_msg = QMessageBox()
        hint_msg.setText(message)
        hint_msg.addButton(QMessageBox.Ok)
        hint_msg.setWindowTitle("提示")
        hint_msg.exec_()
  • 新增的数据库操作
@staticmethod
    def addData(website, username, passwd):

        connect = sqlite3.connect('mydata.db')
        c = connect.cursor()
        command = "insert into mydata values(null,'%s','%s','%s')" % (website, username, passwd)
        print(command)
        c.execute(command)
        connect.commit()
        connect.close()
  • 修改按钮的功能设置,和新增共用一个对话框,只是在点击ok按钮时有所不同
def editDef(self):
        # 选中某行
        selected_row = self.tablewidget.selectedItems()

        # 如果当前选中的项数量为3时,表示只选取了一项
        if len(selected_row) == 3:

            # 获取该行行号
            edit_row = self.tablewidget.row(selected_row[0])

            # 记录当前选中项的id
            self.id = self.tablewidget.item(edit_row, 0).text()
            website = self.tablewidget.item(edit_row, 1).text()
            username = self.tablewidget.item(edit_row, 2).text()
            passwd = self.tablewidget.item(edit_row, 3).text()

            # 将获取到的选中行的数据赋予给修改窗口的方法,同时返回新数据
            self.showDialog(2, website, username, passwd)
        else:
            # 如果没有选中改行时,点击编辑,弹出提示框
            self.showHint("请选中一行进行编辑")

    def editDialogAccept(self):

        if self.ed1.text() != '' and self.ed2.text() != '' and self.ed3.text() != '':
            self.dialog.close()
            # 修改数据
            Tools.editData(self.id,self.ed1.text(), self.ed2.text(), self.ed3.text())
            self.flushTable()
            self.showHint('修改成功')
        else:
            self.showHint('必填项不能为空')
  • 修改的数据库操作
@staticmethod
    def editData(id, website, username, passwd):

        connect = sqlite3.connect('mydata.db')
        c = connect.cursor()
        print(1)
        command = "update mydata set website='%s',username='%s',passwd='%s' where id=%s" % (
        website, username, passwd, id)
        print(command)
        c.execute(command)
        connect.commit()
        connect.close()
  • 删除的按钮功能设置
def delDef(self):
        # 选中某行
        selected_row = self.tablewidget.selectedItems()
        if len(selected_row) == 3:

            del_row = self.tablewidget.row(selected_row[0])
            id = self.tablewidget.item(del_row, 0).text()
            print(id)
            # 如果返回值为True,表示点击了确定删除
            if self.delDialog() == True:
                Tools.delData(id)
                self.flushTable()

        else:
            # 如果没有选中改行时,点击编辑,弹出提示框
            self.showHint("请选中一行进行删除")
            
    # 布局和新增修改相差不大,不详细赘述
    def delDialog(self):
        delDialog = QDialog(self)
        delDialog.setWindowTitle(u'删除')
        group = QGroupBox('', delDialog)
        lb1 = QLabel(u'确定删除吗?删除后无法恢复')
        
        ok_button = QPushButton(u'确定', delDialog)
        cancel_button = QPushButton(u'取消', delDialog)

        ok_button.clicked.connect(delDialog.accept)
        ok_button.setDefault(True)
        cancel_button.clicked.connect(delDialog.reject)
        group_layout = QVBoxLayout()
        group_item = [lb1]
        for item in group_item:
            group_layout.addWidget(item)
        group.setLayout(group_layout)
        group.setFixedSize(group.sizeHint())

        button_layout = QHBoxLayout()
        button_layout.addWidget(ok_button)
        button_layout.addWidget(cancel_button)
        dialog_layout = QVBoxLayout()
        dialog_layout.addWidget(group)
        dialog_layout.addLayout(button_layout)
        delDialog.setLayout(dialog_layout)
        delDialog.setFixedSize(delDialog.sizeHint())

        # 当点击ok是,表示确定删除返回True
        if delDialog.exec_():
            return True
        # 否则返回False
        return False
  • 删除的数据库操作
@staticmethod
    def delData(id):
        print(33333)
        connect = sqlite3.connect('mydata.db')
        c = connect.cursor()
        print(1)
        command = "delete from mydata where id = %s" % id
        print(command)
        c.execute(command)
        connect.commit()
        connect.close()

打包程序

使用pyinstaller库将代码打包成exe可执行文件

安装pyinstaller库:

pip install pyinstaller

打包命令:

pyinstaller -F FirstApp.py --noconsole

最后就会在 dist目录下生成exe可执行文件

源码下载

FirstApp.py
Tools.py