PyQt5学习笔记:使用Qt designer制作一个简单的打卡系统

学习使用PyQt编写一个带GUI的员工打卡系统:

(1) 首先要安装PyQt5 参考网址 使用到的命令有:

pip install PyQt5pip install PyQt5-toolspip install pyqt5designer

如果下载过慢下载过程经常出错的同学,可以自行换源:参考网址 

#临时换源 源地址+包名
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple package_name
#永久换源
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

以上的库安装完成后打开PyCharm配置外部工具:从菜单依次进入File>Settings>Tools

配置Qt Designer外部工具:(这是PyQt中一款辅助GUI设计的工具)

Name:  Qtdesigner           #可自定义,自己认得就行

Program:                          #为python安装路径下的designer.exe

#通常为python\Lib\site-packages\qt5_applications\Qt\bin\designer.exe

Working diectory:          $ProjectFileDir$

#建议就按这么写,表示designer直接将文件生成到当前项目文件里

 配置PyUIC:(用Qt designer设计GUI界面后生成的ui文件需要通过uic来将其转换为py文件)

Name: PyUIC            #可自定义

Program:                   #为python安装路径下的Scripts >>pyuic5.exe

Arguments:            $FileName$ -o $FileNameWithoutExtension$.py

#非常重要,声明生成的py文件与ui文件同名,否则容易混淆

Working diectory:  $ProjectFileDir$         #与配置designer同理

(2) 创建项目

(3) 使用Qt designer绘制一个简单的用户界面

我这里使用QMainWindow来创建GUI的初始界面

 

 

相较于Python原生的tkinter,使用Qt designer除了可以更直观地预览窗体的设计。

Qt designer还为用户提供了对象查看器和属性编辑器协助用户更轻松地完成窗体初始化,

在对象查看器中要特别留意哪些组件是与用户有交互的,这些组件的命名要自己能识别。

完成界面设计后保存将生成一个ui文件。

 对生成的ui文件启用PyUIC工具,即可生成一个同名的py文件,如果没有在项目中看到py文件,请检查之前配置工具时的参数。

 (4) 将window1.py移入view包后测试窗体运行

import sys
from PyQt5 import uic
from PyQt5.QtWidgets import QMainWindow, QApplication
from view.window1 import Ui_window_1 #这里要看你生成的py文件中类名叫什么class Myapp(QMainWindow, Ui_window_1):def __init__(self):super().__init__()self.setupUi(self)# 第一种方法,导入.ui转换好的.py文件# 该方法允许继承,和普通类一样运行程序时没有额外的负载# 但是每次更新ui都要重新转换py,无法即时更新# uic.loadUi('mainw.ui', self)# 第二种方法,使用uic直接加载.ui文件# 该方法可以即时更新ui的变化# 但不允许被继承,在PyCharm中也无法补全if __name__ == "__main__":app = QApplication(sys.argv)win1 = Myapp()win1.show()sys.exit(app.exec_())

这里我提供两次运行的方案:第一种是直接调用py文件,缺点是每次更新ui都需要更新一遍py文件;第二种是直接调用uic包来读取ui文件,优点是窗体可以直接跟着ui更新,缺点是pycharm里似乎是不能在没有第三方工具的情况下直接编辑ui文件的,这样怎么将窗体和与用户交互的功能绑定就成了一个问题,所以这种方式我并不推荐。

(5) 活动外包

既然包含界面的window1.py文件可能会需要频繁更新,那把需要和用户交互的代码全部塞进window1.py里就不合理了,我们可以把不需要依赖组件完成的动作交给外部的文件来执行,首先在ctrl包里建立一个act1来测试向窗体外的py文件传输文本框的值。

 随后定义action1的内容:令action1将window1.textedit的值传输给ctrl.act1.test1()中,test1()收到后将其打印至控制台。

# 找到你创建的按钮对象button,添加一个点击事件action1
self.button.clicked.connect(self.action1)

    def action1(self):# test1(self.texteidt.toPlainText())text = signUp(self.textedit.toPlainText())

这里的self代指这个window1的对象,后续我们要对组件进行编辑都需要声明self。我这里的action1方法也定义在class里,与其他方法同级,定义在不同位置调用方法也不同。 

 act1.test1代码,text即从前端窗体的文本框获取的内容。

def test1(text):print("获取文本:"+text)

测试通过即可进行下一步实验。 

(6) 实现打卡功能

为员工打卡设计一套流程:

输入工号>查询是否存在该员工>是:打卡成功 否:打卡失败

所以我们最好有个第三方的数据表来存放员工的打卡数据,可以像老师一样创建一个文件来保存读取,我这里选用的是mysql数据库,如果软件要实现多个终端共享数据终究还是要绕回数据库的,使用navicat建两张表分别保存员工信息和打卡记录。

要连接数据的动作通常不会只有一处需要调用,所以我们可以把连接数据库的行为单独拿出来做一个工具类DBUtil,连接数据库还需要安装pymysql库,网上我找到了关于pymysql连接池的写法:参考网址,但据作者称该方法对python的版本有要求,所以这里我简单写一个较为粗糙的工具类先应付打卡系统的需求,建议大家另寻更优的写法。 

 

 使用act1.test1测试DBUtil,测试效果可行进行下一步。

在act1中编写实现打卡功能的signUp方法(名字自定义)

该方法的运行需导入import datetime as dt

from util.db import DBUtil
import datetime as dtdef signUp(eid):print("获取工号:"+eid)sql = "select * from employee where eid = "+eidemployee = DBUtil.selectOne(sql)if employee is None:result = "打卡失败:查无此工号"else:   #查询到该工号,添加打卡记录ct = dt.datetime.now()      #dt为导入datetime的别名,该行是取当前系统时间赋值给ctename = employee[1]         #刚才取出的数据库数据[0]为工号,[1]为姓名if ct.hour < 9 or (ct.hour == 9 and ct.minute == 0):    #9点之后视为迟到amood = "准时"else:amood = "迟到"atime = ct.strftime("%Y-%m-%d %H:%M:%S")    #ct的格式无法直接使用,需进一步转换sql = "insert into attendance (eid,ename,atime,amood) values " \"(" + eid + ",\'" + ename + "\',\'" + atime + "\',\'" + amood + "')"if DBUtil.updateData(sql) >= 1: #cursor.execute(sqlstr)返回的是受影响的行数result = "打卡成功:" + eid + "\t" + ename + "\t" + atime + "\t" + amoodelse:result = "打卡失败:数据库连接异常"print("signUp执行结果:"+result)return result   #返回窗体window1用于修改label_2的内容

看着比较难受的就是sql语句的写法,为了看得清楚点,再提供一个例句:

insert into attendance (eid,ename,atime,amood) values (2,'王五','2023-03-09 18:33:21','迟到')

 

 之后就是重写window1.py里的action1:

from ctrl.act1 import signUpdef action1(self):# test1(self.texteidt.toPlainText())text = signUp(self.textedit.toPlainText())# 调用ctrl.act1.signUp()的方法输入用户的工号,返回打卡的结果并显示在label_2完成交互self.label_2.setText(text)

 测试结果,打卡记录添加成功

至此员工打卡的功能基本完成

(7) 子窗体交互

实际的应用中单一窗体通常难以满足用户的基本需求,就拿我们做的这个打卡系统而言,它要不既要面对员工这个群体,也要面对管理员这个群体,通常我们有三种方案解决这个问题:

第一种是为这两个群体分别做一套程序,这种方案最彻底,效率通常也最低,在此不做演示;

第二种是当这两个群体的需求高度重合时,可以在一个窗体里分别为这两个群体设计一套不同的ui,由于这个方案和我创建view的逻辑相悖,在此也不做演示;

第三种就是为另一个群体再做一个窗体,这个方案在有Qt designer的辅助下可以轻松很多,我们先使用Qt designer为管理员设计一套ui,与用户的需求不同,管理员需要输入工号查询出该工号所有的打卡记录,所以我们为这个窗体添加一个支持显示多条记录的QTableWidget,并将该窗体命名为window2。

再用PyUIC工具生成py文件,将其移入view层,接下来修改run.py文件:参考网址 

 

import sys
from PyQt5 import uic
from PyQt5.QtWidgets import QMainWindow, QApplication
from view.window1 import Ui_window_1
from view.window2 import Ui_window_2class Myapp(QMainWindow, Ui_window_1):def __init__(self):super().__init__()self.setupUi(self)# 第一种方法,导入.ui转换好的.py文件# 该方法允许继承,和普通类一样运行程序时没有额外的负载# 但是每次更新ui都要重新转换py,无法即时更新# uic.loadUi('mainw.ui', self)# 第二种方法,使用uic直接加载.ui文件# 该方法可以即时更新ui的变化# 但不允许被继承,在PyCharm中也无法补全self.action2.triggered.connect(self.openWin2)# 为子菜单action2设置一个链接事件:openWin2--打开窗体window2def openWin2(self):print("打开窗体window2")win2.show()     #打开win2win1.close()    #关闭win1class Newapp(QMainWindow, Ui_window_2):def __init__(self):super().__init__()self.setupUi(self)if __name__ == "__main__":app = QApplication(sys.argv)win1 = Myapp()win1.show()win2 = Newapp()sys.exit(app.exec_())

 模仿window1的写法在run.py中导入window2然后为window1添加一个子菜单的点击事件使其完成窗体的切换,同样在Newapp类中win2也可以定义相同的方法实现交互,由于我偷懒没有给window2建菜单栏,在此不做演示。

 至此不同窗体之间的功能基本实现。

 (8) 二维数组的显示

最后是实现多组数据的读取,鉴于我们要做的功能与act1服务不同的窗体,我们新建一个act2来实现读取打卡记录的功能以便于后续的更新。

 

from util.db import DBUtildef searchAll(eid):print("获取工号:" + eid)sql = "select eid,ename,atime,amood from attendance where eid = " + eidreturn DBUtil.selectList(sql)

 定义act2的searchAll来向数据库读取数据返回给window2,在这项业务中aid对于用户来说是无意义的数据,所以读取数据的时候不必读取所有字段。

 

# 在window2.py中找到你创建的按钮对象button,添加一个点击事件action2
self.button.clicked.connect(self.action2)

在window2.py导入ctrl.act2.searchAll并为按钮button链接一个点击事件action2;

编写action2事件:参考网址

    def action2(self):att = searchAll(self.textedit.toPlainText())#向ctrl.act2.searchAll传递文本框的内容,查询结果赋值给attrow = len(att)  #定义总行数,总行数取决于查询到的数据col = 4         #定义总列数,总列数取决于你要展示的内容self.table.setRowCount(row)self.table.setColumnCount(col)# 先设置表单格式↑再设置table的表单首项↓item0 = QtWidgets.QTableWidgetItem()item0.setText("工号")self.table.setHorizontalHeaderItem(0, item0)item1 = QtWidgets.QTableWidgetItem()item1.setText("姓名")self.table.setHorizontalHeaderItem(1, item1)item2 = QtWidgets.QTableWidgetItem()item2.setText("打卡时间")self.table.setHorizontalHeaderItem(2, item2)item3 = QtWidgets.QTableWidgetItem()item3.setText("打卡结果")self.table.setHorizontalHeaderItem(3, item3)# '工号'  '姓名'    '打卡时间'  '打卡结果'total = list(att)  # 将数据格式改为列表形式,其是将数据库中取出的数据整体改为列表形式for i in range(len(total)):total[i] = list(total[i])   #实现二维数组的转化for x in range(row):            #遍历每行for y in range(col):        #遍历每列item = QtWidgets.QTableWidgetItem()item.setText(str(total[x][y]))  #取用转化后的集合赋值self.table.setItem(x, y, item)

 执行程序查看结果:

至此一款简易的打卡系统基本完成。

(9) 写在最后

本例程尽量以便于理解的方式编写,有许多命名不规范和设计不合理之处。望诸君取其精华弃其糟粕(例如window1实为window_1,textedit拼写为texteidt等,后面项目里改正过来但没有在文档中订正),本文档中的所有参考网址可按住ctrl点击查看。 


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部