参考教程:基于PyQt5的数据库可视化操作界面
MainWindow:生成的一个类,下面有setupUi和retranslateUi两个方法。
接下来是登录操作。教程中用的是pymssql,我们用pyodbc,目测应该差不多。
import pyodbc
cnxn = pyodbc.connect('DRIVER={SQL Server};SERVER=localhost;DATABASE=register;UID=sa;PWD=gslsgsls')
教程上说:“本项目的意义在于简单的图像化操作代替数据库语言来实现数据的增删查改。考虑到绘制的问题,采用的简单的单一界面交互。对于多界面交互则需要绘制多个ui文件并在主程序中调用。”可见一个ui文件对应一个界面
现在我们开始读代码。
教程中的第一段代码是主程序。主程序很短,主要就是用来调用另一个文件的:
# HospitalRegSys0045.py
from PyQt5 import QtCore, QtGui, QtWidgets
from MainWindow import Ui_MainWindow
import decimal
class mwindow(QtWidgets.QMainWindow,Ui_MainWindow):
def __init__(self):
super(mwindow,self).__init__()
self.setupUi(self)
if __name__=="__main__":
import sys
app=QtWidgets.QApplication(sys.argv)
mshow=mwindow()
mshow.show()
sys.exit(app.exec_())
这个教程中主程序的写法和痞子衡嵌入式:超级好用的可视化PyQt GUI构建工具(Qt Designer)中基本一致,区别在于前者只是简单使用,估计把信号和槽放在窗口代码中了,后者把信号和槽放在主程序中。另外它import decimal好像是为了之后打包成exe用的。
第二段是主窗口代码。之后代码在上解释在下。
from PyQt5 import QtCore, QtGui, QtWidgets
import pymssql
import time
我们应该是import pyodbc而不是pymssql
i_id = int(time.time()%1000 - time.time()%100)*10 # 默认ID基于时间,并会自动在挂号后增加1
if i_id == 0:
i_id == 1000 # 避开ID等于0的情况
logmsg = '' # 用于保存Log
对保存log不太理解。i_id是随机生成的1-1000的整数。
class Ui_MainWindow(object):
def setupUi(self, MainWindow): # 由UI文件自动生成,再将自定义connect信号
# Designer自动生成的代码,见附录
自动生成的setupUi和retranslateUi都原封不动地保存。
def Auto_ID(self): # ID自动编号
global i_id
self.lineEdit_ID.setText(str(i_id))
lineEdit_ID,一个LineEdit。单行文本框。setText是Edit有的一种函数,可以在文本框中显示具体内容。这里显示的就是i_id。
setupUi函数中保存了关于这个元件的信息。
def DB_checkDoc(self):
global logmsg
logmsg = ''
strDp = self.comboBox_Dept.currentText()
strDp是组合框的当前值。
if self.checkBox_isExpertRequired.isChecked():
isExpt = 1
isExptzh = '是'
else:
isExpt = 0
isExptzh = '否
用1或0来表示是否需要专家。isChecked()是checkBox复选框类的函数,返回布尔值,表示这个复选框是否被选中。
我发现一个命名规则,往往都是"类_功能"成为一个元件的名字。
Msg = '科室: ' + strDp + ' 是否专家: ' + isExptzh + '\n查询到如下医生'
self.textBrowser.setText(Msg)
textBrowser和textedit有什么区别呢?
# 构造SQL语句
sql = "SELECT * FROM doctor where Dept ='" + strDp + "' and ON_DUTY = 'Y' and Expert = " + str(isExpt)
if strDp == '全部':
sql = "SELECT * FROM doctor where ON_DUTY = 'Y' and Expert = " + str(isExpt)
print(sql)
sql是储存用来查询的sql语句的字符串
# 连接到数据库
server = "DESKTOP-9RUT87E"
user = "Hospital"
password = "123456"
#print('Connecting to MSSQL...')
conn = pymssql.connect(server, user, password, database="Hospital", charset='utf8')
print('Connected')
啥时候连都行,早就可以连了。
# 数据库操作
logmsg += Msg + "\n" + sql +"\n"
cursor = conn.cursor()
cursor.execute(sql)
row = cursor.fetchone()
while row:
print(row)
self.textBrowser.append("%s\t%s\t%8s\t%s" %(row[0], row[1], row[2], row[3]))
logmsg += "%s\t%s\t%8s\t%s\n" %(row[0], row[1], row[2], row[3])
row = cursor.fetchone()
conn.close()
self.statusbar.showMessage('查询成功', 1000)
现在要查询并在textBrowser上显示结果了。logmsg字符串看起来好像没有什么用,只是一个全程的记录?
fetchone()语句在遇到空值时返回null。
append和settext的区别:前者是在原基础上增加显示,适合while循环。
statusbar是状态栏类,但是我不知道statusbar到底在哪(图上没有)。不管怎么说,statusbar类有showMessage方法,第一个参数是显示的字符串,第二个参数是状态停留的时间,单位毫秒。默认是0,即在下一个状态出现之前一直显示。
def DB_insert(self): # 挂号操作
global i_id
global logmsg
logmsg = ''
flag = 0 # 用于处理异常
需要几个变量就用几个,这里global了i_id和logmsg。但是这样不会把logmsg清空吗?之前记录的logmsg还有什么用?
# 获取输入
strID = self.lineEdit_ID.text()
strName = self.lineEdit_Name.text()
strNo = self.lineEdit_no.text()
strAge = self.lineEdit_Age.text()
strDocNo = self.lineEdit_DocNo.text()
strDp = self.comboBox_Dept.currentText()
把所有的信息都用text()方法和currentText()方法赋值给局部变量们。
ID:随机领取的号码
Name:患者姓名
No:患者的健康号
Age:患者年龄
DocNo:填写的医师号
Dp:科室
if self.radioButton_Man.isChecked():
strGender = '男'
i_iGender = 1
else:
strGender = '女'
i_iGender = 1
radioButton的特点是选项互斥,有isChecked()方法。
Msg = '正在挂号'
self.textBrowser.setText(Msg)
#连接到数据库
server = "DESKTOP-9RUT87E"
user = "Hospital"
password = "123456"
print('Connecting to MSSQL...')
conn = pymssql.connect(server, user, password, database="Hospital", charset='utf8')
print('Connected...')
cursor = conn.cursor()
# 构造SQL语句
sql = "insert into registration values('" + strID +"', '"+strName+"', '"+strNo+"', '"+strGender+"', '"+strAge+"', '"+strDp+"', '"+strDocNo+"')"
# print(sql)
logmsg += Msg +"\n" + sql
# 数据库操作
try:
cursor.execute(sql)
conn.commit()
conn.close()
self.textBrowser.append("\n挂号成功")
logmsg += "\n挂号成功"
self.textBrowser.append(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())))
# 清除填入的信息
self.lineEdit_Name.clear()
self.lineEdit_ID.clear()
self.lineEdit_DocNo.clear()
self.lineEdit_Age.clear()
self.lineEdit_no.clear()
self.radioButton_temp.setChecked(1)
self.statusbar.showMessage('挂号成功', 2000)
i_id += 1
#self.lineEdit_ID.setText(str(i_id))
flag = 1
except Exception: #处理异常
if flag == 0:
self.textBrowser.append("挂号失败,检查输入")
self.statusbar.showMessage('插入异常', 2000)
logmsg += '插入异常'
修改命令在执行之后必须加一句commit()才可以真正修改数据库。
clear()是edit的方法,可以清除文本框内的信息
statusbar之前是用来显示“查询成功”的,现在还可以用来显示“插入异常”。
参考教程:Qt信号与槽机制详解
补充的补充:pyqt5是Qt的python版,qtdesigner是pyqt5_tool下的一个应用程序,是pyqt5的拓展(应该是这样吧)。
信号(Signal)就是在特定情况下被发射的事件,例如PushButton 最常见的信号就是鼠标单击时发射的 clicked() 信号,一个 ComboBox 最常见的信号是选择的列表项变化时发射的 CurrentIndexChanged() 信号。
PushButton指的是按钮类,这个类的全名叫QPushButton。ComboBox是下拉选项框(组合框)类,全名叫QComboBox。
如果触发事件是点击按钮,mainwin(主函数中继承了两个类的类)的定义会是这样的
class mainWin(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(mainWin, self).__init__(parent)
self.setupUi(self)
# 将响应函数绑定到指定Button
self.ok_Button.clicked.connect(self.showMessage)
ok_Button是按钮的名字。
最后一行ok_Button.clicked.connect(),作用是在点击按钮时,会调用括号内的self.showMessage函数。这一行中的clicked和教程中讲的clicked()信号对应。
槽(Slot)就是对信号响应的函数。槽就是一个函数,与一般的C++函数是一样的,可以定义在类的任何部分(public、private 或 protected),可以具有任何参数,也可以被直接调用。槽函数与一般的函数不同的是:槽函数可以与一个信号关联,当信号被发射时,关联的槽函数被自动执行。
(呃,为什么我不管做什么最后都会回归到重新学python)
count = 1
def cc():
count = count+1
cc()
这样会报错,因为count是局部变量。在cc函数内,使用前必须还得赋值。
global语句可以声明一个或多个变量为全局变量。该声明仅在当前代码块中有效。
def cc():
global count
count = count+1
print(count)
cc()
2
像上面这样就可以了。
try:
正常的操作
…
except:
发生异常,执行这块代码
…
else:
如果没有异常执行这块代码