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())