打开PyQt5的Qt Designer,会自动弹出新建窗体对话框,对于我们最常用的就是Widget通用窗口类,还有个MainWindows顾名思义主窗口。PyQt5的Widget被分离出来,似乎用来替代Dialog,并将Widget放入了QtWidget模块(库)中,PyQt4是QtGUI。
这是一个Widget和MainWindows,MainWindows默认添加了菜单栏、工具栏和状态栏等。
默认左边是控件栏,提供了很多空间类,我们可以直接拖放到widget中看到效果,点窗体--预览(Ctrl+R)。
每个空间都有自己的名称,提供不同的功能,比如常用的按钮、输入框、单选、文本框等等。
右边是对窗口及控件的各种调整、设置、添加资源(列如:图片)、动作。还可以直接编辑Qt引以为豪的信号槽(signal和slot)。
有了Qt Designer使得我们在程序设计中更快的能开发设计出程序界面,避免了用纯代码来写一个窗口的繁琐,同时PyQt支持界面与逻辑分离,这对于新手来说无疑是个最大的福音,当然要做出华丽的界面还是要学代码的。至少Qt Designer为我们提供了一些解决方法,另外我们也可以通过Qt Designer生成的代码来学习一些窗口控件的用法。
Qt Designer Layouts窗口布局
Qt Designer窗口布局Layouts提供了四种布局方法,他们是:
Vertical Layout 纵向布局
Horizontal Layout 横向布局
Grid Layout 栅格布局
Form Layout 在窗体布局中布局
前三种是我们经常会用到的,我们将布局Layouts拖动到窗体上会有红色框来显示(中间窗体中的四个小红框就是),Layout的一些属性可以通过属性编辑器来控制,一般包括:上下左右边距间隔,空间之间间隔等。
在我们使用布局之前,我们得对层次要有个了解,在程序设计中一般用父子关系来表示。当然有过平面设计经验的童鞋对分层应该有所了解,这里我们还需要将层分成层次。其实就像python中规定的代码缩进量代表不同层次的道理差不多。
从对象查看器中我们可以方便的看出窗体(Form)--布局(Layout)--控件(这里是PushButton按钮)之间的层次关系。Form窗口一般作为顶层显示,然后使用Layout将控件按照我们想要的方式规划开来。
小提示:
通常我们使用栅格布局作为顶层布局,将控件放置好之后可以通过右键--布局--栅格布局,将布局充满整个窗体。我们可以先放入控件,然后ctrl选中多个控件,然后点击工具栏上快速布局工具进行布局。
这里要注意一下,Qt Designer设计出来的文件默认为ui文件,里面包含的类css布局设计语言,如果想要查看代码我们还需要将它转换(编译)成py文件,我们可以使用一条DOS命令来完成D:\Python33\Lib\site-packages\PyQt5\pyuic5.bat main.ui -o frist.py。 我的pycharm经过PyQt5+python3+pycharm开发环境配置的配置。通过下图的操作可以便捷的对UI进行转化
更实用的转换命令可以将当前文件夹下所有ui转换成py文件:
for /f "delims=" %%i in ('dir /b /a-d /s *.ui') do D:\Python33\Lib\site-packages\PyQt5\pyuic5.bat %%i -o %%i.py
PyQt支持用LoadUi方法直接加载ui文件,当然我们通过转换后可以方便学习PyQt窗体控件的源代码。
下面来分析一下Qt Designer生成的源码。
Qt Designer制作的图形界面为
生成的代码如下
现在运行这段代码,窗口是不会出现的。如何使窗口出现呢?下面需要添加一段代码
新建一个文件,导入我们设计的untitled .py文件,实现代码与界面分离。
一些信号槽的基本介绍:信号和槽是一种高级接口,应用于对象之间的通信,它是 QT 的核心特性,也是 QT 区别于其它工具包的重要地方。它为高层次的事件处理自动生成所需要的附加代码。在我们所熟知的很多 GUI 工具包中,窗口小部件 (widget) 都有一个回调函数用于响应它们能触发的每个动作,这个回调函数通常是一个指向某个函数的指针。但是,在 QT 中信号和槽取代了这些凌乱的函数指针,使得我们编写这些通信程序更为简洁明了。
在Qt Designer中为我们提供了一些基本的信号槽方法,我们来看看:
点击工具栏上的“编辑信号/槽”,进入信号槽编辑模式,我们可以直接在发送者(button)上按住鼠标左键不放,拖动到接收者(Form窗体)上。这样就建立起了连接。
接着,会弹出配置连接对话框。
左边是发送者(按钮)的信号(动作事件),右边是接收者(窗体)的槽(动作事件)
如图所示,我信号选择的是clicked,槽选择的是close
我们看一下编译后生成的代码:
self.quitButton.clicked.connect(Form.close)
实现的功能是:当按钮点击之后关闭窗体。
流程:按钮是信号发送者,当点击按钮之后会发送一个信号出去,通过这段代码程序内部的通讯机制知道这个按钮的点击事情被连接到窗体的关闭事件上去了,然后通知接受者窗体,你该运行槽函数close了!
那么我们怎么能执行自己的“槽”呢?
槽其实就个函数(方法),Qt5中的槽函数不在限定必须是slot,可以是普通的函数、类的普通成员函数、lambda函数等。编译期间就会检查信号与槽是否存在!
信号的connect连接最好放在__init__析构函数里面,这样只会声明一次连接,如果在类方法(函数中)使用的话,要记得disconnect,否则connect会连接多次,导致程序异常。
信号槽函数不用加 (),否则可能会导致连接异常。
运行结果
注意:当信号与槽函数的参数数量相同时,它们参数类型要完全一致。信号与槽不能有缺省参数。 当信号的参数与槽函数的参数数量不同时,只能是信号的参数数量多于槽函数的参数数量,且前面相同数量的参数类型应一致,信号中多余的参数会被忽略。此外,在不进行参数传递时,信号槽绑定时也是要求信号的参数数量大于等于槽函数的参数数量。这种情况一般是一个带参数的信号去绑定一个无参数的槽函数。
可以出传递的参数类型有很多种:str、int、list、object、float、tuple、dict等等
PyQt5中为我们提供了很多默认信息框QMessageBox,注意为方便使用需要导入模块。
QMessageBox对话框包含类型只是图标不同其他无太大差别:
QMessageBox.information 信息框
QMessageBox.question 问答框
QMessageBox.warning 警告
QMessageBox.ctitical危险
QMessageBox.about 关于
if result == reply.Yes
print("Yes")
if result == reply.No
print("No")
多个文件打开 QFileDialog.getOpenFileNames()
文件夹选取 QFileDialog.getExistingDirectory()
文件保存 QFileDialog.getSaveFileName()
QColorDialog颜色对话框
QFontDialog字体对话框
再次回到Qt Designer。
MainWindows 即主窗口,主要包含了菜单栏、工具栏、任务栏等。双击菜单栏上的“在这里输入”,然后录入文字,回车即可。注意要用回车键确认。
对于一级菜单,我们可以通过输入 文件(&F) 编辑(&E) 来加入菜单的快捷按钮,在预览中按alt+m alt+e 可以看到效果.
而子菜单可以通过动作编辑器或者属性编辑器中的shortcut来添加快捷键.
、
双击需要编辑的动作,可以对其进行设置并添加图标、快捷键等等。
工具栏,默认在PyQt5中不显示工具栏,我们可以点击右键来添加工具栏,通过属性编辑器我们可以修改图标大小。而工具栏上面的图标,我们可以通过动作编辑器建立并拖入工具栏中
编写代码
(以上几条特点翻译于官方文档),接下来,我将以若干个实例,来体现以上几个特点。
(1)内置信号槽的使用
(2)自定义信号槽的使用
(3)信号槽N对N连接、断开连接
(4)多线程信号槽通信
首先来看一个例子:
我增加了一个WorkerThread类。WorkerThread继承自QThread类,重写了其run()函数。可以认为,run()函数就是新的线程需要执行的代码。在这里就是要执行这个循环,然后发出计算完成的信号。而在按钮点击的槽函数中,使用work()中的workThread.start()函数启动一个线程(注意,这里不是run()函数)。再次运行程序,你会发现现在界面已经不会被阻塞了
正如前面所说,要使用QThread开始一个线程,我们可以创建它的一个子类,然后覆盖其QThread.run()函数:
特性 | QThread |
QRunnable |
QtConcurrent |
高级 API | ✘ | ✘ | ✔ |
面向任务 | ✘ | ✔ | ✔ |
内建对暂停/恢复/取消的支持 | ✘ | ✘ | ✔ |
具有优先级 | ✔ | ✘ | ✘ |
可运行事件循环 | ✔ | ✘ | ✘
来源: |
需要注意的是,与信号不同,事件并不是一产生就被分发。事件产生之后被加入到一个队列中(这里的队列含义同数据结构中的概念,先进先出),该队列即被称为事件队列。事件分发器遍历事件队列,如果发现事件队列中有事件,那么就把这个事件发送给它的目标对象。这个循环被称作事件循环。事件循环的伪代码描述大致如下所示:
伪代码里面的while会遍历整个事件队列,发送从队列中找到的事件;wait_for_more_events()函数则会阻塞事件循环,直到又有新的事件产生。我们仔细考虑这段代码,在wait_for_more_events()函数所得到的新的事件都应该是由程序外部产生的。因为所有内部事件都应该在事件队列中处理完毕了。因此,我们说事件循环在wait_for_more_events()函数进入休眠,并且可以被下面几种情况唤醒:
在类 UNIX 系统中,窗口管理器(比如 X11)会通过套接字(Unix Domain 或 TCP/IP)向应用程序发出窗口活动的通知,因为客户端就是通过这种机制与 X 服务器交互的。如果我们决定要实现基于内部的socketpair(2)函数的跨线程事件的派发,那么窗口的管理活动需要唤醒的是:
使用Pyqt编程过程中,经常会遇到给槽函数传递额外参数的情况。但是信号-槽机制只是指定信号如何连接到槽,信号定义的参数被传递给槽,而额外的参数(用户定义)不能直接传递。
而传递额外参数又是很有用处。你可能使用一个槽处理多个组件的信号,有时要传递额外的信息。
一种方法是使用lambda表达式。
解释一下,on_button是怎样处理从两个按钮传来的信号。我们使用lambda传递按钮数字给槽,也可以传递任何其他东西---甚至是按钮组件本身(假如,槽打算把传递信号的按钮修改为不可用)
第2个方法是使用functools里的partial函数。
哪个办法好一点?这个属于风格的问题。个人观点,喜欢lambda,条理清楚,而且灵活。
《Rapid GUI Program with Python and QT》 P143例子。
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'untitled1.ui'
#
# Created by: PyQt5 UI code generator 5.5
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object): #创建窗体类MainWindow,继承object
def setupUi(self, MainWindow): #创建setupUi函数
MainWindow.setObjectName("MainWindow") #设置窗体名
MainWindow.resize(434, 390) #设置窗体的大小
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(80, 100, 54, 12))
self.label.setObjectName("label")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(70, 170, 75, 23))
self.pushButton.setObjectName("pushButton")
#MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 434, 23))
self.menubar.setObjectName("menubar")
#MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
#MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow) # 加载retranslateUi函数,只是做一系列的 setText、translate 等调用
QtCore.QMetaObject.connectSlotsByName(MainWindow) #关联信号槽
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.label.setText(_translate("MainWindow", "文本测试"))
self.pushButton.setText(_translate("MainWindow", "按钮"))
__author__ = 'zd'
from PyQt5 import QtWidgets,QtCore
from PtQt import Ui_MainWindow
import sys,time
class mydesignshow(QtWidgets.QWidget,Ui_MainWindow): #同时继承QWidget和Ui_MainWindow
_mysignal = QtCore.pyqtSignal() #定义信号
_mysignal1 = QtCore.pyqtSignal(str) #定义含参数信号
def __init__(self): # 析构函数,实例化后默认加载
super(mydesignshow,self).__init__() #超级加载
self.setupUi(self)
self.pushButton.clicked.connect(self.prn) #调用槽函数prn,注意槽函数不需要加()
self._mysignal.connect(self.mysignalslot) #将信号连接至槽
self._mysignal1.connect(self.mysignalslot1) #将信号连接至槽
def prn(self): #定义槽函数
print("按钮按下信号")
time.sleep(1)
print("延时1秒")
self._mysignal.emit() #发射信号
self._mysignal1.emit("有参信号") #发射带参信号
def mysignalslot(self): #定义自己的槽函数
print("无参信号")
def mysignalslot1(self,parameter): #定义自己的槽函数
print(parameter)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv) #程序入口
myshow = mydesignshow() #实例化
myshow.show() #调用继承QWidget中的show方法
sys.exit(app.exec_())