前面的教程中有讲过对于python自带的Tk模块的使用,实际上个人感觉Tk的使用不仅繁琐,而且做出来的界面并不美观,这里向大家推荐pyqt。
qyqt这个模块的最大优势在于界面可以使用qtdesigner直观的制作,然后代码直接读取就可以,省去了繁琐的排版等等步骤,而且界面的美观程度也是tk所无法达到的。
我个人比较喜欢qtdesigner制作界面,在qtdesigner上对界面进行修改,这样的话可以省去很多修改和调试的时间,当然如果你用qt熟练的话据说直接写代码的效率并不比用qtdesigner高,并且更灵活,这个就看个人喜好了。
用qtdesigner的时候最好在制作完界面后对界面进行布局,当然你不使用布局也是可以运行的,但是在别的电脑,分辨率不同的情况下会导致软件显示出问题,并且无法根据界面拉伸来解决。如果你布局过,那么可以使用界面的伸缩让界面自己重新排布。至于排版的方法就是选中你要布局的目标然后点击布局,在布局中你可以多运用Horizontal Spacer 和vertical spacer。一般我会一行一行进行水平布局之后再整体进行垂直布局。
首先你需要用qtdesigner制作一个ui界面,然后通过python代码进行界面的读取。
对于UI文件的可视化操作这里有两种办法:
第一种:直接在python文件上读取QTDesigner创建的UI文件
直接读取ui文件,假设我的ui文件名为"SL_manage.ui",那么我的python代码应该是:
#MainUi.py
from PyQt5 import QtWidgets, uic,QtCore,QtGui
import os
path = os.getcwd()
qtCreatorFile = path +os.sep+"ui"+os.sep+"SL_manage.ui" # Enter file here.
Ui_MainWindow, QtBaseClass = uic.loadUiType(qtCreatorFile)
#_translate = QtCore.QCoreApplication.translate#
_translate = QtCore.QCoreApplication.translate这个主要用于后期的UI界面文字字体以及颜色便捷的更改
创建一个对象,该对象就是你的整个窗体:
class MainUi(QtWidgets.QMainWindow, Ui_MainWindow):
#这里的第一个变量是你该窗口的类型,第二个是该窗口对象。
#这里是主窗口类型。所以设置成当QtWidgets.QMainWindow。
#你的窗口是一个会话框时你需要设置成:QtWidgets.QDialog
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
Ui_MainWindow.__init__(self)
self.setupUi(self)
至此,窗体对象已经创建完毕,接着,我们需要用一个函数取创建这个对象并将他显示出来,那么就需要下列代码:
#文件名: ShowUi.py
from PyQt5 import QtWidgets, uic,QtCore,QtGui
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = MainUi() #创建窗体对象
window.show() #窗体显示
sys.exit(app.exec_())
如果你的程序还有新的窗口,你可以用同样的方法在继续写一个窗体对象,
然后在你的ShowUi.py中进行对象的创建,甚至于在我们的MainUi中创建也是可以的。
第二种办法是将UI文件直接转换成py文件:
将UI文件转化成python文件,然后进行相同的对象调用。方法实际上和前者是一样的,这里首先来说明一下如何转换ui文件:
我用的编译器是pycharm,在pycharm的工具栏类有个外部工具,你可以将指令填入外部工具,这样,你只需要将UI文件拖入pacharm然后运行你这个工具就可以自动生成python文件,这样可以省去反复敲指令的麻烦。配置如图:在工具栏的外部工具中。
配置如图,名称:自定义。
程序栏写入python所在目录下的python.exe文件。参数则是:-m PyQt5.uic.pyuic $FileName$ -o $FileNameWithoutExtension$.py
就是生成一个与当前操作文件相同名字的python文件。
工作目录则是$FileDir$,意思为当前文件所在目录
当然如果你不想使用这个工具,你就喜欢自己手动敲指令,那么你就打开你的cmd,或者终端,然后输入
python -m 路径+PyQt5.uic.pyuic ui文件路径+名字 -o 输出的py文件路径加名字
到这里你就会生成一个python文件,但这个python文件实际上还是不能直接生成窗口的,需要你进行代码的可视化操作,操作方法如下:
假设我生成了一个叫"main_menu.py"的python文件,文件内容大抵为这样:
文件名:main_menu.py:
在这里我们可以看到,这个python文件创建了一个叫UI_MainWindow的对象,这时你应该已经猜到了,这个对象正是我们所制作的界面,也就是说我们的UI文件被QT的一个组件翻译成了一个python对象。我们接下来所需要做的,就是在python文件中引用这个对象,引用方式如下:
#首先你需要在引用的python文件内导入该对象所在的文件,也就是main_menu.py
import main_menu #导入该对象所在文件
Ui_MainWindow = main_menu.Ui_MainWindow#指定Ui_MainWindow 为main_menu文件下的Ui_MainWindow对象。
class CoperQt(QtWidgets.QMainWindow,Ui_MainWindow):#创建一个Qt对象
#这里的第一个变量是你该窗口的类型,第二个是该窗口对象。
#这里是主窗口类型。所以设置成当QtWidgets.QMainWindow。
#你的窗口是一个会话框时你需要设置成:QtWidgets.QDialog
def __init__(self):
QtWidgets.QMainWindow.__init__(self) # 创建主界面对象
Ui_MainWindow.__init__(self)#主界面对象初始化
self.setupUi(self) #配置主界面对象
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = CoperQt()#创建QT对象
window.show()#QT对象显示
sys.exit(app.exec_())
这样,你的python工程就可以脱离UI文件直接进行使用了,好处就是在打包成EXE文件的时候你不用给他搭配上UI文件。
关于Qt的操作我这里简单说几个比较常用而且典型的,如果需要深入那就需要你们你几查阅相关手册文档了,我对于这类工具基本就是够用就行,并不是我的主业:
按钮
按钮的触发在QT里用的是一个槽和信号的概念,这个概念我们实际上不用想的那么复杂,无非也就是将按钮的事件绑定一个函数,当事件触发,那么函数就运行,模板代码如下:
self.Button0.clicked.connect(self.start_find) # button0的点击事件绑定start_find函数
关于绑定函数的传参除了可以使用全局变量还可以使用python的lambda,如我要传入x:
self.Button0.clicked.connect(command=lambda:button_process(x))
定时器
接着是Qt定时器,这个组件也特别实用,可以让你的界面定时的去执行某个函数,概念基本上就跟单片机的中断是一样的。
比如我在窗口显示一个时间,我需要他每一秒都更新窗口的时间信息,那么我就需要设置一个窗口定时器,代码如下:
self.testTimer = QtCore.QTimer() # 创建定时器
self.testTimer.timeout.connect(self.show_time) # 定时超时事件绑定show_time这个函数
self.testTimer.start(1000) #定时器每一秒执行一次
下面是show_time这个函数
def show_time(self):
self.time_now = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))#
同理我们也可以把别的函数绑定到定时器上,让他定时运行。道理也是一样的
下拉选项栏配置
下拉选项栏在UI制作的时候也非常的常用,他的配置也不难,假设下拉选项栏是self.comboBox,代码如下:
选项栏有两个标识,一个是每一栏的编号index(从0开始),一个是每一栏的文本内容text:
self.comboBox.insertItem(0, self.tr("None")) # 第一个选项插入空
self.comboBox.insertItem(0, "x") 第一个选项插入x字符
self.comboBox.currentText() 读取选项栏文本:
self.comboBox.currentIndex() 读取选项栏编号:
self.comboBox.setCurrentIndex() 设置当前选项栏显示位置:(通过编号)
self.comboBox.setCurrentText() 设置当前选项栏显示位置:(通过文本)
self.comboBox.clear() 清空当前选项框内元素
这里有个小技巧,如何让选项栏选中后不用按按钮就触发事件。实际上就是运用到上面的定时器,你可以将定时器绑定上选项栏读取函数,这个函数每隔200MS或者1s读取选项栏的数值,这样,用户的只要更换选项立马能够做出反映。
会话窗:以下是最简单的会话窗代码:
header:会话窗标题
info:会话窗内容
def show_message(self,header = "说明",info = "无"):
QtWidgets.QMessageBox.information(self,header,info,QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
# 使用infomation信息框
#判断按键(显示两个按键,是和否)
QtWidgets.QMessageBox.Yes|QtWidgets.QMessageBox.No)
#确定按键(显示一个按键,OK)
# QtWidgets.QMessageBox.Ok
文件操作
文件操作的话实际上和TK基本类似,就是调用窗口来读取用户选取的文件路径以及名称,接着用户自己调用os函数去进行文件操作。
首先是打开文件,基本代码如下:
def open_file(self):
fileName1, filetype =QtWidgets.QFileDialog.getOpenFileName(self,
"请打开EXCEL文件", path,"Text Files (*.xlsx;*.xls)") # 设置文件扩展名过滤,
# 注意用双分号间隔
if len(fileName1) == 0:
return
fileName1 = fileName1.replace('/', "\\")#win下需要进行文件分隔符转换
my_excel = EasyExcel(filename=fileName1)#这是我自己写的excel读取程序这里创建一个excel读取对象
self.data_list = my_excel.get_content()#读取excel的内容,返回是一个字典列表
QtWidgets.QMessageBox.information(self, # 使用infomation信息框
"说明", "数据加载成功", QtWidgets.QMessageBox.Ok)
#会话窗提醒,数据读取成功
接下来是保存文件:
def save_file(self): #只是获取路径,实际保存需要调用OS模块
fileName2, ok2 = QtWidgets.QFileDialog.getSaveFileName(self,
"文件保存","C:/","Text Files (*.xlsx);;Text Files (*.xls);;All Files (*)")
#地址分隔符更改,QT获取的地址分隔符为/,而python为\\,需要替换
if not fileName2:
return
fileName2 = fileName2.replace('/',"\\")
#获取到文件路径,接下来的操作就需要你自己填写
表格
表格作为一个可以输入和输出的组件,在做工具的时候也非常的实用,他的基本使用方法如下:
self.tableWidget.setHorizontalHeaderLabels(key_list)#设置表格表头数据
self.tableWidget.setColumnCount(x) #设置表格的列数
self.tableWidget.setRowCount(x) #设置表格的行数
self.tableWidget.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) #表格设置成大小随内容改变
self.tableWidget.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) # 表格设置成只读
self.tableWidget.setSelectionBehavior(QTableWidget.SelectRows)#选择行
self.table.setSelectionMode(QTableWidget.SingleSelection)#选择单个行
self.tableWidget.setAlternatingRowColors(True)#隔行改变颜色
self.tableWidget.verticalHeader().sectionClicked.connect(self.VerSectionClicked) # 表头单击信号
self.tableWidget.horizontalHeader().sectionClicked.connect(self.HorSectionClicked) # 表头单击信号
self.tableWidget.rowCount()#返回表格的行数
self.tableWidget.columnCount()#返回表格的列数
self.tableWidget.item(row,clo ).text()#获取row行clo列的表格数据,从0开始
self.tableWidget.clear()#表格清空,不清空的话会永远残留
QTableWidget.clearContents() 只清除表项,不清除表头。
self.tableWidget.setItem(row,col, QTableWidgetItem("content"))#设置表格内容为字符串"content"
self.tableWidget.insertColumn(self, int column) 在某一列插入新的一列。
self.tableWidget.insertRow(self, int row)在某一行插入新的一行。
self.tableWidget.removeColumn(self, int column) 移除column列及其内容。
QTableWidget.removeRow(self, int row)移除第row行及其内容。
QTableWidget.setShowGrid(False)#不显示分割线
QTableWidget.hideRow(),hideColumn(),showRow(),showColumn()#隐藏相应的行或者列
如何在TableWidget内添加其他组件;下面额例程是讲timeEdit组件添加进TableWidget中
self.timeEdit = QtWidgets.QTimeEdit(Dialog)#创建一个timeEdit
self.tableWidget.setCellWidget(0, 0, self.timeEdit)#把timeedit添加进tableWidget内
单选框
单选框在实际中我们也经常用到,比如写一个软件的登陆界面的时候,单选框可以设置为自动登陆,记住密码等等
单选框的用法如下:
self.radioButton.clicked.connect(self.change_radio) # 单选按钮选中绑定函数
self.radioButton.hide() # 单选框2隐藏
self.radioButton.setChecked(True)#单选框选中,反之False
self.radioButton.isChecked() #单选框是否选中,选中返回True反之False
单选框补充:
单选框这里要注意,就是单独的单选框并不能正常使用,假设我创建了个单选框用作记住密码按钮,实际上我点一下单选框则选中记住密码,但是再点一下他仍然是选中状态并不会取消,因为系统默认规定必须有且只有一个单选框被选中。所以这里教大家一个小诀窍,那就是多创建一个单选框,然后将它隐藏掉,这样就可以让两个交替被选中,从而达到点击选中,再点击取消的效果。
除此之外有个更好的办法就是不使用单选框,直接使用复选框
在QTdesigner里右键单选框选择变形为checkBox,则变成复选框,复选框和单选框的操作基本一样,唯一不同的是复选框可以允许一个界面上没有一个被选中,或者全部被选中,还有复选框是方形的,单选框是圆形的。
输入框
输入框主要用于人机交互中人的指令或者文本输入,他的基本操作方法是:
self.lineEdit.returnPressed.connect(self.check_info) # 文本栏回车绑定函数
self.lineEdit.setEchoMode(QtWidgets.QLineEdit.Password)#设置成密码模式,也就是输入内容显示为实心的圆
self.lineEdit.setEchoMode(QtWidgets.QLineEdit.Normal)#设置成普通模式(默认)
self.lineEdit.setText(“hello world”) # 设置输入文本
self.lineEdit.text()#返回输入框文本内容
self.lineEdit.show()#显示文本框
self.lineEdit.hide()#隐藏文本框
self.lineEdit.clear()#清空文本框
进度条
进度条一般会和定时器一起使用,用定时器绑定一个函数去获取进度然后再改变进度条的值,内容如下:
self.pbar.setValue(self.step) #设置进度条进度 1则为1%
菜单栏
菜单栏主要功能无非就是绑定函数予以触发:
#Open_file是菜单栏内oepn_file这个按钮
self.Open_file.triggered.connect(self.open_file)
标题,标签
标题标签主要用于软件上的文字标题或者描述
self.label.setText( translate("MainWindow","
{}
" ).format(self.time_now))
标签补充:这里就说到了之前translate函数的作用,实际上因为我对QT不熟并且也没打算去多深入的学习,所以我在程序中更新Label的时候将原本在QTdesigner中设定的字体类型也直接复制过来,写入这个函数,不然你直接使用setText函数之后你会发现原来设置的字体格式全部会变成默认,当然你也可以不用translate函数,直接使用pyqt5内置的字体修改函数来修改。
timeEdit&&dateEdit&&datetimeEdit
self.timeEdit.setDisplayFormat('hh:mm')#设置时间格式
now_day = time.strftime("%Y-%m-%d", time.localtime())
self.dateEdit.setDate(QDate.fromString(now_day, 'yyyy-MM-dd'))
time = QtCore.QTime.toString(self.timeEdit.time(),"hh:mm")#获取时间便转化成字符串
now_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
self.dateTimeEdit.setDateTime(QDateTime.fromString(now_time, 'yyyy-MM-dd hh:mm:ss'))#
time = int(QtCore.QDateTime.toTime_t(self.dateTimeEdit.dateTime()))#获取时间并转化成时间戳
self.dateTimeEdit.setDateTime(QtCore.QDateTime.fromTime_t(时间戳))#通过时间戳设置timeedit显示的时间
self.dateTimeEdit.setMaximumDateTime(QtCore.QDateTime(QtCore.QDate(2106, 2, 7), QtCore.QTime(14, 28, 15)))#设置最大时间
在pyqt内创建一个线程
pyqt编程下经常会用到线程,因为有些耗时的操作,如果不放入线程中,则会引发qt界面的刷新卡顿,甚至没有反应。
(在这里友情提醒一下,多线程你也可以用python自带的thread模块做,但是因为gil锁,实际上只会用单核。但是qthread是用c++实现的,所以实际推荐用qthread来写多线程(如果条件允许))
class newThread(QtCore.QThread):
def __init__(self,parent=None):
super(readThread, self).__init__(parent)
def run(self):
while True:
print("hello")
time.sleep(10)
#如上操作创建了一个newThread的线程,
工作是每十秒打印一个hello。
当然如果想让线程开启,则需要在qt主程序内进行如下操作:
self.new_thread = newThread() #创建线程对象
self.new_thread.start() #开启。
#开启后你的qt就会起一个线程,
并且跑在run的while循环以内。
如果你想要中断这个线程。
那么就可以用标志位的形式从while(1)里面break出来。
在qt内给线程添加回调函数
def updateFigure():
try:
GetLastStatus = getLastStatus()#
GetLastStatus.finished_signal.connect(success)#回调绑定成功函数
GetLastStatus.error_signal.connect(error)#回调绑定失败函数
GetLastStatus.start()#程序开始
except Exception as e:
print(e)
def success()
print("Success")
def error()
print("error")
class getLastStatus(QThread):
finished_signal = pyqtSignal(str)#创建完成信号
error_signal = pyqtSignal(str)#创建错误信号
def __init__(self, parent=None):
super().__init__(parent)
def run(self):#重写内置运行函数
if self._test() == True:
self.finished_signal.emit('done')
#如果完成,发送完成信号
else:
self.error_signal.emit("error")
#如果出错,发送出错信号
def _test(self):
#return True
return False
在pyqt内嵌Matplotlib
首先创建一个名为MatplotMenu的UI文件,在该文件内添加一个QFrame组件,然后创建一个LineEdit和两个Button。效果如下图所示。然后,用上述的办法,将他生成PY文件。
接着,创建一个名为MatplotObject.py的文件,填入如下代码。
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import Ui.MatplotMenu as MatplotMenu #UI目录下的MatplotMenu
import matplotlib
matplotlib.use("Qt5Agg") # 声明使用QT5
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.dates as mdate
import datetime
Ui_MainDialog = MatplotMenu.Ui_Dialog
class MatplotMenu(QDialog,Ui_MainDialog):#创建一个会话框对象
def __init__(self):
super(MatplotMenu,self).__init__(parent)
self.title = 'Matplot测试'#设置窗体title
self.setupUi(self)
self._createCanvas()#创建画布
self._createLayouts()#配置组件
self.show()#显示组件
self._updateFigure()#更新曲线图
self.timer = QTimer(self)#创建定时更新定时器
self.timer.timeout.connect(self._updateFigure)#绑定更新函数
self.pushButton_2.clicked.connect(self._reFresh)#按钮2绑定开始更新函数
def closeEvent(self, event):#重写会话框关闭信号产生函数
reply = QMessageBox.question(self, 'Message',
"Are you sure to quit?", QMessageBox.Yes, QMessageBox.No)#产生提醒会话界面
if reply == QMessageBox.Yes:#如果确认
if self.timer.isActive():#定时器激活状态
self.timer.stop()#停止定时器
print("停止定时器")
else:
print("无需停止定时器")
event.accept()#关闭事件接受
else:
event.ignore()#舍弃关闭事件
def _reFresh(self):#刷新按钮绑定函数
if self.timer.isActive() == True:#如果定时器激活状态
self.timer.stop()#关闭定时器
self.pushButton_2.setText("实时更新")#设置按钮字符
print("关闭定时器......")
else:
self._updateFigure()#刷新界面
self.timer.start(30000)#开启定时器
self.pushButton_2.setText("暂停更新")#设置按钮字符
print("打开定时器......")
def _updateFigure(self):
try:
self._showLine()#显示曲线
except Exception as e:
print(e)
def _showLine(self):
self._canvas.refreshLine()
def _errorProcess(self):
pass
def _createCanvas(self):
self.setWindowTitle(self.title)#设置title
# self.setGeometry(self.left, self.top, self.width, self.height)
self._canvas = PlotCanvas(self, width=5, height=4)#创建matplot对象
def _createLayouts(self):
layout = QHBoxLayout(self.frame)#选定frame组件
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(self._canvas) # 添加matplot对象到QT组件内,这是最重要的一个函数,用于将matplotlib和qt绑定
class PlotCanvas(FigureCanvas):#创建一个matplot对象
def __init__(self, parent=None, width=5, height=4, dpi=100):
self._Font = {
'family': 'SimHei',
'weight': 'bold',
'size': 15}
fig = Figure(figsize=(width, height), dpi=dpi)
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self,QSizePolicy.Expanding,QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
self._ax = self.figure.add_subplot(111)
def refreshLine(self):
x = [];y = []#这里自行创建数据
try:
self._ax.plot(x,y, '--r*', label="Matplot Test")#绘图
self._ax.set_title('Matplot Test', fontdict=self._Font)
self._ax.set_xlabel("X轴", fontdict=self._Font)
self._ax.set_ylabel("Y轴", fontdict=self._Font)
self._ax.xaxis.grid(True, which='major') # x坐标轴的网格使用主刻度
self._ax.yaxis.grid(True, which="major")
self.draw()
except Exception as e:
print(e)
def plot(self):
import datetime
price_list = [
{
"PRICE" : "0.0",
"Time" : "2018-01-22 18:56:00"},
{
"PRICE": "0.16461536288261414",
"Time": "2018-01-22 20:07:18"
},
{
"PRICE": "0.0",
"Time": "2018-01-22 20:19:30"
},
{
"PRICE": "0.01397849153727293",
"Time": "2018-01-22 20:31:50"
}
]
Font = {'family': 'SimHei',
'weight': 'bold',
'size': 15}
x = []
y = [] # 定义两个列表
for item in price_list: # 逐年取出单年所有信息
y.append(float(item["PRICE"])) # 价格数值加入y列表
x.append(datetime.datetime.strptime(item["Time"], "%Y-%m-%d %H:%M:%S"))
# print(type(datetime.datetime.strptime(item["Time"], "%Y-%m-%d %H:%M:%S")))
self._ax = self.figure.add_subplot(111)
self._ax.plot(x,y, '--r*',label = "NH3")
self._ax.set_title('测试曲线图',fontdict = self._Font)
self._ax.set_xlabel("时间(\"年-月-日 小时:分:秒\")",fontdict=self._Font)
self._ax.set_ylabel("价格($)",fontdict=self._Font)
self._ax.xaxis.set_major_formatter(mdate.DateFormatter('%Y-%m-%d %H:%M:%S'))
self._ax.xaxis.grid(True, which='major') # x坐标轴的网格使用主刻度
self._ax.yaxis.grid(True, which="major")
# self._ax.xaxis.set_xticklabels(rotation=75)
# for label in self._ax.get_xticklabels() + self._ax.get_yticklabels():
# label.set_fontsize(6)
# label.set_bbox(dict(facecolor='green', edgecolor='None', alpha=0.7))
# self._ax.xticklabels(rotation=75)
# self.xticks(rotation=75)
self.draw()
if __name__ == "__main__":
import sys
app =QApplication(sys.argv)
window = App()#创建QT对象
window.show()#QT对象显示
sys.exit(app.exec_())