python 支持多种图形界面的第三方库。包括:
Qt 是一个由 C++编写的跨平台的应用程序开发框架。可以用来开发 GUI 程序,也可以用来开发非 GUI 程序。目前 Qt 有两种版本,一种是商业版本(付费使用),另一种则是基于(L)GPL v3 的开源版本。
Qt 具体在某些方面的应用,大家可以访问 Qt 官网) 进行查看。
Qt 和 PyQt 有什么区别?简而言之,PyQt 是 Qt 的一个 wrapper(封装)。PyQt 为 python 开发 GUI 程序提供了解决方案,同样 PyQt 也是跨平台的,支持 Linux、Windows、Mac OS。从 PyQt4 开始,所有平台上均支持 GPL(General Public License) 协议即开源协议。
PyQt 官方网站请 点击 。PyQt4 官方文档请 点击 。
目前有 PyQt4 和 PyQt5 两种版本,且两者不兼容。PyQt4 在官网介绍说明中,未来将不会得到支持,所以 PyQt5 是未来的一个趋势。当然目前 PyQt4 使用最为广泛。
本文主要以 PyQt4 进行介绍。
PyQt 安装这一块,可能比较复杂。需要下载包手动安装。工具包 下载地址 。
首先 PyQt 工具包版本需要与 Python 版本进行匹配。版本位数也必须要匹配,比如 python 解释器是 32 位,那么工具包也必须是 32 位的。这里推荐大家版本都采用 64 位。
PyQt4 为跨平台的工具包,它包括了 300 多个类和 6000 多个方法。由于过多的类和方法,整个工具库被划分成了多个模块。不同模块主要实现不同的功能,这一部分大家主要了解以下模块的主要作用。并学习掌握几个关键模块。
具体模块如下:
以下描述组件均为 GUI 界面的组件,属于 QtGui 模块。重点关注几个常用组件的信号和方法。
1、按键 QPushButton
# 初始化 button 类,得到一个按键对象
ok_button = QtGui.QPushButton('OK', widget)
信号:
构造方法:
# 添加一个按键,并设置图标和文本
__init__(self, QIcon, QString_text, QWidget parent = None)
2、行编辑框 QLineEdit
# 初始化类,得到对象
useredit = QtGui.QLineEdit(widget)
信号:
构造方法:
# 生成一个行编辑框
__init__(self, QWidget parent = None)
# 生成一个带初始值的行编辑框
__init__(self, QString contents, QWidget parent = None)
方法:
3、文本编辑框 QTextEdit
# QTextEdit 类
logedit = QtGui.QTextEdit(widget)
信号:
构造方法:
# 生成一个文本编辑框
__init__(self, QWidget parent = None)
# 生成一个有初始文本的文本编辑框
__init__(self, QString_text, QWidget parent = None)
方法:
4、标签 QLabel
userlabel = QtGui.QLabel('label', widget)
构造方法:
__init__(self, QWidget parent = None, Qt.WindowFlags flags = 0):
# 带值的标签
__init__(self, QString_text, QWidget parent = None, Qt.WindowFlags flags = 0)
方法:
5、下拉框 QComboBox
# 得到下拉框对象
select_box= QtGui.QComboBox(widget)
信号:
构造方法:
__init__(self, QWidget parent = None)
方法:
6、树结构 QTreeWidget
# 树结构对象
casetree = QtGui.QTreeWidget(self.centralwidget)
信号:
构造方法:
__init__(self, QWidget parent = None)
方法:
7、树目录 QTreeWidgetItem
root= QtGui.QTreeWidgetItem(self.casetree)
构造方法:
__init__(self, int type = QTreeWidgetItem.Type)
__init__(self, QStringList strings, int type = QTreeWidgetItem.Type)
__init__(self, QTreeWidget parent, int type = QTreeWidgetItem.Type)
__init__(self, QTreeWidget parent, QStringList strings, int type = QTreeWidgetItem.Type)
__init__(self, QTreeWidget parent, QTreeWidgetItem preceding, int type = QTreeWidgetItem.Type)
__init__(self, QTreeWidgetItem parent, int type = QTreeWidgetItem.Type)
__init__(self, QTreeWidgetItem parent, QStringList strings, int type = QTreeWidgetItem.Type)
__init__(self, QTreeWidgetItem parent, QTreeWidgetItem preceding, int type = QTreeWidgetItem.Type)
__init__(self, QTreeWidgetItem other)
方法:
这一块大家了解一下即可。基本上 GUI 界面都是通过绘图得到的。
1、绝对定位
用像素(px) 指定每个组件(控件) 的大小和位置。
# 使用 move 方法确定组件的位置
move(x, y)
# 开始的两个参数是窗体的 x 和 y 的位置,a、b 为组件的宽度、高度
setGeometry(x, y, a, b)
2、框布局
基本的布局类是 QHBoxLayout 和 QVBoxLayout ,它们可以横向和纵向排列窗口组件。QHBoxLayout 布局类为水平布局类,在该布局中的组件呈水平排列。QVBoxLayout 布局类为垂直布局类,在该布局中的组件呈垂直排列。框布局就是结合水平布局与垂直布局,使组件能够二维分布
import sys
from PyQt4 import QtGui
class MainWindow(QtGui.QWidget):
"""docstring for ClassName"""
def __init__(self, parent=None):
super(MainWindow, self).__init__()
self.initUI()
def initUI(self):
okButton = QtGui.QPushButton("OK")
cancelButton = QtGui.QPushButton("Cancel")
hbox = QtGui.QHBoxLayout()
hbox.addStretch(1)
hbox.addWidget(okButton)
hbox.addStretch(1)
hbox.addWidget(cancelButton)
vbox = QtGui.QVBoxLayout()
vbox.addStretch(1)
vbox.addLayout(hbox)
vbox.addStretch(1)
self.setLayout(vbox)
self.setWindowTitle('box layout')
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
3、网格布局
最常用的布局类是网格布局,网格布局把空间划分成行和列,一般使用 QGridLayout 类来创建网格布局。布局类型设置好以后使用 addWidget() 方法来把窗口组件加到网格中,参数是部件(widget),行(row)和列(column)数字。
代码实例:
import sys
from PyQt4 import QtGui
class MainWindow(QtGui.QWidget):
"""docstring for ClassName"""
def __init__(self, parent=None):
super(MainWindow, self).__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('grid layout')
names = ['Cls', 'Bck', '', 'Close', '7', '8', '9', '/',
'4', '5', '6', '*', '1', '2', '3', '-',
'0', '.', '=', '+']
grid = QtGui.QGridLayout()
j = 0
pos = [(0, 0), (0, 1), (0, 2), (0, 3),
(1, 0), (1, 1), (1, 2), (1, 3),
(2, 0), (2, 1), (2, 2), (2, 3),
(3, 0), (3, 1), (3, 2), (3, 3 ),
(4, 0), (4, 1), (4, 2), (4, 3)]
for i in names:
button = QtGui.QPushButton(i)
if j == 2:
grid.addWidget(QtGui.QLabel(''), 0, 2)
else:
grid.addWidget(button, pos[j][0], pos[j][1])
j = j + 1
self.setLayout(grid)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
我们将利用 QtGui 模块实现生成 GUI 界面,学习常用的几个类。
实例一:学习了解几个方法
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtGui
def main():
# 创建 app 对象,每个 PyQt 程序都必须创建一个 Application 对象
# sys.argv 获取参数列表
app = QtGui.QApplication(sys.argv)
# w 为 QWidget 类的对象,QWidget 窗口组件是 PyQt4 所有用户界面的基类
w =QtGui.QWidget()
# 初始化界面的大小,以下单位为像素(px)
w.resize(250, 150)
# 移动窗口的位置
w.move(300, 300)
# 设置界面标题
w.setWindowTitle('Simple GUI')
# 设置应用程序图标
w.setWindowIcon(QtGui.QIcon('icons/web.png'))
# show() 方法将 GUI 界面显示在屏幕上
w.show()
# 退出循环时,调用 exit() 方法关闭
sys.exit(app.exec_())
if __name__ == '__main__':
main()
实例二:了解事件和 QMessageBox
# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtGui
class MainWindow(QtGui.QMainWindow):
"""主 GUI 界面窗口"""
def __init__(self, parent=None):
# 继承父类的构造方法,也可称之为超类
super(MainWindow, self).__init__()
# setGeometry() 方法定义了窗体在屏幕上的位置,并设置窗体的大小。
# 开始的两个参数是窗体的 x 和 y 的位置,后面两个为 GUI 界面的宽度、高度
self.setGeometry(200,200,600,500)
# 设置标题
self.setWindowTitle('test')
# 气泡提示
self.setToolTip('This is a <b>QWidget</b> widget.')
# 设置字体
# QtGui.QToolTip.setFont(QtGui.QFont('OldEnglish', 10))
def closeEvent(self, event):
"""
关闭 GUI 界面时,将会产生一个 QCloseEvent 事件。
这里重写了 closeEvent 方法,实现关闭窗口时需要确认。改变了组件的行为。
"""
reply = QtGui.QMessageBox.question(self,'Message','Are you sure to quit?',QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
event.accept()
else:
event.ignore()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
实例三:
学习主 GUI 界面的状态栏、菜单栏、工具栏。工具栏一般不常用,常用的是另外两个。
注意:QMainWindow 类才有状态栏、菜单栏、工具栏。QWidget 与 QDialog 类型的窗口都不能这样使用。
# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtGui, QtCore
class MainWindow(QtGui.QMainWindow):
"""主 GUI 界面窗口"""
def __init__(self, parent=None):
# 继承父类的构造方法
super(MainWindow, self).__init__()
self.resize(600,500)
self.setWindowTitle('Test')
# 调用 QMainWindow 的 statusBar() 方法得到状态栏
# 调用 showMessage() 方法显示状态信息
self.statusBar().showMessage('Ready')
# 定义一个 exit 的动作,设置图标和名字
exit = QtGui.QAction(QtGui.QIcon(''),'Exit',self)
# 设置动作的快捷键
exit.setShortcut('Ctrl+Q')
# 状态栏显示 exit 动作提示信息
exit.setStatusTip('Exit the Application')
# connect() 方法连接信号和槽函数
self.connect(exit, QtCore.SIGNAL('triggered()'), QtCore.SLOT('close()'))
# 调用 menuBar() 方法得到菜单栏
menu = self.menuBar()
# 调用 addMenu() 方法得到菜单项
tools = menu.addMenu('&Tools')
# 添加动作到菜单
tools.addAction(exit)
# 通过 addToolBar() 方法得到工具栏,工具栏一般不常用。
self.toolbar = self.addToolBar('GoBack')
#初始化一个动作
goback = QtGui.QAction(QtGui.QIcon(''),'goback',self)
#设置动作的快捷方式
goback.setShortcut('Ctrl+D')
#将动作添加到工具栏
self.toolbar.addAction(goback)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
这部分是 PyQt 程序一个核心部分。开发 PyQt 程序必须要掌握理解以下概念
事件(Events)是 GUI 程序中很重要的一部分。它由用户或系统产生。当我们调用程序的 exec_() 方法时,程序就会进入主循环中。主循环捕获事件并将它们发送给相应的对象进行处理,事件往往伴随着一个信号的发生,或一个信号的处理。
这里事件(Events)包括鼠标点击、键盘按键输入等事件。
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtGui, QtCore
class KeyExample(QtGui.QWidget):
def __init__(self):
super(KeyExample, self).__init__()
self.setWindowTitle('Escape')
self.resize(500, 500)
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Escape:
self.close()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
ex = KeyExample()
ex.show()
sys.exit(app.exec_())
当用户单击一个按钮,拖动一个滑块或进行其它动作时,相应的信号就会被触发。除此之外,信号还可以因为环境的变化而被发射。比如一个运行的时钟将会发射时间信号等。而所谓的槽则是一个方法(函数),该方法将会响应它所连接的信号。在 Python 中,槽可以是任何可以被调用的对象。槽一般为事件处理函数,专门用于处理某个信号。
在 PyQt 中,通过 connect() 方法连接信号与槽函数。此方法一般传入的参数为发送信号的对象、信号、对信号作出反应的方法(槽函数)。
connect(sender, QtCore.SIGNAL('clicked()'), revicer, QtCore.SLOT())
我们来看一个信号与槽的代码实例,目前我们用的最多就是 button 按钮的信号(clicked())。
# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtGui, QtCore
class ButtonExample(QtGui.QMainWindow):
def __init__(self):
super(ButtonExample, self).__init__()
self.initUI()
def initUI(self):
button1 = QtGui.QPushButton("Button 1", self)
button1.move(30, 50)
button2 = QtGui.QPushButton("Button 2", self)
button2.move(150, 50)
# 连接信号与槽函数
self.connect(button1, QtCore.SIGNAL('clicked()'),
self.buttonClicked)
self.connect(button2, QtCore.SIGNAL('clicked()'),
self.buttonClicked)
self.statusBar().showMessage('Ready')
self.setWindowTitle('Event sender')
self.resize(290, 150)
def buttonClicked(self):
"""自定义槽函数,对信号作出反应"""
# sender() 方法获取产生信号的对象
sender = self.sender()
self.statusBar().showMessage(sender.text() + ' was pressed')
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
button = ButtonExample()
button.show()
sys.exit(app.exec_())
当然,我们也可以自定义信号。自定义信号不仅可以用于改变页面的显示状态,还可用于 GUI 线程与逻辑线程之间的数据传递。
# 创建一个不传递参数的信号
self.mysignal = QtCore.SIGNAL('bigthan10()')
# 使用 emit() 方法发送信号
self.emit(self.mysignal)
# 创建一个传递参数的信号,参数类型为 int
self.mysignal = QtCore.pyqtSignal(int)
# 发送信号
self.mysignal.emit(20)
注意:使用自定义的槽函数处理自定义带传递参数的信号时,槽函数的参数与信号的参数必须保持一致(参数类型、参数个数、参数顺序)。
# -*- coding: utf-8 -*-
import sys
import time
from PyQt4 import QtGui, QtCore
class MainWindow(QtGui.QMainWindow):
# 定义一个信号,信号不能定义在构造函数中,否则会报错
mysignal = QtCore.pyqtSignal(str)
def __init__(self):
super(MainWindow,self).__init__()
self.setGeometry(100,100,500,500)
self.setWindowTitle('test')
#self.setWindowIcon(QtGui.QIcon('C:\Users\Administrator\cut.png'))
self.statusbar =self.statusBar() #创建一个状态栏
self.mysignal.connect(self.statusMessage) #连接信号与槽函数
date =time.ctime()
self.mysignal.emit(str(date)) #发射信号
def statusMessage(self,date):
# 自定义槽函数
self.statusbar.showMessage("now is %s!"%date)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
此部分为开发 PyQt 程序的核心部分。
前面我们介绍了 GUI 界面的相关组件、以及如何通过代码实现得到一个 GUI 界面。对于简单的图形界面,我们可以通过编写源码实现。但是如果图形界面过于复杂时,此时通过编码方式生成图形界面,工作量是非常巨大。此时我们可以通过 PyQt 工具包的 Designer 工具设计图形界面,保存得到.ui 文件,然后通过命令得到此 GUI 界面的 python 源码。
pyuic4 gui.ui > gui.py
现场演示如何使用此工具。
1、组件栏:显示组件,供用户选择
2、主界面栏:显示主界面,相当与一个画布,可以在上面设计我们需要的页面
3、对象编辑栏:包括几个部分
import sys
from PyQt4 import QtGui,QtCore
from gui import Ui_MainWindow
class MainWindoW(QtGui.QMainWindow):
"""导入 gui.py 得到图形界面"""
def __init__(self):
super(MainWindoW, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
wm = MainWindoW()
wm.show()
sys.exit(app.exec_())
此部分为图形化编程核心部分。
在 GUI 编程中,由于主界面是一个无限循环,且一直监听事务的发生,所以如果在主 UI 线程中做一些耗时的操作,会导致线程阻塞卡死,所以 PyQt 编程需要将主 UI 线程与逻辑线程分离,将一些耗时的操作都放在子线程里执行,主线程与子线程之间的数据传递通过自定义信号实现,PyQt 中定义的线程管理类为位于 QtCore 包的 QThread 类。
首先我们先学习 QThread 类有哪些方法。
1、信号
2、方法
1、通过 PyQt 中信号与槽的机制传递数据。
2、通过自定义的公共配置文件传递数据(如 config.ini)。
继承 QThread 类,重写 run 方法。
代码实例如下:
# -*- coding: utf-8 -*-
import sys, time
from PyQt4 import QtGui, QtCore
from PyQt4.QtGui import QPushButton, QLineEdit
class TimeThread(QtCore.QThread):
"""工作线程"""
# 自定义信号,指定参数类型为 str
signal_time = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
super(TimeThread, self).__init__(parent)
def start_timer(self):
self.start()
def stop_timer(self):
self.terminate()
self.wait()
def run(self):
while True:
print("Working", self.thread())
print(time.ctime(),type(time.ctime()))
# 发送信号,并携带参数值
self.signal_time.emit(time.ctime())
self.sleep(1)
class TimeWindow(QtGui.QMainWindow):
def __init__(self):
super(TimeWindow, self).__init__()
self.init_ui()
# 实例化子线程
self.timer_t = TimeThread()
# 信号连接槽函数
self.timer_t.signal_time.connect(self.update_timer_tv)
def init_ui(self):
"""生成 GUI 界面"""
self.resize(300, 300)
self.setWindowTitle('Time Clock')
self.statusBar().showMessage('Ready')
# 初始化行标签对象
self.timer_tv = QLineEdit(self)
self.timer_tv.setText("Wait")
self.timer_tv.setGeometry(15, 15, 270, 25)
# self.timer_tv.move(15, 15)
lable1 = QtGui.QLabel('Current Time',self)
lable1.move(150,100)
# 初始化设置 Quit 按键对象
stop_btn = QPushButton('Stop', self)
stop_btn.setToolTip('Click to Stop')
stop_btn.move(150, 150)
# 连接 clicked 信号和槽函数
self.connect(stop_btn, QtCore.SIGNAL("clicked()"), self.click_stop_button)
# 初始化设置 start 按键对象
start_btn = QPushButton('Start', self)
start_btn.setToolTip("Click to start")
start_btn.move(50, 150)
# 连接 clicked 信号和槽函数
self.connect(start_btn, QtCore.SIGNAL("clicked()"), self.click_start_button)
def click_start_button(self):
"""槽函数拉起执行子线程"""
self.timer_t.start_timer()
self.statusBar().showMessage('Go')
def click_stop_button(self):
"""槽函数终止子线程"""
self.timer_t.stop_timer()
self.statusBar().showMessage('Ready')
def update_timer_tv(self, text):
# 槽函数
self.timer_tv.setText(text)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = TimeWindow()
window.show()
sys.exit(app.exec_())
(这里推荐 python 内置函数 eval())

