本系列文章前情回顾:
前面的两节中是铺路的,都是为了第三章,这里我们就干一票大事,把前面两节的给整合了,来个AI 的界面初体验,最终的结果如下所示:
一、创建项目
这里不小心就创建了一个QDialog 的项目,其实这个关系不太,下面是这三组项目选择适合的区别,可以在之后的项目中慎重的选择一个,这里就使用QDialog 撰写本项目吧。
Qt 中 QMainWindow、QWidget、QDialog 的区别:
- QWidget 是所有图形界面的基类,也就是 QMainWindow 和 QDialog 都是 QWidget 的子类;
- QMainWindow 是一个提供了菜单、工具条的程序主窗口;
- QDialog 是对话框,多用于短时间与用户的交互。
再回头看一下前面的最终结果图,这里我们就把它想作是UI小姐姐为你精心设计的一个美丽的,富含设计感的UI组合吧,其中包含的主要控件如下:
整理来看,可以分为3大块:
- 打印显示输出部分,况且定义为left
- 参数输入部分,定义为right
- 功能按钮部分,定义为button
(1)打印输出部分,left包含两个
- label--打印输出
- lineEdit--显示输出信息
(2)参数输入部分,right
- 学习率,优化方式等5个label
- 其对应的lineEdit,或
(3)三个pushbutton功能按钮
有了这么多,我们接下来就瘦撸代码,实现对上述功能界面的实现吧。
二、动手实现
这里就不一步一步的逐一来实现了,而是先对整个过程进行展示,和章节一类型相似,总览加分述,分述部分我们在第三章节中再进行。话不多说,来看全局代码(代码实现的过程中间进行了各个小段内实现目的的简述,作为对整个代码部分的注释)。
# -*- coding: utf-8 -*-
from PyQt4 import QtCore
from PyQt4 import QtGui
import sys
QtCore.QTextCodec.setCodecForTr(QtCore.QTextCodec.codecForName("utf8"))
class InputDlg(QtGui.QWidget):
def __init__(self,parent=None):
super(InputDlg,self).__init__(parent)
QtGui.QWidget.__init__(self,parent=None)
# self.setGeometry(400,200,640,680)
self.setWindowTitle(self.tr("自助训练系统"))
train = QtGui.QPushButton(u'训练开始', self)
self.connect(train, QtCore.SIGNAL("clicked()"), self.train_slot) ######################
test = QtGui.QPushButton(u'测试开始', self)
self.connect(test, QtCore.SIGNAL("clicked()"), self.main) #######################
evaluate = QtGui.QPushButton(u'评估开始', self)
self.connect(evaluate, QtCore.SIGNAL("clicked()"), self.evaluate_slot)#########################
# train、test、evaluate水平布局
bottomLayout = QtGui.QHBoxLayout()
bottomLayout.addStretch()
bottomLayout.addWidget(train)
bottomLayout.addWidget(test)
bottomLayout.addWidget(evaluate)
#self.setLayout(bottomLayout)
##########################################################################
label_print = QtGui.QLabel(self.tr("打印输出"))
self.printTextEdit = QtGui.QTextEdit()
leftLayout = QtGui.QVBoxLayout()
leftLayout.addWidget(label_print)
leftLayout.addWidget(self.printTextEdit)
###############################################################################
label_lr=QtGui.QLabel(self.tr(u"学习率:"))
label_opt=QtGui.QLabel(self.tr(u"优化方式:"))
label_me=QtGui.QLabel(self.tr(u"最大迭代数:"))
label_itr=QtGui.QLabel(self.tr(u"每迭代数保存模型:"))
label_begin = QtGui.QLabel(self.tr(u"是否从头开始训练:"))
self.lrLineEdit = QtGui.QLineEdit()
self.optComboBox = QtGui.QComboBox()
self.optComboBox.insertItem(0, self.tr("SGD"))
self.optComboBox.insertItem(1, self.tr("Adam"))
self.meLineEdit = QtGui.QLineEdit()
self.itrComboBox = QtGui.QComboBox()
self.itrComboBox.insertItem(0, self.tr("2"))
self.itrComboBox.insertItem(1, self.tr("5"))
self.beginComboBox = QtGui.QComboBox()
self.beginComboBox.insertItem(0, self.tr("Yes"))
self.beginComboBox.insertItem(1, self.tr("No"))
self.checkpointLabel = QtGui.QLabel("0")
self.connect(self.beginComboBox, QtCore.SIGNAL('currentIndexChanged(int)'), self.retrain_begin_num)
# 右上侧信息网格布局
rightLayout = QtGui.QGridLayout()
rightLayout.addWidget(label_lr,0,0)
rightLayout.addWidget(self.lrLineEdit,0,1)
rightLayout.addWidget(label_opt,1,0)
rightLayout.addWidget(self.optComboBox,1,1)
rightLayout.addWidget(label_me,2,0)
rightLayout.addWidget(self.meLineEdit,2,1)
rightLayout.addWidget(label_itr,3,0)
rightLayout.addWidget(self.itrComboBox,3,1)
rightLayout.addWidget(label_begin, 4, 0)
rightLayout.addWidget(self.beginComboBox, 4, 1)
rightLayout.addWidget(self.checkpointLabel, 4, 2)
##################################################################
# 把左部分、右部分、下部分进行网格布局
mainLayout = QtGui.QGridLayout(self)
mainLayout.setMargin(15)
mainLayout.setSpacing(10)
mainLayout.addLayout(leftLayout, 0, 0)
mainLayout.addLayout(rightLayout, 0, 1)
#mainLayout.addLayout(midLayout, 0, 2)
mainLayout.addLayout(bottomLayout, 1, 0, 1, 2)
mainLayout.setSizeConstraint(QtGui.QLayout.SetFixedSize)
########################################################################
# Button_runs_slot
########################################################################
def retrain_begin_num(self):
checkpoint_num, ok = QtGui.QInputDialog.getInteger(self,self.tr("checkpoint_num"),
self.tr("请输入模型开始步数:"),
int(self.checkpointLabel.text()),0,150)
if ok:
self.checkpointLabel.setText(str(checkpoint_num))
def test_slot(self):
button = QtGui.QMessageBox.question(self, "Question",
self.tr("正在测试,等稍等···?"))
def evaluate_slot(self):
button = QtGui.QMessageBox.question(self, "Question",
self.tr("正在评估,等稍等···?"))
def train_slot(self):
lr_value = self.lrLineEdit.text() # 获取文本框内容
print(u'学习率=', lr_value)
opt_value = self.optComboBox.currentText() # 返回选中选项的文本
print(u'优化方式=', opt_value)
me_value = self.meLineEdit.text() # 获取文本框内容
print(u'最大迭代轮数=', me_value)
itr_value = self.itrComboBox.currentText()
print(u'每迭代{}保存模型'.format(itr_value))
begin_value = self.beginComboBox.currentText()
print(u'是否从0开始训练=', begin_value)
checkpoint_value = self.checkpointLabel.text()
print(u'继续训练,Epoch=', checkpoint_value)
###### QtGui.QTextEdit() 显示内容 ##########
self.printTextEdit.setText(u'打印训练参数:\n' + u'学习率: ' + lr_value
+ '\n' + u'优化方式: ' + opt_value
+ '\n' + u'最大迭代轮数: ' + me_value
+ '\n' + u'每迭代步数保存模型: ' + itr_value
+ '\n' + u'是否从0开始训练: ' + begin_value
+ '\n' + u'继续训练,Epoch: ' + checkpoint_value)
button = QtGui.QMessageBox.question(self, u"训练,打印学习率",
self.tr("学习率={}".format(lr_value)),
QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel,
QtGui.QMessageBox.Ok)
if button == QtGui.QMessageBox.Ok:
self.tr("ok")
elif button == QtGui.QMessageBox.Cancel:
self.tr("bay")
else:
return
if __name__ == "__main__":
app=QtGui.QApplication(sys.argv)
s=InputDlg()
s.show()
app.exec_()
三、代码段详述
顺序,从下往上
(1)从bottom开始吧,控件部分是PushButton,这里节选出来,如下
self.setWindowTitle(self.tr("自助训练系统"))
train = QtGui.QPushButton(u'训练开始', self)
self.connect(train, QtCore.SIGNAL("clicked()"), self.train_slot) ######################
test = QtGui.QPushButton(u'测试开始', self)
self.connect(test, QtCore.SIGNAL("clicked()"), self.main) #######################
evaluate = QtGui.QPushButton(u'评估开始', self)
self.connect(evaluate, QtCore.SIGNAL("clicked()"), self.evaluate_slot)#########################
- 首先是对整个GUI界面设置一个窗口标题(setWindowTitle),有一点都是集成self的,在用class类的时候,会比较常见的,在之后的编程中也是要牢记这一点
- 添加一个“训练开始”的按钮,在PyQt中,QtGui主要是用于对图像用户接口GUI(Graphical User Interface)中的控件进行定义的,比如按钮、文本框。只要是hi在界面中能够被我妈看到的,都是它来进行定义的;QtCore则于此相反,一个负责貌美如花(人前),一个负责内涵,如3中的信号发射部分
- 有了按钮,还需要使得按钮有一定的功能才行。下面这个就是使用才qt中最具代表性的信号槽机制,接下来就看看信号槽机制一个比较形象的图吧(如下)。train---是我们设置的按钮名称;QtCore内有一个信号机制,其中这里的触发是clicked;信号发射后,接受的函数的train-slot函数(这个函数的定义在下文中是需要进行定义的,就是定义我们要这个按钮干点啥)
功能按钮都定义好了,那他们三个究竟会以什么的形式,或者说谁前谁后进行一个排列呢。这里也需要我们自己定义,如下面这样:
# train、test、evaluate水平布局
bottomLayout = QtGui.QHBoxLayout()
bottomLayout.addStretch()
bottomLayout.addWidget(train)
bottomLayout.addWidget(test)
bottomLayout.addWidget(evaluate)
- 实例化一个QtGui.QHBoxLayout(),这个是用来水平布局的
- 将上文中定义的三个按钮train、test、evaluate使用add添加进去
- 按照前面的想法,是不是应该做完啦,那位什么还有一个addStretch语句呢?看下它什么意思,我们翻一下:伸展、拉紧、过分延长,这里和名字的意思一样,是为了使得排列后的格式更加的好看,而故意填充了一块空白。
(2)left定义显示
- label--打印输出
- lineEdit--显示输出信息
看下,其实也就是这样的思路进行界面的设定的,就不赘述了
但是,有一点是需要进行注意的:常用的方法有addWidget()和addLayout()
- addWidget()用于在布局中插入控件
- addLayout()用于在布局中插入子布局
label_print = QtGui.QLabel(self.tr("打印输出"))
self.printTextEdit = QtGui.QTextEdit()
leftLayout = QtGui.QVBoxLayout()
leftLayout.addWidget(label_print)
leftLayout.addWidget(self.printTextEdit)
添加好后,还是需要进行布局,1中三个是并排放的,水平布局,这里是堆叠放置的,采用垂直布局。先实例化,然后将控件添加进去吧。似不似很简单了呢
(3)第三块部分相对来说看着复杂一些,但仔细一看,其实大体上都是相似的。程序就是这样,看着复杂,其实是有程序化的处理方式的,就显得简单了一些。
代码如下:
label_lr=QtGui.QLabel(self.tr(u"学习率:"))
label_opt=QtGui.QLabel(self.tr(u"优化方式:"))
label_me=QtGui.QLabel(self.tr(u"最大迭代数:"))
label_itr=QtGui.QLabel(self.tr(u"每迭代数保存模型:"))
label_begin = QtGui.QLabel(self.tr(u"是否从头开始训练:"))
self.lrLineEdit = QtGui.QLineEdit()
self.optComboBox = QtGui.QComboBox()
self.optComboBox.insertItem(0, self.tr("SGD"))
self.optComboBox.insertItem(1, self.tr("Adam"))
self.meLineEdit = QtGui.QLineEdit()
self.itrComboBox = QtGui.QComboBox()
self.itrComboBox.insertItem(0, self.tr("2"))
self.itrComboBox.insertItem(1, self.tr("5"))
self.beginComboBox = QtGui.QComboBox()
self.beginComboBox.insertItem(0, self.tr("Yes"))
self.beginComboBox.insertItem(1, self.tr("No"))
self.checkpointLabel = QtGui.QLabel("0")
self.connect(self.beginComboBox, QtCore.SIGNAL('currentIndexChanged(int)'), self.retrain_begin_num)
- 一次性把所有需要的label部分都添加进去
- 这种留给用户自己输入的控件,可以就定义为LineEdit,可空可默认;后面还有这种选择性的ComboBox(下拉列表框),设定可选择的东西,这里用插入项目(条款);这块主要是涉及这两个,别的不多说
- 有一点不同的地方,就是可以对下拉列表框进设定为信号机制,上面的代码中就设定“当当前的index改变时,就让他发射信号,激活槽函数retrain_begin_num”
- 由于right部分,有水平也有垂直方向的,就采用网格形式就行布局吧,(设定参考这里:PyQt4学习2之---BoxLayout布局、网格GridLayout布局,实现计算器界面设计)这如下面这样:
# 右上侧信息网格布局
rightLayout = QtGui.QGridLayout()
# addWidget用于在布局中插入控件
# QGridLayout.addWidget(窗体, 起始行, 起始列, 占用行, 占用列, 对齐方式)
rightLayout.addWidget(label_lr,0,0)
rightLayout.addWidget(self.lrLineEdit,0,1)
rightLayout.addWidget(label_opt,1,0)
rightLayout.addWidget(self.optComboBox,1,1)
rightLayout.addWidget(label_me,2,0)
rightLayout.addWidget(self.meLineEdit,2,1)
rightLayout.addWidget(label_itr,3,0)
rightLayout.addWidget(self.itrComboBox,3,1)
rightLayout.addWidget(label_begin, 4, 0)
rightLayout.addWidget(self.beginComboBox, 4, 1)
rightLayout.addWidget(self.checkpointLabel, 4, 2)
(4)前面三个内容,分别把bottom部分进行了水平布局、把left部分进行了垂直布局、把right部分进行了网格布局,可以说是把qt中比较常用的记住布局方式都囊括了,最后呢,还是要呈现在整个界面上,还是不知道他们三个之间的关系,这里就要把他们三个的关系也明确的定义下,如下:
- addLayout()用于在布局中插入子布局
# 把左部分、右部分、下部分进行网格布局
mainLayout = QtGui.QGridLayout(self)
mainLayout.setMargin(15) # 表示控件与窗体的左右边距
mainLayout.setSpacing(10) # 表示各个控件之间的上下间距
mainLayout.addLayout(leftLayout, 0, 0)
mainLayout.addLayout(rightLayout, 0, 1)
#mainLayout.addLayout(midLayout, 0, 2)
mainLayout.addLayout(bottomLayout, 1, 0, 1, 2)
mainLayout.setSizeConstraint(QtGui.QLayout.SetFixedSize) # 设置对话框大小固定,不允许用户改变
(5)槽函数实现部分
主要就说下对GUI框中数据的获取吧,然后再打印到显示框中。这个在之后的应用中会比较常见
def train_slot(self):
lr_value = self.lrLineEdit.text() # 获取文本框内容
print(u'学习率=', lr_value)
opt_value = self.optComboBox.currentText() # 返回选中选项的文本
print(u'优化方式=', opt_value)
me_value = self.meLineEdit.text() # 获取文本框内容
print(u'最大迭代轮数=', me_value)
itr_value = self.itrComboBox.currentText()
print(u'每迭代{}保存模型'.format(itr_value))
begin_value = self.beginComboBox.currentText()
print(u'是否从0开始训练=', begin_value)
checkpoint_value = self.checkpointLabel.text()
print(u'继续训练,Epoch=', checkpoint_value)
###### QtGui.QTextEdit() 显示内容 ##########
self.printTextEdit.setText(u'打印训练参数:\n' + u'学习率: ' + lr_value
+ '\n' + u'优化方式: ' + opt_value
+ '\n' + u'最大迭代轮数: ' + me_value
+ '\n' + u'每迭代步数保存模型: ' + itr_value
+ '\n' + u'是否从0开始训练: ' + begin_value
+ '\n' + u'继续训练,Epoch: ' + checkpoint_value)
四、总结
qt 关于窗口布局的设计,我知道的有两种方式。
- 一是直接采用qt 的design进行拖拽式的创建,这样会比较快捷;
- 另一种就是采用代码的形式,直接进行一个块一个块的设计。
无论是何种方式,最终都会转化为代码的形式,进行转移。