过去10多年中,我一直断断续续地学习和开发Web应用,对桌面应用开发技术的认识还停留在大学时学习的VB。近段时间在工作中产生了一个在单机上运行一个小型数据管理软件的需求,于是开始学习使用PyQt5技术,断断续续大概1周时间基本上弄出来了,现记录一下。
管理的数据只有一张表,10多个字段。要求:
1、能够根据其中的几个字段查询、筛选
2、能够添加数据,并对数据进行修改、删除等操作
3、能够把当前数据导出到Excel文件中。
4、数据列表能够翻页
包括一个主窗口和一个编辑数据的子窗口(内含系列表单控件)。具体内容不便展示
双击主窗口中某一行记录时,会弹出子窗口。或者点击“添加”按钮 也会弹出子窗口。但表单控件的内容不同。
import os
import sys
import time
import math
from PyQt5.QtWidgets import (QApplication, QDialog, QMainWindow, QMessageBox)
from PyQt5 import QtWidgets, QtCore, QtGui
from main_ui import Ui_MainWindow #这是利用PyQt5制作的主窗口界面,内含Ui_MainWindow类
from editDialog import editDialogClass#这是利用PyQt5制作的子窗口界面,内含editDialogClass类,具体为大量表单控件
import pymysql
from pymysql import cursors
import pandas as pd
#定义主窗口类,继承自PyQt5的QMainWindow类和自定义的主窗口界面Ui_MainWindow类
class MainWindowClass(QtWidgets.QMainWindow,Ui_MainWindow):
fulldata = () # 完整数据集
curpagedata = () # 当前页数据集
curpage = 1 # 当前页码
pagenum = 0 # 总页数
rowperpage = 10 # 每页行数
def __init__(self):
super().__init__()
self.setupUi(self) #初始化界面元素
self.showMaximized()#初始化时窗口最大化
#初始化加载全部数据
self.getData()
self.arrangeDateEnd.setDate(QtCore.QDate.currentDate()) #修改一个日期控件中的显示值为当前日期
#定义按钮功能
self.addBtn.clicked.connect(self.addRow) #添加数据
self.exitBtn.clicked.connect(self.exitSys)#退出系统
self.queryBtn.clicked.connect(self.queryData)#开始查询
self.clearBtn.clicked.connect(self.clearQuery)#清空查询条件
self.delBtn.clicked.connect(self.delData)#删除数据
self.summaryBtn.clicked.connect(self.makeSummary)
self.exportBtn.clicked.connect(self.exportData)#导出数据
#翻页按钮
self.firstBtn.clicked.connect(self.gotoFirstPage) #首页
self.lastBtn.clicked.connect(self.gotoLastPage) #末页
self.pageupBtn.clicked.connect(self.gotoPrePage)#上一页
self.pagedownBtn.clicked.connect(self.gotoNextPage)#下一页
#创建子窗口实例,窗口尺寸不可调整,标题栏保留关闭按钮
self.editDialogInstance=editDialogClass()
self.editDialogInstance.setFixedSize(self.editDialogInstance.width(), self.editDialogInstance.height())
self.editDialogInstance.setWindowFlags(QtCore.Qt.WindowCloseButtonHint)
self.editDialogInstance._signal.connect(self.getData) #父窗口接收子窗口返回值,刷新数据
#设置数据区域的列宽、行高
self.queryRecordResult.setColumnWidth(0, 60)
self.queryRecordResult.setColumnWidth(1, 100)
self.queryRecordResult.setColumnWidth(2, 150)
self.queryRecordResult.setColumnWidth(3, 150)
self.queryRecordResult.setColumnWidth(4, 150)
self.queryRecordResult.setColumnWidth(5, 200)
self.queryRecordResult.setColumnWidth(6, 100)
self.queryRecordResult.setColumnWidth(7, 370)
self.queryRecordResult.setColumnWidth(8, 100)
self.queryRecordResult.setColumnWidth(9, 100)
self.queryRecordResult.setColumnWidth(10, 100)
self.queryRecordResult.setColumnWidth(11, 100)
self.queryRecordResult.setColumnWidth(12, 100)
self.queryRecordResult.setColumnWidth(13, 100)
#设置单元格高度根据内容自动调整
self.queryRecordResult.verticalHeader().setSectionResizeMode(QtWidgets.QHeaderView.ResizeMode.ResizeToContents)
#在状态栏显示当前时间
timer = QtCore.QTimer(self)
timer.timeout.connect(self.showtime)
timer.start()
#创建数据库连接的方法
def connDB(self):
db = pymysql.connect(host='localhost', user='root', password='mypwd', database='cluerecord')
return db
#获取当前日期时间的方法
def showtime(self):
datetime = QtCore.QDateTime.currentDateTime()
text = datetime.toString("yyyy年MM月dd日 HH时mm分ss秒")
self.statusbar.showMessage("当前日期时间:" + text, 0)
#执行数据查询的方法,默认仅显示第1页数据
def queryData(self):
sql="select clueid,cluenum,cluesource,cluesourceunit,objname,objpost,objpostclass,left(content,50),left(receivedate,10),judge,arrangetype,receivedept,left(arrangedate,10),remark from records where 1=1"
if self.clueSource.currentText()!="不限":
sql+=" and cluesource ='"+self.clueSource.currentText()+"'"
if self.objName.text()!="":
sql+=" and objname ='"+self.objName.text()+"'"
if self.objPost.text()!="":
sql+=" and objpost like'%"+self.objPost.text()+"%'"
if self.content.text()!="":
sql+=" and content like'%"+self.content.text()+"%'"
if self.arrangeDateBegin.text()!="" and self.arrangeDateEnd.text()!="":
sql+=" and arrangedate between '"+self.arrangeDateBegin.text()+"' and '"+self.arrangeDateEnd.text()+"'"
if self.arrangeType.currentText()!="不限":
sql+=" and arrangetype ='"+self.arrangeType.currentText()+"'"
if self.receiveDept.text()!="":
sql+="and receivedept like'%"+self.receiveDept.text()+"%'"
sql+=" order by cluenum desc"
db = self.connDB()
cursor = db.cursor()
cursor.execute(sql)
data = cursor.fetchall()
self.fulldata = data
self.queryRecordResult.setRowCount(10)
self.curpage = 1
self.showCurPage()
cursor.close()
db.close()
self.queryRecordResult.doubleClicked.connect(self.updateItem) #为tablewidget添加双击行弹出子窗口进行修改的功能
#清空查询条件的方法
def clearQuery(self):
self.clueSource.setCurrentText("不限")
self.objName.setText("")
self.objPost.setText("")
self.content.setText("")
self.arrangeDateBegin.setDate(QtCore.QDate(2023, 1, 1)) #把一个日期控件的显示值调整为2023年1月1日
self.arrangeDateEnd.setDate(QtCore.QDate.currentDate())#把一个日期控件的显示值调整为当前日期
self.arrangeType.setCurrentText("不限")
self.receiveDept.setText("")
self.getData() #调用方法,恢复显示全部数据
# 显示全部数据的方法
def getData(self):
try:
db = self.connDB()
cursor = db.cursor()
cursor.execute("select clueid,cluenum,cluesource,cluesourceunit,objname,objpost,objpostclass,left(content,50),left(receivedate,10),judge,arrangetype,receivedept,left(arrangedate,10),remark from records order by cluenum desc")
data = cursor.fetchall()
self.fulldata=data
self.queryRecordResult.setRowCount(10)
self.curpage=1
self.showCurPage()
self.queryRecordResult.doubleClicked.connect(self.updateItem) # 为tablewidget添加修改功能
cursor.close()
db.close()
except:
QMessageBox.warning(self,"警告!","数据库异常,请确认MySQL数据库服务已启动。")
# 点击添加按钮的方法,主要是根据数据库中已有编号自动生成要添加的记录编辑,以避免人工进行编号可能造成的重复
def addRow(self):
#控制子窗口部分控件的属性
self.editDialogInstance.show()
self.editDialogInstance.acttype.setTitle("添加")
self.editDialogInstance.clueId.setText("0")
# 自动生成新增记录编号
sql = "select * from records order by cluenum desc limit 1"
db = pymysql.connect(host='localhost', user='root', password='mypwd', database='cluerecord',
cursorclass=pymysql.cursors.DictCursor)
cursor = db.cursor()
cursor.execute(sql)
rs = cursor.fetchall()
lastClueNum = rs[0]['cluenum']
lastClueNumRight3 = int(lastClueNum[-3:])
if lastClueNumRight3 < 10:
lastClueNumRight3 = "00" + str(lastClueNumRight3 + 1)
elif lastClueNumRight3 < 100:
lastClueNumRight3 = "0" + str(lastClueNumRight3 + 1)
else:
lastClueNumRight3 = str(lastClueNumRight3 + 1)
curYear = time.localtime(time.time())[0]
curClueNum = "XS" + str(curYear) + str(lastClueNumRight3)
#表单控件清空
self.editDialogInstance.clueNum.setReadOnly(False)
self.editDialogInstance.clueNum.setText(curClueNum)
self.editDialogInstance.clueSource.setCurrentIndex(0)
self.editDialogInstance.clueSourceUnit.setText("")
self.editDialogInstance.objName.setText("")
self.editDialogInstance.objPost.setText("")
self.editDialogInstance.objPostClass.setText("")
self.editDialogInstance.content.setPlainText("")
self.editDialogInstance.receiveDate.setDate(QtCore.QDate.currentDate())
self.editDialogInstance.judge.setText("")
self.editDialogInstance.arrangeType.setCurrentIndex(0)
self.editDialogInstance.receiveDept.setText("")
self.editDialogInstance.arrangeDate.setDate(QtCore.QDate.currentDate())
self.editDialogInstance.remark.setText("")
#双击某记录的方法
def updateItem(self):
# 获取当前行的id
row_select = self.queryRecordResult.selectedItems()
selectedId=row_select[0].text()
#设置子窗口的属性值
self.editDialogInstance.acttype.setTitle('修改')
self.editDialogInstance.clueId.setText(selectedId)
#根据当前记录id查询记录内容,并显示到表单控件中
db = pymysql.connect(host='localhost', user='root', password='mypwd', database='cluerecord',cursorclass=pymysql.cursors.DictCursor)
cursor = db.cursor()
cursor.execute("select * from records where clueid="+selectedId)
rs=cursor.fetchall()
self.editDialogInstance.clueNum.setText(rs[0]['cluenum'])
self.editDialogInstance.clueNum.setReadOnly(True)
self.editDialogInstance.clueSource.setCurrentText(rs[0]['cluesource'])
self.editDialogInstance.clueSourceUnit.setText(rs[0]['cluesourceunit'])
self.editDialogInstance.objName.setText(rs[0]['objname'])
self.editDialogInstance.objPost.setText(rs[0]['objpost'])
self.editDialogInstance.objPostClass.setText(rs[0]['objpostclass'])
self.editDialogInstance.content.setPlainText(rs[0]['content'])
if rs[0]['receivedate'] != None:
self.editDialogInstance.receiveDate.setDate(rs[0]['receivedate'])
self.editDialogInstance.judge.setText(rs[0]['judge'])
if rs[0]['arrangetype'] != None:
self.editDialogInstance.arrangeType.setCurrentText(rs[0]['arrangetype'])
self.editDialogInstance.receiveDept.setText(rs[0]['receivedept'])
if rs[0]['arrangedate'] != None:
self.editDialogInstance.arrangeDate.setDate(rs[0]['arrangedate'])
self.editDialogInstance.remark.setText(rs[0]['remark'])
self.editDialogInstance.show()
cursor.close()
db.close()
# 删除记录的方法
def delData(self):
row_select = self.queryRecordResult.selectedItems()
if len(row_select)==0:
QMessageBox.warning(self, "警告", "请先选中您需要删除的记录行!")
else:
selectedId = row_select[0].text()
userselected=QMessageBox.question(self,"确认","您确定要删除编号为【"+row_select[1].text()+"】的记录吗?\n\n注意:删除操作不可恢复,请慎重!")
if userselected == QMessageBox.Yes:
db = self.connDB()
cursor = db.cursor()
cursor.execute("delete from records where clueid="+row_select[0].text())
db.commit()
cursor.close()
db.close()
self.getData()
# 导出数据的方法
def exportData(self):
#生成文件路径和文件名
datetime = QtCore.QDateTime.currentDateTime()
datetimestr = datetime.toString("yyyy-MM-dd HH-mm-ss")
filepath=os.getcwd()+'\\exportdata\\'
filename=datetimestr+"导出数据.xlsx"
#将当前表中数据注入到一个Excel文件中
# 创建写文件的句柄
writer = pd.ExcelWriter(filepath+filename)
#表头字段
header=['流水号','编号','来源','单位部门','姓名','职务','职级','内容摘要','接收时间','研判情况','处理方式','承办部门','处理日期','备注']
#根据表格数据和表头字段生成数据帧
df = pd.DataFrame(self.fulldata, columns=header)
#把数据帧写入工作表
df.to_excel(writer, sheet_name='Sheet1', index=False)
# 保存文件
writer.save()
#导出成功后弹出一个是否立即打开的确认框
selectedAct = QMessageBox.question(self, "确认", "数据导出成功,是否立即打开文件所在目录?")
print(filepath)
if selectedAct == QMessageBox.Yes:
os.system(f'explorer /select, {filepath+filename}') #此方法仅可用于Windows系统
#展示当前页数据的方法,分页的核心
def showCurPage(self):
#获取记录总条数
self.totalRsCount.setText(str(len(self.fulldata)))
#如果记录总条数小于每页记录数,说明总记录不满1页,则将总页数设置为1
if len(self.fulldata)<self.rowperpage:
self.pagenum=1
#否则,用总记录数除以每页记录数,向上取整可得到总页数
else:
self.pagenum=math.ceil(len(self.fulldata)/self.rowperpage)
#在界面上显示相应的分页状态数据
self.totalPageCount.setText(str(self.pagenum))
self.curPage.setText(str(self.curpage))
#计算当前页的起始记录行号和结束记录行号
beginrow=(self.curpage-1)*self.rowperpage #当前页码数减1,乘以每页记录数
endrow=self.curpage*self.rowperpage #当前页码数乘以每页记录数
#根据上述计算得到当前页数据,并显示到表格中
self.curpagedata=self.fulldata[beginrow:endrow];
self.queryRecordResult.clearContents() #先清空表格已有内容
#利用循环重新为表格注入数据
x = 0
for i in self.curpagedata:
y = 0
for j in i:
self.queryRecordResult.setItem(x, y, QtWidgets.QTableWidgetItem(str(self.curpagedata[x][y])))
y = y + 1
x = x + 1
#跳转至首页的方法
def gotoFirstPage(self):
self.curpage=1;
self.showCurPage()
#跳转至末页的方法
def gotoLastPage(self):
self.curpage = self.pagenum;
self.showCurPage()
#跳转至上一页的方法
def gotoPrePage(self):
if self.curpage==1:
QMessageBox.warning(self,"消息","当前已是第1页!")
else:
self.curpage = self.curpage-1;
self.showCurPage()
#跳转至下一页的方法
def gotoNextPage(self):
if self.curpage == self.pagenum:
QMessageBox.warning(self, "消息", "当前已是最后1页!")
else:
self.curpage = self.curpage + 1;
self.showCurPage()
#退出系统的方法
def exitSys(self):
self.close()
if __name__ == '__main__':
#入口方法,创建实例
app = QApplication(sys.argv)
myMainWindowInstance = MainWindowClass()
sys.exit(app.exec())
import sys
from PyQt5.QtWidgets import (QApplication, QDialog, QMainWindow, QMessageBox)
from PyQt5 import QtWidgets, QtCore, QtGui
from editDialog_ui import Ui_editWindow
import pymysql
from pymysql import cursors
class editDialogClass(QMainWindow,Ui_editWindow):
# 定义信号,用于向父窗口返回参数
_signal = QtCore.pyqtSignal(str)
def __init__(self):
super(editDialogClass, self).__init__()
self.setupUi(self)
self.retranslateUi(self)
#定义保存按钮
self.saveBtn.clicked.connect(self.saveData)
#定义取消按钮
self.exitBtn.clicked.connect(self.close)
#执行保存的方法
def saveData(self):
#如果执行的是新增
if self.acttype.title()=="添加":
# 进行简单表单验证
if self.clueNum.text()=="" or self.clueSource.currentText()=="" or self.objName.text()=="" or self.objPost.text()=="":
QMessageBox.warning(self,"警告","请完整填写各项内容!")
else:
# 先查询编号是否重复
sql="select count(cluenum) from records where cluenum='"+self.clueNum.text()+"'";
db = pymysql.connect(host='localhost', user='root', password='mypwd', database='cluerecord')
cursor = db.cursor()
cursor.execute(sql)
rs = cursor.fetchall()
if rs[0][0]>0:
QMessageBox.warning(self, "警告", "您填写的编号已经存在,请更正后重试!")
cursor.close()
db.close()
else:
cursor.close()
db.close()
sql="insert into records (clueid,cluenum,cluesource,cluesourceunit,objname,objpost,objpostclass,content,receivedate,judge,arrangetype,receivedept,arrangedate,remark) "
sql+="values(null,'"+self.clueNum.text()+"','"+self.clueSource.currentText()+"','"+self.clueSourceUnit.text()+"','"+self.objName.text()+"','"+self.objPost.text()+"','"+self.objPostClass.text()+"','"+self.content.toPlainText()+"',"
sql+="'"+self.receiveDate.text()+"','"+self.judge.text()+"','"+self.arrangeType.currentText()+"','"+self.receiveDept.text()+"','"+self.arrangeDate.text()+"','"+self.remark.text()+"')"
db = pymysql.connect(host='localhost', user='root', password='mypwd', database='cluerecord')
cursor = db.cursor()
cursor.execute(sql)
db.commit()
QMessageBox.information(self, "提示", "添加成功!")
cursor.close()
db.close()
self._signal.emit("ok") #向主窗口发送信号
self.hide()
#如果执行的是修改
elif self.acttype.title()=="修改":
#进行简单表单验证
if self.clueNum.text()=="" or self.clueSource.currentText()=="" or self.objName.text()=="" or self.objPost.text()=="":
QMessageBox.warning(self,"警告","请完整填写各项内容!")
else:
sql="update records set cluesource='"+self.clueSource.currentText()+"',objname='"+self.objName.text()+"',objpost='"+self.objPost.text()+"',"
sql += "cluesourceunit='" + self.clueSourceUnit.text()+"',"
sql += "objpostclass='" + self.objPostClass.text()+"',"
sql += "content='" + self.content.toPlainText()+"',"
sql += "receivedate='" + self.receiveDate.text() + "',"
sql += "judge='" + self.judge.text() + "',"
sql += "arrangetype='" + self.arrangeType.currentText() + "',"
sql += "receivedept='" + self.receiveDept.text() + "',"
sql += "arrangedate='" + self.arrangeDate.text() + "',"
sql += "remark='" + self.remark.text() + "'"
sql += " where clueid=" + self.clueId.text()
db = pymysql.connect(host='localhost', user='root', password='mypwd', database='cluerecord')
cursor = db.cursor()
cursor.execute(sql)
db.commit()
QMessageBox.information(self, "提示", "修改成功!")
cursor.close()
db.close()
self._signal.emit("ok") #向主窗口发送信号
self.hide()
第1步:把数据库环境搭建好
第2步:在PyCharm中配好PyQt5和PyUIC,能够顺利打开Qt Designer界面设计器,并编译为Python代码
第3步:创建窗口
第4步:创建父类,引入窗口类,编写相应的方法
第5步:创建子类,引入窗口类,编写相应的方法,并把子类import到父类中
第6步:功能的调试、测试