文章目录
- 一、认识PyQt5
- 二、Qt Designer
- 二、PyQt5基本窗口控件
- 三、PyQt5高级界面控件
- 四、PyQt5布局管理
- 五、信号与槽
- 六、PyQt5图形和特效
- 七、实战案例
- 七、实战案例
一、认识PyQt5
1.PyQt5简介
GUI 就是图形用户界面,PyQt5是 Qt 为Python专门提供的GUI扩展。Qt 是挪威的奇趣科技公司开发的一个 C++GUI程序,之后其被诺基亚收购,后来又被芬兰IT业务供应商Digia从诺基亚手中收购。PyQt是Qt框架的Python语言实现,使用信号/槽的机制在窗口控件之间传递事件和消息,这种机制完全不同于其他图形用户界面开发库所采用的回调(callback)机制,使用信号/槽的机制使程序更加简洁和安全。
二、Qt Designer
1.第一个案例
import sys
from PyQtQtWidgets import QApplication,QWidget
if __name__=='__main__':
#创建QApplication
app = QApplication(sys.argv)
#创建一个窗口
w = QWidget()
#设置窗口大小
w.resize(400,200)
#移动窗口
w.move(300,300)
#设置窗口标题
w.setWindowTitle('第一个PyQt5的桌面应用')
#显示窗口
w.show()
#循环扫描窗口,进入主循环,并通过exit函数确保主循环安全结束
sys.exit(app.exec_())
2.将 ui 文件转化为 py 文件
1.在命令行下输入 python -m PyQtuic.pyuic 文件名.ui -o 文件名.py
2.调用文件中的 pyuic5 文件名.ui -o 文件名.py
3.通过Python脚本把.ui文件转换为.py文件
import os
import os.path
#UI文件所在的路径
dir='./'
#列出目录下的所有UI文件
def listUIFile():
lis=[]
files=os.listdir(dir)
for filename in files:
if os.path.splitext(filename)[1]=='.ui':
lis.append(filename)
return lis
#把扩展名为.ui的文件改成扩展名为.py的文件
def transPyFile(filename):
return os.path.splitext(filename)[0]+'.py'
#使用系统命令把UI文件转化为Python文件
def runMain():
lis=listUIFile()
print(lis)
for uifile in lis:
pyfile=transPyFile(uifile)
cmd='pyuic5 -o {pyfile} {uifile}'.format(pyfile=pyfile,uifile=uifile)
os.system(cmd)
if __name__=='__main__':
runMain()
3.基本布局
Horizontal Layout 水平布局
Vertical Layout 垂直布局与水平布局类似
Grid Layout 栅格布局
Form Layout 表单布局/在窗体布局中布局
在容器中完成布局:右键选中容器选择变形为:Qframe 或者在 左侧的 Containers 选择
综合应用,先将每一部分变成一块,一步步扩大
1.选中控件,右键选择水平控件,控件从左到右水平排列,等宽等距
2.先拖动选框,出现蓝线表示辅助线
保存为 .ui 文件转换为 .py 文件 新创建 .py文件
import sys
import MainWinHorizontalLayout
from PyQtQtWidgets import QApplication,QMainWindow
if __name__=='__main__':
#创建应用程序
app = QApplication(sys.argv)
#创建主窗口
mainWindow = QMainWindow()
#引入类
ui = MainWinHorizontalLayout.Ui_MainWindow()
#调用类方法,传入主窗口
ui.setupUi(mainWindow)
#展示窗口
mainWindow.show()
#主循环
sys.exit(app.exec_())
4.基本操作
使用分割线(Line)与间隔(Spacer)
控件尺寸的最大值和最小值 调整每一项属性值(必须展开)
尺寸策略
sizeHint(期望尺寸)是默认尺寸,对于大多数控件 sizeHint 是只读的,与当前的尺寸无关,不能小于最小期望尺寸
self.pushButton.sizeHint().width() #默认期望尺寸 77
self.pushButton.sizeHint().height() #默认期望尺寸 32
self.textEdit.minimumSizeHint().height() #最小期望尺寸和默认尺寸都是 78
prefereed是设置为期望尺寸,策略使用 Expanding 可以通过调整 伸展值的大小控制固定控件大小比例
设置控件之间的伙伴关系
在控件中编辑(&A),可以把控件与 Alt + A 快捷键绑定在一起,在 Edit 中选择编辑伙伴,左键拖动互为伙伴的两个控件,按 Alt + A 即可获得焦点,实际是通过 setBuddy() 方法来实现的
设置控件的 Tab 顺序
在预览状态,按 Tab 可以根据放置控件的顺序依次获得焦点,在Edit 中选择编辑 Tab顺序可以通过双击或右键制表符顺序列表修改
设置信号(signal)与槽(slot)
信号是由对象或控件发射出去的消息,可以理解为事件,如单击按钮时会发出单击的消息,这些发送出去的信号需要一些代码来拦截,这些代码就是槽,槽一般是一个方法或函数,相当于事件函数,需要将信号和槽绑定,一个信号可以和多个槽绑定,一个槽也可以拦截多个信号
在 designer 中选择 Edit中的编辑信号与槽,左键拖动发射信号的控件到接收信号的控件(槽)后松开,即可选择关联控件与槽,打开的窗口设置槽与函数或方法的关联,记得勾选 显示从 QWigdget 继承的信号与槽,选择左侧发射信号的事件,右侧选择关联的函数或方法
在 QtDesigner 中为窗口添加菜单栏和工具栏
在菜单栏中输入文本,按下回车键即可设置一个菜单栏,双击添加分隔符即可添加分隔符
每一个菜单栏选项实际都是一个 action,在动作编辑器中双击可以调整其属性,将其拖动到工具栏可以设置工具,动作发生变化时,菜单栏和工具栏的内容全部改变
在窗体中加载资源文件
Qt Designer中不能直接加入图片和图标资源,需要在开发目录下编写.qrc文件
<rcc verision="1.0">
<qresource>
</qresource>
</rcc>
打开设计界面,进入资源编辑界面,打开以上资源文件,设置前缀为 pic 然后就可以在右侧添加或删除资源了,按以上步骤添加资源后,之前的.qrc文件发生了变化
<RCC>
<qresource prefix="pic">
<file>test.ico</file>
</qresource>
</RCC>
放入Label控件,将其pixmap属性改为资源文件中的资源路径即可
若要在.py文件中使用,还需要将.qrc文件转化为.py文件,在命令行下输入pyrcc5 资源名.qrc -o 资源名_rc.py(已集成到tool.py文件中),在UI文件生成的.py文件中导入刚才生成的文件即可
二、PyQt5基本窗口控件
PyQt5有三种窗口,QMainWindow是主窗口,可以包含菜单栏,工具栏,状态栏和标题栏,是最常见的窗口形似,QDialog是对话窗口的基类,主要执行短期任务,没有菜单栏,工具栏,状态栏或标题栏,不确定窗口的用途时,可以使用QWidget
1.QMainWindow
主窗口QMainWindow中会有一个控件QWidget占位符来占着中心窗口,可以使用setCentralWidget()来设置窗口中心,QMainWindow类中比较重要的方法如下:
addToolBar():添加工具栏
centralWidget():返回窗口中心的一个控件,未设置时返回NULL
menuBar():返回主窗口的菜单栏
setCentralWidget():设置窗口中心的控件
setStatusBar():设置状态栏
statusBar():获得状态栏对象后调用状态栏对象的showMessage(message,int,timeout=0)方法显示状态栏信息,第二个参数是信息停留时间,单位是毫秒
主窗口不能设置布局(使用setLayout()方法),因为它由自己的布局
1.1.创建主窗口
import sys
from PyQtQtWidgets import QMainWindow,QApplication
from PyQtQtGui import QIcon
class FirstMainWin(QMainWindow):
def __init__(self,parrent=None):
super(FirstMainWin,self).__init__()
#设置主窗口的标题
self.setWindowTitle('第一个主窗口应用')
#设置窗口尺寸
self.resize(400,300)
#获取状态栏
self.status = self.statusBar()
self.status.showMessage('只存在5秒的消息',5000)
if __name__=='__main__':
app=QApplication(sys.argv)
app.setWindowIcon(QIcon('./favicon.ico'))
main = FirstMainWin()
main.show()
sys.exit(app.exec_())
1.2.使主窗口居中
需要手工计算使主窗口居中,通过移动窗口左上角来带动整个窗口居中
import sys
from PyQtQtWidgets import QMainWindow,QApplication,QDesktopWidget
class CenterForm(QMainWindow):
def __init__(self,parrent=None):
#继承父类构造方法
super(CenterForm,self).__init__(parrent)
#设置主窗口的标题
self.setWindowTitle('让窗口居中')
#设置窗口尺寸
self.resize(400,300)
def center(self):
#获得屏幕坐标系
screen = QDesktopWidget().screenGeometry()
#获取窗口坐标系
size = self.geometry()
newLeft = (screen.width()-size.width())/2
newTop=(screen.height()-size.height())/2
self.move(newLeft,newTop)
if __name__=='__main__':
app=QApplication(sys.argv)
main = CenterForm()
main.show()
sys.exit(app.exec_())
1.3.退出应用程序
import sys
from PyQtQtWidgets import QMainWindow,QApplication,QHBoxLayout,QWidget,QPushButton
class QuitApplication(QMainWindow):
def __init__(self):
super(QuitApplication,self).__init__()
self.resize(300,120)
self.setWindowTitle('退出应用程序')
#添加Button对象
self.button1=QPushButton('退出应用程序')
#将信号与槽关联
self.button1.clicked.connect(self.OnClick_Button)
#创建水平布局
#控件置于水平布局,水平布局置于mainFrame,mainFrame置于主窗口
layout=QHBoxLayout()
layout.addWidget(self.button1)
mainFrame=QWidget()
mainFrame.setLayout(layout)
self.setCentralWidget(mainFrame)
#按钮单击事件的方法(自定义的槽)
def OnClick_Button(self):
#sender是发送信号的对象
sender=self.sender()
print(sender.text()+'按钮被按下')
app=QApplication.instance()
#退出应用程序
app.quit()
if __name__=='__main__':
app=QApplication(sys.argv)
main = QuitApplication()
main.show()
sys.exit(app.exec_())
2.QWidget
基础窗口控件QWidget类是所用用户界面对象的基类,所有窗口和控件都直接或间接继承QWidget类
窗口控件(Widget),简称控件,是PyQt中建立界面的主要主要元素,在PyQt中把没有嵌入到其他控件中的控件称为窗口,一般的窗口都有边框,标题栏,窗口是指程序的整体界面,可以包含标题栏,菜单栏,工具栏等,控件是指按钮,复选框等组成程序的基本元素,一个程序可以由多个窗口,一个窗口也可以由多个控件
2.1.窗口坐标系统
PyQt 使用统一的坐标系统来定位窗口控件,屏幕的左上角为原点来构成屏幕坐标系,以窗口左上角为原点构成窗口坐标系(窗口包含菜单栏等),客户区不包含菜单栏的工作区,根据其几何结构坐标的成员函数分为三类:
QWidget直接提供的成员函数: x() 和 y() 获得窗口左上角坐标,width() 和 height() 获得客户区的宽度和高度
QWidget的geometry()提供的成员函数:x() 和 y() 获得客户区左上角坐标,width() 和 height() 获得客户区的宽度和高度
QWidget的 frameGeometry() 提供的成员函数:x() 和 y() 获得窗口左上角坐标,width() 和 height() 获得窗口的宽度和高度
2.2.常用的几何结构
QWidget不包含边框的常用函数
一般不包含边框的部分是客户区,在Qt中保存客户区的使用的是QRect类,有下面几个常用函数
QWidget.resize(width,height):改变客户区的面积 参数也可传入 QSize
QWidget.size():获得客户区的大小
QWidget.width()/QWidget.height():获得客户区的宽度和高度
QWidget.setFixedWidth(int width)/QWidget.setFixedHeight(int height):固定宽/高而改变另一个
QWidget.setFixedSize(int width,int height):固定高宽,不可用通过鼠标改变窗口的宽度和高度 参数也可传入 QSize size
QWidget.setGeometry(int x,int y,int width,int height):同时改变窗口的大小和位置 参数也可传入 QRect rect
QWidget包含边框的常用函数
QWidget.frameGeometry():获得窗口的大小和位置
QWidget.move(int x,int y):设置窗口位置,可以用于初始化窗口位置 也可传入参数QPoint point
QWidget.pos():获得窗口左上角的坐标
2.3.设置窗口和应用程序图标
import sys
from PyQtQtWidgets import QMainWindow,QApplication
from PyQtQtGui import QIcon
class IcoForm(QMainWindow):
def __init__(self,parrent=None):
super(IcoForm,self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(300,300,250,250)
#设置主窗口的标题
self.setWindowTitle('设置窗口图标')
#设置窗口图标,窗口的setWindowIcon方法用于设置窗口的图标,只在Windows中可用
self.setWindowIcon(QIcon('./favicon.ico'))
if __name__=='__main__':
app=QApplication(sys.argv)
main = IcoForm()
main.show()
sys.exit(app.exec_())
2.4.显示控件的提示信息
import sys
from PyQtQtWidgets import QMainWindow,QApplication,QHBoxLayout,QWidget,QPushButton,QToolTip
from PyQtQtGui import QFont
class TooltipForm(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
QToolTip.setFont(QFont('SansSerif',12))
self.setToolTip('今天是星期五')
#类似可以设置控件提示信息
#self.button.setToolTip()
self.setGeometry(300,300,200,300)
self.setWindowTitle('设置控件提示信息')
if __name__=='__main__':
app=QApplication(sys.argv)
main = TooltipForm()
main.show()
sys.exit(app.exec_())
3.QLabel
Qlabel对象作为一个占位符可以显示不可编辑的文本或图片,也可以作为提示标记为其他控件,纯文本,链接或富文本可以显示在标签上
常用方法:
setAlignment():文本对齐方式,参数可为Qt.Align(Left/Right/Center/Justify/Top/Bottom/VCenter)
setIndent():文本缩进
text():获取文本内容
*setBuddy():设置助记符及伙伴,即使用QLabel设置快捷键,会在快捷键后将焦点设置到其buddy上,buddy可以是任何一个Widget控件,使用setBuddy(QWidget )设置,其QLabel必须是文本内容,并且使用“&”符合设置了助记符
setText():设置文本内容
selectedText():返回所选择的字符
setWordWrap():是否允许换行
setPixmap():设置一个Pixmap图片
QLabel 常用的信号(事件):
1.鼠标滑过 QLabel 控件(希望打开文件setOpenExternalLinks特性必须设置为True):linkHovered
2.鼠标单击QLabel控件:linkActivated
3.1.显示QLabel标签
import sys
from PyQtQtWidgets import QMainWindow,QApplication,QHBoxLayout,QWidget,QPushButton,QToolTip,QLabel,QVBoxLayout
from PyQtQtGui import QPalette,QPixmap
from PyQtQtCore import Qt
class QLabelDemo(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
label1=QLabel(self)
label2=QLabel(self)
label3=QLabel(self)
label4=QLabel(self)
label1.setText('<font color=yellow>这是一个文本标签.</font>')
label1.setAutoFillBackground(True)
palette=QPalette()
palette.setColor(QPalette.Window,Qt.blue)
label1.setPalette(palette)
label1.setAlignment(Qt.AlignCenter)
label2.setText('<a href="#">欢迎使用Python Gui程序')
label3.setAlignment(Qt.AlignCenter)
label3.setToolTip('这是一个图片')
label3.setPixmap(QPixmap('./test.ico'))
label4.setText('<a href="https://www.baidu.com">点击跳转百度</a>')
label4.setAlignment(Qt.AlignRight)
label4.setToolTip('这是一个超链接')
vbox=QVBoxLayout()
vbox.addWidget(label1)
vbox.addWidget(label2)
vbox.addWidget(label3)
vbox.addWidget(label4)
label4.setOpenExternalLinks(True)
self.setLayout(vbox)
self.setWindowTitle('QLabel控件演示')
label2.linkHovered.connect(self.linkHovered)
label4.linkActivated.connect(self.linkClicked)
def linkHovered(self):
print('当鼠标滑过label2时触发事件')
def linkClicked(self):
print('当鼠标点击label4时触发事件')
if __name__=='__main__':
app=QApplication(sys.argv)
main = QLabelDemo()
main.show()
sys.exit(app.exec_())
3.2.QLabel标签快捷键的使用
import sys
from PyQtQtWidgets import *
class QLabelDemo(QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle('QLabel例子')
namelb1=QLabel('&Name',self)
nameed1=QLineEdit(self)
namelb1.setBuddy(nameed1)
namelb2=QLabel('&Password',self)
nameed2=QLineEdit(self)
namelb2.setBuddy(nameed2)
btnOK=QPushButton('&OK')
btnCancel=QPushButton('&Cancel')
mainlayout=QGridLayout(self)
mainlayout.addWidget(namelb1,0,0)
mainlayout.addWidget(nameed1,0,1,1,2)
mainlayout.addWidget(namelb2,1,0)
mainlayout.addWidget(nameed2,1,1,1,2)
mainlayout.addWidget(btnOK,2,1)
mainlayout.addWidget(btnCancel,2,2)
def link_hovered():
print('鼠标滑过label2标签时触发')
def link_clicked():
print('鼠标点击label4标签时触发')
if __name__=='__main__':
app=QApplication(sys.argv)
labelDemo=QLabelDemo()
labelDemo.show()
sys.exit(app.exec_())
#按下Alt+N快捷键可以切换到第一个文本框,因为这个文本框与QLabel进行了关联,QLabel组件设置了快捷方式"&Name"
4.QLineEdit
QLineEdit类是一个单行文本框控件,可以输入单行字符串,如果需要输入多行字符串,则使用QTextEdit类
常用方法:
setAlignment():文本对齐方式
clear():清除文本框内容
setEchoMode():设置文本框显示格式,允许输入的文本显示格式的值可以是:QLineEdit.(Normal/NoEcho 不显示任何输入字符且密码长度保密/Password 显示平台相关的密码掩码字符/PasswordEdchoOnEdit 显示密码)
setPlaceholderText():文本框浮显文字
setMaxlength():文本框允许输入的最大字符数
setReadOnly():设置文本框只读
setText():设置文本框内容
Text():返回文本框内容
setDragEnabled():文本框是否接受拖动
selectAll():全选
setFocus():得到焦点
setInputMask():设置掩码
setValidator():设置文本框的验证规则,可以是QIntValidator(限制输入整数),QDoubleValidator(限制输入浮点数),QRegexpValidator(检查输入是否符合正则表达式)
定义输入掩码的字符如下:
掩码由掩码字符和分隔字符串组成,后面可以跟一个分号和控股白字符,空白字符在编辑后会从文本中删除
掩码示例:
000.000.000.000;_ :IP地址,空白字符是"_"
HH:HH:HH:HH:HH:HH;_:MAC地址
0000-00-00:日期,空白字符串是空格
QLineEdit类的常用信号:
selectionChanged:只要选择改变触发事件
textChanged:修改文本内容触发事件
editingFinished:编辑文本结束触发事件
4.1.EchoMode的显示效果
from PyQtQtWidgets import QApplication,QLineEdit,QWidget,QFormLayout
import sys
class lineEditDemo(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('QLineEdit例子')
#初始化布局
flo=QFormLayout()
pNormalLineEdit=QLineEdit()
pNoEchoLineEdit=QLineEdit()
pPasswordlineEdit=QLineEdit()
pPasswordEchoOnEditlineEdit=QLineEdit()
#设置前面的提示输入什么信息
flo.addRow('Normal',pNormalLineEdit)
flo.addRow('NoEcho',pNoEchoLineEdit)
flo.addRow('Password',pPasswordlineEdit)
flo.addRow('PasswordEchoOnEdit',pPasswordEchoOnEditlineEdit)
#设置灰色的提示输入信息
pNormalLineEdit.setPlaceholderText('Normal')
pNoEchoLineEdit.setPlaceholderText('NoEcho')
pPasswordlineEdit.setPlaceholderText('Password')
pPasswordEchoOnEditlineEdit.setPlaceholderText('PasswordEchoOnEdit')
#设置显示效果
pNormalLineEdit.setEchoMode(QLineEdit.Normal)
pNoEchoLineEdit.setEchoMode(QLineEdit.NoEcho)
pPasswordlineEdit.setEchoMode(QLineEdit.Password)
pPasswordEchoOnEditlineEdit.setEchoMode(QLineEdit.PasswordEchoOnEdit)
self.setLayout(flo)
if __name__=='__main__':
app=QApplication(sys.argv)
win=lineEditDemo()
win.show()
sys.exit(app.exec_())
4.2.验证器
from PyQtQtWidgets import QApplication,QLineEdit,QWidget,QFormLayout
import sys
from PyQtQtCore import QRegExp
from PyQtQtGui import QIntValidator,QDoubleValidator,QRegExpValidator
class lineEditDemo(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('QLineEdit例子')
#初始化布局
flo=QFormLayout()
pIntLineEdit=QLineEdit()
pDoubleLineEdit=QLineEdit()
pValidatorLineEdit=QLineEdit()
#设置前面的提示输入什么信息
flo.addRow('整型',pIntLineEdit)
flo.addRow('浮点型',pDoubleLineEdit)
flo.addRow('字母和数字',pValidatorLineEdit)
#设置灰色的提示输入信息
pIntLineEdit.setPlaceholderText('整型')
pDoubleLineEdit.setPlaceholderText('浮点型')
pValidatorLineEdit.setPlaceholderText('字母和数字')
#构造验证器
#整型,范围0-99
pIntValidator=QIntValidator(self)
pIntValidator.setRange(1,99)
#浮点型,范围-360-360,精度:小数点后两位
pDoubleValidator=QDoubleValidator(self)
pDoubleValidator.setRange(-360,360)
pDoubleValidator.setNotation(QDoubleValidator.StandardNotation)
pDoubleValidator.setDecimals(2)
#字母和数字
reg=QRegExp('[a-zA-Z0-9]+$')
pValidator=QRegExpValidator(self)
pValidator.setRegExp(reg)
#控件与验证器链接
pIntLineEdit.setValidator(pIntValidator)
pDoubleLineEdit.setValidator(pDoubleValidator)
pValidatorLineEdit.setValidator(pValidator)
self.setLayout(flo)
if __name__=='__main__':
app=QApplication(sys.argv)
win=lineEditDemo()
win.show()
sys.exit(app.exec_())
4.3.输入掩码
from PyQtQtWidgets import QApplication,QLineEdit,QWidget,QFormLayout
import sys
class lineEditDemo(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('QLineEdit输入掩码例子')
flo=QFormLayout()
pIPLineEdit=QLineEdit()
pMACLineEdit=QLineEdit()
pDateLineEdit=QLineEdit()
PLicenseLineEdit=QLineEdit()
pIPLineEdit.setInputMask('000.000.000.000;_')
pMACLineEdit.setInputMask('HH:HH:HH:HH:HH:HH;_')
pDateLineEdit.setInputMask('0000-00-00')
PLicenseLineEdit.setInputMask('>AAAAA-AAAAA-AAAAA-AAAAA-AAAAA;#')
flo.addRow('数字掩码',pIPLineEdit)
flo.addRow('Mac掩码',pMACLineEdit)
flo.addRow('日期掩码',pDateLineEdit)
flo.addRow('许可证掩码',PLicenseLineEdit)
self.setLayout(flo)
if __name__=='__main__':
app=QApplication(sys.argv)
win=lineEditDemo()
win.show()
sys.exit(app.exec_())
4.4.综合案例
from PyQtQtWidgets import QApplication,QLineEdit,QWidget,QFormLayout
from PyQtQtGui import QIntValidator,QDoubleValidator,QFont
from PyQtQtCore import Qt
import sys
class lineEdit(QWidget):
def __init__(self):
super().__init__()
e1=QLineEdit()
#设置整型检查器
e1.setValidator(QIntValidator())
#设置最大字符长度
e1.setMaxLength(4)
#设置文本对齐方式
e1.setAlignment(Qt.AlignRight)
#设置字体
e1.setFont(QFont('Arial',20))
e2=QLineEdit()
#设置浮点数范围和精度
e2.setValidator(QDoubleValidator(0.99,99.99,2))
flo=QFormLayout()
#设置提示
flo.addRow('integer validator',e1)
flo.addRow('Double validator',e2)
e3=QLineEdit()
#设置掩码
e3.setInputMask('+99_9999_999999')
flo.addRow('Input Mask',e3)
e4=QLineEdit()
#设置信号发射
e4.textChanged.connect(self.textChanged)
flo.addRow('Text Changed',e4)
e5=QLineEdit()
#设置展示效果
esetEchoMode(QLineEdit.Password)
flo.addRow('Password',e5)
e6=QLineEdit('Hello PyQt5')
#设置为只读
esetReadOnly(True)
flo.addRow('Read Only',e6)
eeditingFinished.connect(self.enterPress)
self.setLayout(flo)
self.setWindowTitle('QLineEdit例子')
def textChanged(self,text):
print('输入的内容为'+text)
def enterPress(self):
print('已输入值')
if __name__=='__main__':
app=QApplication(sys.argv)
win=lineEdit()
win.show()
sys.exit(app.exec_())
QTextEdit
1.基本使用
QTextEdit类是一个多行文本框控件,可以显示多行文本内容,当文本内容超出显示范围时,可以显示滚动条,QTextEdit不仅可以显示文本,还可以显示HTML文档
常用方法
setPlainText():设置多行文本框的文本内容
toPlainText():返回多行文本框的文本内容
setHtml():设置多行文本框的内容为HTML,HTML文档是描述网页的
toHtml():返回多行文本框的HTML文档内容
clear():消除多行文本框的内容
from PyQtQtWidgets import QApplication,QWidget,QTextEdit,QVBoxLayout,QPushButton
import sys
class TextEditDemo(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('QTextEdit例子')
self.resize(300,270)
self.textEdit=QTextEdit()
self.btn1=QPushButton('显示文本')
self.btn2=QPushButton('显示HTML')
layout=QVBoxLayout()
layout.addWidget(self.textEdit)
layout.addWidget(self.btn1)
layout.addWidget(self.btn2)
self.setLayout(layout)
self.btn1.clicked.connect(self.btn1_clicked)
self.btn2.clicked.connect(self.btn2_clicked)
def btn1_clicked(self):
self.textEdit.setPlainText('Hello PyQt5\n单击按钮')
def btn2_clicked(self):
self.textEdit.setHtml("<font color='red' size=6><red>Hello PyQt5\n单击按钮</font>")
if __name__=='__main__':
app=QApplication(sys.argv)
win=TextEditDemo()
win.show()
sys.exit(app.exec_())
2.监控文本选中
QAbstractButton
PyQt中根据不同使用场景将按钮划分为不同的表现形式,按钮的基类是QAbstractButton,提供了按钮的通用功能,但是抽象类,不能实例化,必须由其他按钮继承,来实现不同的功能,不同的表现形式
常见的按钮类包括:QPushButton,QToolButton,QRadioButton 和 QCheckBox,这些按钮类均继承自QAbstractButton类,根据各自的使用场景通过图形展现出来,QAbstractButton提供的状态如下:
isDown():按钮是否被按下
isChecked():按钮是否已标记
isEnable():按钮是否可以被点击
isCheckAble():按钮是否为可标记的
setAutoRepate():是否在用户长按时可以自动重复执行
QAbstractButton提供的信号如下:
Pressed:鼠标左键按下时发射信号
Released:鼠标左键被释放时发射信号
Clicked:鼠标按下然后被释放或快捷键被释放时发射信号
Toggled:按钮的标记状态发生改变时发射信号
1.QPushButton
QPushButton类是一种命令按钮,可以显示文本或图标,常用方法:
setCheckable():设置按钮是否被选中,若为True,表示按钮保持已点击和释放状态
toggle():在按钮状态之间进行切换
setIcon():设置按钮上的图标
setEnabled():设置按钮是否可用,若不可用点击它不会发射信号
isChecked():检查按钮状态,返回True或False
setDefault():设置按钮的默认状态
setText():设置按钮的显示文本
text():返回按钮的显示文本
通过&和名字搭配来实现 Alt + X 快捷键
from PyQtQtGui import *
from PyQtQtWidgets import *
import sys
class Form(QDialog):
def __init__(self):
super().__init__()
layout=QVBoxLayout()
self.btn1=QPushButton('Button1')
self.btn1.setCheckable(True)
self.btn1.toggle()
self.btn1.clicked.connect(lambda:self.whichbtn(self.btn1))
self.btn1.clicked.connect(self.btnstate)
layout.addWidget(self.btn1)
self.btn2=QPushButton('image')
self.btn2.setIcon(QIcon(QPixmap('C:\\Users\\PC\\Desktop\\快捷方式\\test.ico')))
self.btn2.clicked.connect(lambda:self.whichbtn(self.btn2))
layout.addWidget(self.btn2)
self.setLayout(layout)
self.btn3=QPushButton('Disabled')
self.btn3.setEnabled(False)
layout.addWidget(self.btn3)
self.btn4=QPushButton('Dow&nload')
self.btn4.setDefault(True)
self.btn4.clicked.connect(lambda:self.whichbtn(self.btn4))
layout.addWidget(self.btn4)
self.setWindowTitle('Button Demo')
def btnstate(self):
if self.btn1.isChecked():
print('Button Pressed')
else:
print('Button Released')
def whichbtn(self,btn):
print('Clicked Button is '+btn.text())
if __name__=='__main__':
app=QApplication(sys.argv)
btnDemo=Form()
btnDemo.show()
sys.exit(app.exec_())
2.QRadioButton
QRadioButton提供了一组可供选择的按钮和文本标签,用户可以选择其中的一个选项,标签用于显示对应的文本信息,单选钮可以切换为on或off,即checked或unchecked,主要提供“多选一”功能。单选钮控件默认是独占的,对于继承自同一个父类的多个单选钮属于同一个按钮组合,在单选钮里依次只能选择一个单选钮,如果需要多个独占的按钮组合,需要将他们放在QGroupBox或QButtonGroup中
常用方法:
setCheckable():设置按钮是否被选中,若为True,表示单选钮将保持已点击和释放状态
isChecked():返回单选钮的状态,返回值为True或False
setText():设置单选钮的显示文本
text():返回单选钮的显示文本
在QRadioButton中,toggled信号是在切换单选钮状态时发射的,而clicked信号则是在每次点击单选钮时都会发射,因此toggled信号更适合用于状态监控
import sys
from PyQtQtCore import *
from PyQtQtGui import *
from PyQtQtWidgets import *
class Radiodemo(QWidget):
def __init__(self):
super().__init__()
layout=QHBoxLayout()
self.btn1=QRadioButton('b1')
self.btn1.setChecked(True)
self.btn1.toggled.connect(lambda:self.btnstate(self.btn1))
layout.addWidget(self.btn1)
self.btn2=QRadioButton('b2')
self.btn2.toggled.connect(lambda:self.btnstate(self.btn2))
layout.addWidget(self.btn2)
self.setLayout(layout)
self.setWindowTitle('Radio Demo')
def btnstate(self,btn):
if btn.isChecked():
print(btn.text()+"is selected")
else:
print(btn.text()+"is deselected")
if __name__=='__main__':
app=QApplication(sys.argv)
Radiodemo=Radiodemo()
Radiodemo.show()
sys.exit(app.exec_())
3.QCheckBox
QCheckBox提供了一组带文本标签的复选框,复选框可以显示文本或图标,只要复选框被选中或取消选中,就会发射一个stateChanged信号,此外QCheckBox还提供了半选中状态来表明"没有变化",当需要为用户提供一个选中或未选中复选框的选择时,可以通过setTristate()来使它生效,并用CheckState()来检查状态
常用方法
setChecked():设置复选框的状态,设置为True时表示选中复选框(包括半选中)
checkState():返回三态复选框状态
setText():设置显示文本
text():返回显示文本
isChecked():检查是否被选中
setTriState():设置复选框为一个三态复选框
三态复选框由三种状态
Qt.Checked:值为 0,没有被选中
Qt.Paritially:值为 1,被半选中
Qt.Unchecked:值为 2,被选中
import sys
from PyQtQtCore import *
from PyQtQtWidgets import *
from PyQtQtCore import Qt
class CheckBoxDemo(QWidget):
def __init__(self):
super().__init__()
groupbox=QGroupBox('Checkboxes')
groupbox.setFlat(True)
layout=QHBoxLayout()
self.Checkbox1=QCheckBox('&Checkbox1')
self.Checkbox1.setChecked(True)
self.Checkbox1.stateChanged.connect(lambda:self.btnstate(self.Checkbox1))
layout.addWidget(self.Checkbox1)
self.Checkbox2=QCheckBox('Checkbox2')
self.Checkbox2.toggled.connect(lambda:self.btnstate(self.Checkbox2))
layout.addWidget(self.Checkbox2)
self.Checkbox3=QCheckBox('Checkbox3')
self.Checkbox3.setTristate(True)
self.Checkbox3.setCheckState(Qt.PartiallyChecked)
self.Checkbox3.stateChanged.connect(lambda:self.btnstate(self.Checkbox3))
layout.addWidget(self.Checkbox3)
groupbox.setLayout(layout)
mainLayout=QVBoxLayout()
mainLayout.addWidget(groupbox)
self.setLayout(mainLayout)
self.setWindowTitle('CheckBox Demo')
def btnstate(self,btn):
chk1status=self.Checkbox1.text()+',isChecked='+str(self.Checkbox1.isChecked())+',checkstate='+str(self.Checkbox1.checkState())+'\n'
chk2status=self.Checkbox2.text()+',isChecked='+str(self.Checkbox2.isChecked())+',checkstate='+str(self.Checkbox2.checkState())+'\n'
chk3status=self.Checkbox3.text()+',isChecked='+str(self.Checkbox3.isChecked())+',checkstate='+str(self.Checkbox3.checkState())+'\n'
print(chk1status,chk2status,chk3status)
if __name__=='__main__':
app=QApplication(sys.argv)
CheckBoxDemo=CheckBoxDemo()
CheckBoxDemo.show()
sys.exit(app.exec_())
4.QComboBox(下拉列表框)
QComBox是一个集按钮和下拉选项于一体的控件,也被称为下拉列表框
常用方法如下:
addItem():添加一个下拉选项
addItems():从列表中添加下拉选项
Clear():删除下拉选项集合中的所有选项
count():返回下拉选项集合中的数目
currentText():返回下拉选项的文本
item Text(i):返回索引为 i 的 item 的选项文本
currentIndex():返回选中项的索引
setItemText(int index,text):改变序号为 index 项的文本
常用信号:
Acticated:选中一个下拉选项时发射信号
currentIndexChanged:下拉选项的索引改变时发射信号
highlighted:选中一个已选中的下拉选项时发射信号
import sys
from PyQtQtCore import *
from PyQtQtGui import *
from PyQtQtWidgets import *
class ComboBox(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('ComBox 例子')
self.resize(300,90)
layout=QVBoxLayout()
self.lb1=QLabel('')
self.cb=QComboBox()
self.cb.addItem('C')
self.cb.addItem('C++')
self.cb.addItems(['Java','C#','Python'])
self.cb.currentIndexChanged.connect(self.selectionchange)
layout.addWidget(self.lb1)
layout.addWidget(self.cb)
self.setLayout(layout)
def selectionchange(self,i):
self.lb1.setText(self.cb.currentText())
print('Item in the list are:')
for count in range(self.cb.count()):
print('item'+str(count)+'='+self.cb.itemText(count))
print('Current index',i,'selection changed',self.cb.currentText())
if __name__=='__main__':
app=QApplication(sys.argv)
CheckBoxDemo=ComboBox()
CheckBoxDemo.show()
sys.exit(app.exec_())
5.QSpinBox(计数器)
QSpinBox 是一个计数器控件,允许用户选择一个整数值,通过单击向上/向下按钮或按键盘上的箭头,或者输入值来改变当前值
默认取值范围是0-99,步长是1
QSpinBox用于处理整数值,QDoubleSpinBox用于处理浮点数,默认精度是2,可以通过setDecimals()来改变
setMinimum():设置计数器下界
setMaximum():设置计数器上界
setRange():设置计数器最大最小和步长
setValue():设置当前值
Value():返回当前值
singleStep():设置步长
每次单击按钮,计数器都会发射 valueChanged 信号,可以通过 value() 获得当前值
import sys
from PyQtQtCore import *
from PyQtQtGui import *
from PyQtQtWidgets import *
class SpinBox(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('SpinBox 例子')
self.resize(300,100)
layout=QVBoxLayout()
self.l1=QLabel('current value:')
self.l1.setAlignment(Qt.AlignCenter)
layout.addWidget(self.l1)
self.sp=QSpinBox()
layout.addWidget(self.sp)
self.sp.valueChanged.connect(self.valueChanged)
self.setLayout(layout)
def valueChanged(self):
self.l1.setText('current value:'+str(self.sp.value()))
if __name__=='__main__':
app=QApplication(sys.argv)
CheckBoxDemo=SpinBox()
CheckBoxDemo.show()
sys.exit(app.exec_())
6.QSlider(滑动条)
QSlider控件提供了一个垂直或水平的滑动条,滑动条是一个用于控制有界值的典型控件,类似扬声器的调音展示,滑块条控件水平/垂直可以通过构造函数设置:
self.sp=QSlider(Qt.Horizontal)
self.sp=QSlider(Qt.Vertical)
常用方法:
setMinimum():设置最小值
setMaxnimum():设置最大值
setSingleStep():设置步长
setValue():设置滑动条控件的值
value():获得滑动条控件的值
setTickInterval():设置刻度间隔
setTickPosition():设置刻度标记的位置,可以输入一个枚举值,这个枚举值指定刻度线相对滑块和用户操作的位置,枚举值可以是:
QSlider.(NoTicks/TicksBothSides/TicksAbove/TicksBelow/TicksLeft/TicksRight)
常用信号:
valueChanged/sliderPressed/sliderMoved/sliderReleased
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class SliderDemo(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('QSlder例子')
self.resize(300,100)
layout=QVBoxLayout()
self.l1=QLabel('Hello')
self.l1.setAlignment(Qt.AlignCenter)
layout.addWidget(self.l1)
#水平方向
self.s1=QSlider(Qt.Horizontal)
#设置最小值
self.s1.setMinimum(10)
#设置最大值
self.s1.setMaximum(50)
#步长
self.s1.setSingleStep(3)
#当前值
self.s1.setValue(20)
#刻度位置
self.s1.setTickPosition(QSlider.TicksBelow)
#设置刻度
self.s1.setTickInterval(5)
layout.addWidget(self.s1)
#连接信号槽
self.s1.valueChanged.connect(self.valuechange)
self.setLayout(layout)
def valuechange(self):
print('current slider value ={}'.format(self.s1.value))
size=self.s1.value()
self.l1.setFont(QFont('Arial',size))
if __name__=='__main__':
app=QApplication(sys.argv)
CheckBoxDemo=SliderDemo()
CheckBoxDemo.show()
sys.exit(app.exec_())
7.QDialog
Qdialog类的子类主要要QMessageBox,QFileDialog,QFontDialog,QInputDialog
常用方法:
setWindowTitle():设置对话框标题
setWindowModality():设置窗口模态:Qt.(NonModal 非模态/WindowModal 窗口模态/ApplicationModal 应用程序模态)
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class DialogDemo(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('DialogDemo')
self.resize(350,300)
self.btn=QPushButton(self)
self.btn.setText('弹出对话框')
self.btn.move(50,50)
self.btn.clicked.connect(self.showdialog)
def showdialog(self):
dialog=QDialog()
btn=QPushButton('ok',dialog)
btn.move(50,50)
dialog.setWindowTitle('Dialog')
dialog.setWindowModality(Qt.ApplicationModal)
dialog.exec_()
if __name__=='__main__':
app=QApplication(sys.argv)
CheckBoxDemo=DialogDemo()
CheckBoxDemo.show()
sys.exit(app.exec_())
7.1.QMessageBox
QMessageBox是一种通用的弹出式对话框,用于显示消息,允许用户通过单击不同的标准按钮对消息进行反馈,每个标准按钮都有一个预定义的文本,角色和十六进制数,常用的弹出式对话框有提示,询问,警告等,显示只是图标不同,其他都一致
常用方法:
information(QWidget parent,title,text,buttons,defaultButton)
question(QWidget parent,title,text,buttons,defaultButton)
warning(QWidget parent,title,text,buttons,defaultButton)
ctitical(QWidget parent,title,text,buttons,defaultButton)
about(QWidget parent,title,text)
setTitle()
setText()
setIcon()
QMessageBox的标准按钮类型(buttons):
QMessage(OK/Cancel/Yes/No/About/Retry/Ignore)
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class MyWindow(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('QMessageBox例子')
self.resize(300,100)
self.myButton=QPushButton(self)
self.myButton.setText('点击弹出消息框')
self.myButton.clicked.connect(self.msg)
def msg(self):
reply=QMessageBox.information(self,'标题','正文',QMessageBox.Yes|QMessageBox.No,QMessageBox.Yes)
print(reply)
if __name__=='__main__':
app=QApplication(sys.argv)
CheckBoxDemo=MyWindow()
CheckBoxDemo.show()
sys.exit(app.exec_())
7.2.QInputDialog
QInputDialog控件是一个标准对话框,由一个文本框和OK/Cancel按钮组成,常用方法:
getInt()/getDouble()/getText()/getItem()
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class InputDialogDemo(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('InputDialog例子')
layout=QFormLayout()
self.btn1=QPushButton('获得列表选项')
self.btn1.clicked.connect(self.getItem)
self.l1=QLineEdit()
layout.addRow(self.btn1,self.l1)
self.btn2=QPushButton('获得字符串')
self.btn2.clicked.connect(self.getText)
self.l2=QLineEdit()
layout.addRow(self.btn2,self.l2)
self.btn3=QPushButton('获得整数')
self.btn3.clicked.connect(self.getInt)
self.l3=QLineEdit()
layout.addRow(self.btn3,self.l3)
self.setLayout(layout)
def getItem(self):
items=('C','C++','Java')
item,ok=QInputDialog.getItem(self,'select input dialog','语言列表',items,0,False)
if ok and item:
self.l1.setText(item)
def getText(self):
text,ok=QInputDialog.getText(self,'Text Input Dialog','输入姓名')
if ok:
self.l2.setText(str(text))
def getInt(self):
num,ok=QInputDialog.getInt(self,'integer input dialog','输入数字')
if ok:
self.le3.setText(str(num))
if __name__=='__main__':
app=QApplication(sys.argv)
CheckBoxDemo=InputDialogDemo()
CheckBoxDemo.show()
sys.exit(app.exec_())
7.3.QFontDialog
字体选择对话框,使用getFont()类吃泡面字体选择对话框中选择文本的显示大小样式和格式
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class FontDialogDemo(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('FontDialog例子')
layout=QVBoxLayout()
self.fontButton=QPushButton('Choose Font')
self.fontButton.clicked.connect(self.getFont)
layout.addWidget(self.fontButton)
self.fontLineEdit=QLabel('测试字体')
layout.addWidget(self.fontLineEdit)
self.setLayout(layout)
def getFont(self):
font,ok=QFontDialog.getFont()
if ok:
self.fontLineEdit.setFont(font)
if __name__=='__main__':
app=QApplication(sys.argv)
CheckBoxDemo=FontDialogDemo()
CheckBoxDemo.show()
sys.exit(app.exec_())
7.4.QFileDialog
QFileDialog是用于打开和保存文件的对话框,打开文件时使用了文本过滤器,用于显示指定扩展名的文件,也可以设置使用QFileDialog打开文件时的起始目录和指定扩展名的文件,常用方法:
getOpenFileName():返回用户选择文件的名称并打开
getSaveFileName():使用用户选择的文件名并保存文件
setFileMode():可选文件类型,枚举常量:QFileDialog.(AnyFile/ExistingFile/Directory/ExistingFiles)
setFilter():设置过滤器,只显示过滤器允许的文件类型
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class FileDilogDemo(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('FileDiolog例子')
layout=QVBoxLayout()
self.btn=QPushButton('加载图片')
self.btn.clicked.connect(self.getfile)
layout.addWidget(self.btn)
self.le=QLabel('')
layout.addWidget(self.le)
self.btn1=QPushButton('加载文本文件')
self.btn1.clicked.connect(self.getfiles)
layout.addWidget(self.btn1)
self.contents=QTextEdit()
layout.addWidget(self.contents)
self.setLayout(layout)
def getfile(self):
#第一个参数指定父组件,第二个参数是QFileDialog的标题,第三个是默认打开目录,第四个是文件名过滤器,返回路径和文件类型
fname,classification=QFileDialog.getOpenFileName(self,'Open File','C:/','Image files(*.jpg *.gif)')
self.le.setPixmap(QPixmap(fname))
def getfiles(self):
#两种方法
dig=QFileDialog()
dig.setFileMode(QFileDialog.AnyFile)
dig.setFilter(QDir.Files)
if dig.exec_():
print(dig.exec_())
filename=dig.selectedFiles()
print(filename)
f=open(filename[0],'r',encoding='utf-8')
with f:
data=f.read()
self.contents.setText(data)
if __name__=='__main__':
app=QApplication(sys.argv)
CheckBoxDemo=FileDilogDemo()
CheckBoxDemo.show()
sys.exit(app.exec_())
8.QPainter
QPainter类在QWidget(控件)上执行绘图操作,从直线到饼图都可以做到。绘制操作在QWidget.paintEvent()中完成,绘制方法必须放在QtGui.QPainter对象的begin()和end()之间,QPainter类在控件或其他绘图设备上执行较低级别的图形绘制功能,常用方法:
begin():开始在目标设备上绘制
drawArc():在起始角度和最终角度之间画弧
drawEllipse():在一个矩形内画一个椭圆
drawLine(int x1,int y1,int x2,int y2):绘制一条指定端点坐标的线
drawPixmap():从图像文件中提取Pixmap并将其显示在指定在特定的位置
drawPolygon():使用坐标组绘制多边形
drawRect(int x,int y,int w,int h):以给定的宽度和高度从左上角坐标绘制一个矩形
drawText():显示给定坐标处的文字
fillRect():使用QColor参数填充矩形
setBrush():设置画笔风格
setPen():设置用于绘制的笔的颜色,大小和样式
还可以设置画笔风格PenStyle:Qt.(NoPen/SolidLine/DashLine/DotLine/DashDotLine/DashDotDotLine/MPenStyle)\
8.1.绘制文字
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class DrawingDemo(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('在窗口中绘制文字')
self.resize(300,200)
self.text='坚持'
def paintEvent(self,event):
painter=QPainter(self)
painter.begin(self)
#自定义绘制方法
self.drawText(event,painter)
painter.end()
def drawText(self,event,painter):
#设置画笔颜色
painter.setPen(QColor(168,34,3))
#设置字体
painter.setFont(QFont('SimSun',20))
#绘制文字
painter.drawText(event.rect(),Qt.AlignCenter,self.text)
if __name__=='__main__':
app=QApplication(sys.argv)
CheckBoxDemo=DrawingDemo()
CheckBoxDemo.show()
sys.exit(app.exec_())
8.2.绘制点
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys,math
class DrawingDemo(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('在窗口中绘制点')
self.resize(300,200)
def paintEvent(self,event):
painter=QPainter(self)
painter.begin(self)
#自定义绘制方法
self.drawPoint(painter)
painter.end()
def drawPoint(self,painter):
#设置画笔颜色
painter.setPen(Qt.red)
#每次调整窗口大小都会产生一个绘图事件
size=self.size()
for i in range(1000):
#绘制正弦图形,周期(-100,100)
x=100*(-1+2*i/1000)+size.width()/2
y=-50*math.sin((x-size.width()/2)*math.pi/50)+size.height()/2
painter.drawPoint(x,y)
if __name__=='__main__':
app=QApplication(sys.argv)
CheckBoxDemo=DrawingDemo()
CheckBoxDemo.show()
sys.exit(app.exec_())
9.QPen
QPen是一个基本的图形对象,用于绘制直线,曲线或者给轮廓画出矩形,椭圆,多边形及其他形状
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class DrawingDemo(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(300,300,280,270)
self.setWindowTitle('钢笔样式例子')
def paintEvent(self,e):
painter=QPainter()
painter.begin(self)
self.drawLines(painter)
painter.end()
def drawLines(self,painter):
pen=QPen(Qt.black,2,Qt.SolidLine)
painter.setPen(pen)
#参数为(起始x坐标,起始y坐标,线长,终点y坐标)
painter.drawLine(20,40,250,80)
pen.setStyle(Qt.DashLine)
painter.setPen(pen)
painter.drawLine(20,80,250,80)
pen.setStyle(Qt.DashDotLine)
painter.setPen(pen)
painter.drawLine(20,120,250,120)
if __name__=='__main__':
app=QApplication(sys.argv)
CheckBoxDemo=DrawingDemo()
CheckBoxDemo.show()
sys.exit(app.exec_())
10.QBrush
QBrush(画刷)是一个基本的图形对象,用于填充形状,QBrush有三种类型:预定义,过渡和纹理图案
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class Draw(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(300,300,365,280)
self.setWindowTitle('画刷例子')
self.show()
def paintEvent(self,e):
painter=QPainter()
painter.begin(self)
self.drawLines(painter)
painter.end()
def drawLines(self,painter):
brush=QBrush(Qt.SolidPattern)
painter.setBrush(brush)
painter.drawRect(10,15,90,60)
brush=QBrush(Qt.Dense1Pattern)
painter.setBrush(brush)
painter.drawRect(130,15,90,60)
brush=QBrush(Qt.Dense2Pattern)
painter.setBrush(brush)
painter.drawRect(250,15,90,60)
brush=QBrush(Qt.Dense3Pattern)
painter.setBrush(brush)
painter.drawRect(10,105,90,60)
if __name__=='__main__':
app=QApplication(sys.argv)
demo=Draw()
demo.show()
sys.exit(app.exec_())
11.QPixmap
QPixmap类用于绘制设备的图标显示,它可以作为一个QPaintDevice对象,也可以加载到一个控件中,常用方法:
copy():从QRect对象复制到QPixmap对象
fromImage():从QImage对象转换到QPixmap对象
grabWidget():从给定窗口小空间创建一个像素图
load():加载图像文件为QPixmap对象
save():将QPixmap对象保存为文件
toImage():将QPixmap对象转换为QImage对象
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
if __name__=='__main__':
app=QApplication(sys.argv)
win=QWidget()
la1=QLabel()
la1.setPixmap(QPixmap('C:\\Users\\PC\\Desktop\\快捷方式\\test.ico'))
vbox=QVBoxLayout()
vbox.addWidget(la1)
win.setLayout(vbox)
win.setWindowTitle('QPixmap例子')
win.show()
sys.exit(app.exec_())
11.Drag与Drop
基于MIME类型的拖曳数据传输是基于QDrag类的,QMimeData对象将关联的数据与其对应的MIME类型相关联(MIME类型的数据可以简单理解为互联网上的各种资源,比如文本,音频和视频资源等等,互联网上的每一种资源都属于一种MIME类型的数据)
MimeData类函数允许检测和使用方便的MIME类型:
许多 QWidget 对象都支持拖曳动作,允许拖曳数据的控件必须设置QWidget.setDragEnable()为True,另外,控件应该允许响应拖曳事件,以便存储所拖曳的数据,常用的拖曳事件:
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class Combo(QComboBox):
def __init__(self,title,parent):
super().__init__()
self.setAcceptDrops(True)
def dragEnterEvent(self,e):
print(e)
if e.mimeData().hasText():
e.accept()
else:
e.ignore()
def dropEvent(self,e):
self.addItem(e.mimeData().text())
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
lo=QFormLayout()
lo.addRow(QLabel('请把左边的文本拖曳到右边的下拉菜单中'))
edit=QLineEdit()
edit.setDragEnabled(True)
com=Combo("Button",self)
lo.addRow(edit,com)
self.setLayout(lo)
self.setWindowTitle('简单的拖曳例子')
if __name__=='__main__':
app=QApplication(sys.argv)
demo=Example()
demo.show()
sys.exit(app.exec_())
12.QClipboard
QClipboard类提供了对系统剪贴板的方法,可以在应用程序之间复制和粘贴数据,它的操作类似QDrag类,并使用类似的数据类型,QApplication类有一个静态方法clipboard(),它返回对剪贴板对象的引用,任何类型的MimeData都可以从剪贴板复制或粘贴
常用方法:
clear()/setImage()/setMimeData()/setPixmap()/setText()/text
常用信号:dataChanged:当剪贴板内容发生变化时发射信号
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
import os
class Form(QDialog):
def __init__(self):
super().__init__()
textCopyButton=QPushButton('&Copy Text')
textPasteButton=QPushButton('Paste &Text')
htmlCopyButton=QPushButton('C&opy HTML')
htmlPasteButton=QPushButton('Paste &HTML')
imageCopyButton=QPushButton('Copy &Image')
imagePasteButton=QPushButton('Paste I&mage')
self.textLabel=QLabel('Qriginal text')
self.imageLabel=QLabel()
self.imageLabel.setPixmap(QPixmap('C:/Users/PC/Desktop/快捷方式/test.ico'))
layout=QGridLayout()
layout.addWidget(textPasteButton,0,0)
layout.addWidget(imageCopyButton,0,1)
layout.addWidget(htmlCopyButton,0,2)
layout.addWidget(textCopyButton,1,0)
layout.addWidget(imagePasteButton,1,1)
layout.addWidget(htmlPasteButton,1,2)
layout.addWidget(self.textLabel,2,0,1,2)
layout.addWidget(self.imageLabel,2,2)
self.setLayout(layout)
textCopyButton.clicked.connect(self.copyText)
textPasteButton.clicked.connect(self.pasteText)
htmlCopyButton.clicked.connect(self.copyHtml)
htmlPasteButton.clicked.connect(self.pasteHtml)
imagePasteButton.clicked.connect(self.pasteImage)
imageCopyButton.clicked.connect(self.copyImage)
self.setWindowTitle('ClipboardDemo')
def copyText(self):
clipboard=QApplication.clipboard()
clipboard.setText('I have been clipped')
def pasteText(self):
clipboard=QApplication.clipboard()
self.textLabel.setText(clipboard.text())
def copyImage(self):
clipboard=QApplication.clipboard()
clipboard.setPixmap(QPixmap('C:/Users/PC/Desktop/快捷方式/test.ico'))
def pasteImage(self):
clipboard=QApplication.clipboard()
self.imageLabel.setPixmap(clipboard.pixmap())
def copyHtml(self):
MimeData=QMimeData()
MimeData.setHtml('<b><font color="red">Red</font></b>')
clipboard=QApplication.clipboard()
clipboard.setMimeData(MimeData)
def pasteHtml(self):
clipboard=QApplication.clipboard()
MimeData=clipboard.mimeData()
if MimeData.hasHtml():
self.textLabel.setText(MimeData.html())
if __name__=='__main__':
app=QApplication(sys.argv)
demo=Form()
demo.show()
sys.exit(app.exec_())
13.QCalendar
QCalendar是一个日历控件,它提供了一个基于月份的视图,允许用户通过鼠标或键盘选择日期,默认选择今天日期,常用方法:
setDataRange()/setFirstDayOfWeek()重设星期的第一天Qt.(Mondy/Tuesday/Wednesday/Thursday/Friday/Saturday/Sunday)/setMinimumDate()/setMaximumDate()/setSelectedDate()/minimumDate()/maximumDate()/
selectedDate()/setGridivisible() 设置日历控件是否显示网格
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
from PyQt5 import QtCore
class CalendarExample(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.ca1=QCalendarWidget(self)
self.ca1.setMinimumDate(QDate(1980,1,1))
self.ca1.setMaximumDate(QDate(3000,1,1))
self.ca1.setGridVisible(True)
self.ca1.move(20,20)
#发射QtCore.QDate信号
self.ca1.clicked[QtCore.QDate].connect(self.showDate)
self.lb1=QLabel(self)
date=self.ca1.selectedDate()
self.lb1.setText(date.toString('yyy-MM-dd dddd'))
self.lb1.move(20,300)
self.setGeometry(100,100,400,350)
self.setWindowTitle('Calendar例子')
def showDate(self,date):
self.lb1.setText(date.toString('yy-MM-dd dddd'))
if __name__=='__main__':
app=QApplication(sys.argv)
demo=CalendarExample()
demo.show()
sys.exit(app.exec_())
14.QDateTimeEdit
QDateTimeEdit是一个允许用户编辑事件的控件,通过setDisplayFormat()函数来设置显示的日期时间格式,常用方法:
setDisplayFormat():yyyy/MM/dd/HH/mm/ss
setMinimumDate()/setMaximumDate()/time()/date()
常用信号:dateChanged/dateTimeChanged/timeChanged
设置显示格式时,QDateEdit用来编辑控件的日期,QTimeEdit用来编辑控件的时间,仅包括小时,分组和秒
dateEdit=QDateEdit(self)
timeEdita=QTimeEdit(self)
dateEdit=setDisplayFormat('yyy-MM-dd')
timeEdit=setDiaplayFormat('HH:mm:ss')
设置弹出日历时要注意,用来弹出日历的类只有QDateTimeEdit和QDateEdit,而QTimeEdit虽然在语法上可以设置弹出日历,但是不起作用
dateEdit=QDateEdit(self)
dateTimeEdit=QDateTimeEdit(self)
dateEdit.setCalendarPorup(True)
dateTimeEdit.setCalendarPorup(True)
默认情况下,如果使用QDateTime类构造时不指定日期时间,系统会设置一个和本地相同是时间格式,值为2000.1.1.00:00,也可以手动设置,除通过构造函数指定显示日期外,也可以由QDateTimeEdit提供的槽函数设置,比如setDateTime()/setDate()/setTime()
dateTimeEdit=QDateTimeEdit(self)
dateTimeEdit2=QDateTimeEdit(QDateTime.currentDateTime(),self)
dateEdit=QDateTimeEdit(QDateTime.currentDate(),self)
timeEdit=QDateTimeEdit(QDateTime.currentTime(),self)
示例使用QDateTimeEdit
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class DateTimeEditDemo(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('QDateTimeEdit例子')
self.resize(300,90)
vlayout=QVBoxLayout()
self.dateEdit=QDateTimeEdit(QDateTime.currentDateTime(),self)
self.dateEdit.setDisplayFormat('yyy-MM-dd HH:mm:ss')
#设置最小日期
self.dateEdit.setMaximumDate(QDate.currentDate().addDays(365))
self.dateEdit.setCalendarPopup(True)
self.dateEdit.dateChanged.connect(self.onDateChanged)
self.dateEdit.dateTimeChanged.connect(self.onDateTimeChanged)
self.dateEdit.timeChanged.connect(self.onTimeChanged)
self.btn=QPushButton('获得日期和时间')
self.btn.clicked.connect(self.onButtonClick)
vlayout.addWidget(self.dateEdit)
vlayout.addWidget(self.btn)
self.setLayout(vlayout)
def onDateChanged(self,date):
print(date)
def onDateTimeChanged(self,dateTime):
print(dateTime)
def onTimeChanged(self,time):
print(time)
def onButtonClick(self):
dateTime=self.dateEdit.dateTime()
#最大日期
maxDate=self.dateEdit.maximumDate()
#最大日期时间
maxDateTime=self.dateEdit.maximumDateTime()
#最大时间
maxTime=self.dateEdit.maximumTime()
print('\n选择日期与时间')
print('dateTime={}'.format(str(dateTime)))
print('maxTime={}'.format(str(maxTime)))
print('maxDateTime={}'.format(str(maxDateTime)))
print('maxTime={}'.format(str(maxTime)))
if __name__=='__main__':
app=QApplication(sys.argv)
demo=DateTimeEditDemo()
demo.show()
sys.exit(app.exec_())
1QMenuBar
QMainWindow对象的标题栏下方,水平的QMenuBar被保留显示QMenu对象,QMenu类提供了一个可以添加到菜单栏的小控件,也用于创建上下文菜单和弹出菜单,每个QMenu对象都可以包含若干个QAction对象或级联的QMenu对象
要创建一个弹出菜单,PyQt API 提供了creatPopupMenu()函数:munuBar()返回主窗口的QMenuBar对象,addMenu()可以将菜单添加到菜单栏中,通过addAction()函数可以在菜单中进行添加操作,一些重要方法:
menuBar():返回主窗口的QMenuBar()对象
addMenu():想菜单栏中添加新的QMenu对象
addAction():向QMenu小控件中添加一个操作按钮,其中包含文本或图标
setEnabled():将操作按钮状态设置为启用/禁用
addSeperator():在菜单栏中添加分割线
clear():删除菜单/菜单栏内容
setShortcut():将快捷键关联到操作按钮
setText()/setTitle()/text()/title()
单击任何QAction按钮时,QMenu对象都会发射triggered信号
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class MenuDemo(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
layout=QHBoxLayout()
bar=self.menuBar()
file=bar.addMenu('File')
#addMenu接受一个字符串对象或QAction对象
edit=file.addMenu('Edit')
edit.addAction('copy')
edit.addAction('paste')
file.addAction('New')
save=QAction('Save',self)
save.setShortcut('Ctrl+S')
file.addAction(save)
quit=QAction('Quit',self)
file.addAction(quit)
file.triggered[QAction].connect(self.processtrigger)
self.setLayout(layout)
self.setWindowTitle('MenuDemo')
def processtrigger(self,q):
print(q.text()+' is triggered')
if __name__=='__main__':
app=QApplication(sys.argv)
demo=MenuDemo()
demo.show()
sys.exit(app.exec_())
1QToolBar
QToolBar控件是由文本按钮,图标或其他小控件组成的可移动面板,通常位于菜单栏下发,常用方法:
addAction():添加具有文本或图标的工具按钮
addSeperator():分组显示按钮工具
addWidget():添加工具栏中按钮以外的控件
addToolBar():使用QMainWindow类方法添加一个新的工作区
setMoveable():使工具栏可移动
setOrientation():工具栏的方向可以设置为Qt.Horizontal或Qt.vertical
每当单击工具栏按钮时,都会发射actionTriggered信号,这个信号将关联到QAction对象的引用发送到连接的槽函数上
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class ToolBarDemo(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('ToolBarDemo')
self.resize(300,200)
layout=QVBoxLayout()
tb=self.addToolBar('File')
new=QAction(QIcon('C:\\Users\\PC\\Desktop\\快捷方式\\test.ico'),'test',self)
tb.addAction(new)
tb.actionTriggered[QAction].connect(self.toolbtnpressed)
self.setLayout(layout)
def toolbtnpressed(self,a):
print('pressed tool button is',a.text())
if __name__=='__main__':
app=QApplication(sys.argv)
demo=ToolBarDemo()
demo.show()
sys.exit(app.exec_())
17.QStatusBar
MainWindow对象在底部保留有一个水平条作为状态栏,用于显示永久的或临时的状态信息,通过setStatusBar()可以设置状态栏,常用方法:
addWidget()/addPermanentWidget()/showMessage()/clearMessage()/removeWidget()
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class StatusBarDemo(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
bar=self.menuBar()
file=bar.addMenu('File')
file.addAction('show')
file.triggered[QAction].connect(self.processTrigger)
self.setCentralWidget(QTextEdit())
self.statusBar=QStatusBar()
self.setWindowTitle('StatusBarDemo')
self.setStatusBar(self.statusBar)
def processTrigger(self,q):
if q.text()=='show':
self.statusBar.showMessage(q.text()+'菜单选项被点击了',5000)
if __name__=='__main__':
app=QApplication(sys.argv)
demo=StatusBarDemo()
demo.show()
sys.exit(app.exec_())
18.QPrinter
打印图像实际上实在QPaintDevice中画图,与创建一个QPainter画图类似,只是使用的是QPrinter
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
from PyQtQtPrintSupport import *
import sys
class painterDemo(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle(self.tr('打印图片'))
self.il=QLabel()
self.il.setSizePolicy(QSizePolicy.Ignored,QSizePolicy.Ignored)
self.setCentralWidget(self.il)
self.image=QImage()
self.createActions()
self.createMenus()
self.createToolBars()
if self.image.load('C:/Users/PC/Desktop/快捷方式/test.ico'):
self.il.setPixmap(QPixmap.fromImage(self.image))
self.resize(self.image.width(),self.image.height())
def createActions(self):
self.PrintAction=QAction(QIcon('C:/Users/PC/Desktop/快捷方式/test.ico'),self.tr('打印'),self)
self.PrintAction.setShortcut('Ctrl+P')
self.PrintAction.setStatusTip(self.tr('打印'))
self.PrintAction.triggered.connect(self.slotPrint)
def createMenus(self):
PrintMenu=self.menuBar().addMenu(self.tr('打印'))
PrintMenu.addAction(self.PrintAction)
def createToolBars(self):
fileToolBars=self.addToolBar('Print')
fileToolBars.addAction(self.PrintAction)
def slotPrint(self):
printer=QPrinter()
printDialog=QPrintDialog(printer,self)
if printDialog.exec_():
printer=QPrinter(painter)
rect=painter.viewport()
size=self.image.size()
size.scale(rect.size(),Qt.KeepAspectRatio)
painter.setViewport(rect.x(),rect.y(),size.width(),size.height())
printer.setWindow(self,image.rect)
painter.drawImage(0,0,self.image)
if __name__=='__main__':
app=QApplication(sys.argv)
demo=painterDemo()
demo.show()
sys.exit(app.exec_())
三、PyQt5高级界面控件
1.表格与树
表格与树解决的问题是如何在一个控件内有规律地呈现更多的数据,PyQt提供了表格与树两种控件类来解决该问题
1.1.QTableView
通常情况下,一个应用需要和一批数据进行交互,然后以表格的形式输出这些信息,这时候九需要用到QTableView类了,在QTableView中可以用自定义的数据模型来显示内容,通过setModel来绑定数据源
QTableWidget继承自QTableView,主要区别是QTableView可以用自定义数据模型来显示内容,QTableWidget只能使用标准的数据模型,并且单元格数据是通过QTableWidgetItem对象来实现的
QTableView控件可以绑定一个数据模型用于更新控件上的内容,可以的模式:
QStringListModel:存储一组字符串
QStandardItemModel:存储任意层次结构的数据
QDirModel:对文件系统进行封装
QSqlQueryModel:对SQL的查询结果进行封装
QSqlTableModel:对SQL中的表格进行封装
QSqlRelationTableModel:对带有foreign key 的SQL表格进行封装
QSortFilterProxyModel:对模型中的数据进行排序或过滤
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class Table(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('QTableView表格视图控件的例子')
self.resize(500,300)
self.model=QStandardItemModel(4,4)
self.model.setHorizontalHeaderLabels(['标1','标2','标3','标4'])
for row in range(4):
for column in range(4):
item=QStandardItem('row {},column {}'.format(row,column))
self.model.setItem(row,column,item)
self.tableView=QTableView()
self.tableView.setModel(self.model)
dlgLayout=QVBoxLayout()
dlgLayout.addWidget(self.tableView)
self.setLayout(dlgLayout)
#表格填满窗口
self.tableView.horizontalHeader().setStretchLastSection(True)
self.tableView.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
#添加数据
self.model.appendRow([
QStandardItem('row {},column {}'.format(11,11)),
QStandardItem('row {},column {}'.format(11,11)),
QStandardItem('row {},column {}'.format(11,11)),
QStandardItem('row {},column {}'.format(11,11))
])
#删除当前选中数据
index=self.tableView.currentIndex()
print(index.row())
self.model.removeRow(index.row())
if __name__=='__main__':
app=QApplication(sys.argv)
demo=Table()
demo.show()
sys.exit(app.exec_())
1.2.QListView
QListView类用于展示数据,它的子类是QListWidget。QListView是基于模型(Model)的,需要程序来建立模型,然后保存数据
QListWidget是一个升级版本的QListView,它已经建立了一个数据存储模型(QListWidgetItem),直接调用addItem()函数就可以添加条目
常用方法:
setModel():用来设置View所关联的Model,可以使用Python原生的 list 作为数据源Model
selectedItem():选中Model中的条目
isSelected():判断Model中的某条目是否被选中
常用信号:
clicked/doubleClicked
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class ListViewDemo(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('QListViewDemo')
self.resize(300,100)
layout=QVBoxLayout()
listview=QListView()
slm=QStringListModel()
self.qlist=['Item1','Item2','Item3','Item4']
slm.setStringList(self.qlist)
listview.setModel(slm)
listview.clicked.connect(self.clicked)
layout.addWidget(listview)
self.setLayout(layout)
def clicked(self,qModeIndex):
QMessageBox.information(self,'ListWidget','你选择了:'+self.qlist[qModeIndex.row()])
if __name__=='__main__':
app=QApplication(sys.argv)
demo=ListViewDemo()
demo.show()
sys.exit(app.exec_())
1.3.QListWidget
QListWidget类是一个基于条目的接口,用于从列表中添加或删除条目。列表中的每个条目都是一个QListWidgetItem对象,QListWidget可以设置为多重选择,常用方法:
addItem():在列表中添加QListWidgetItem对象或字符串
addItems():添加列表中的每个条目
insertItem():在指定的索引除插入条目
clear():删除列表的内容
setCurrentItem():设置当前所选择的条目
sortItems():按升序重新排列条目
常用信号:
currentItemChanged/itemClicked
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
if __name__=='__main__':
class ListWidget(QListWidget):
def clicked(self,item):
QMessageBox.information(self,'ListWidget','你选择了:'+item.text())
app=QApplication(sys.argv)
ListWidget=ListWidget()
ListWidget.resize(300,120)
ListWidget.addItem('Item 1')
ListWidget.addItem('Item 2')
ListWidget.addItem('Item 3')
ListWidget.addItem('Item 4')
ListWidget.setWindowTitle('QListWidgetDemo')
ListWidget.itemClicked.connect(ListWidget.clicked)
ListWidget.show()
sys.exit(app.exec_())
1.4.QTableWidget
QTableWidget是Qt程序中常用的显示数据表格的控件,QTableWidget是QTableView的子类,,它使用标准的数据模型,并且其单元格数据是通过QTableWidgetItem对象来实现的,使用QTableWidget时就需要QTableWidgetItm,用来表示表格中的一个单元格,整个表格就是用各单元格构建起来的
1.4.1.基本用法
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
from PyQt5 import QtCore
class Table(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('QTableWidgetDemo')
self.resize(400,300)
conLayout=QHBoxLayout()
tableWidget=QTableWidget()
tableWidget.itemClicked.connect(self.getitem)
#设置表格为不可编辑状态(默认为可编辑)
tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
#表格默认选中的是单个单元格,通过下面的代码可以设置为整行选中
tableWidget.setSelectionBehavior(QAbstractItemView.SelectRows)
tableWidget.setRowCount(5)
tableWidget.setColumnCount(3)
#将表格行和列的宽度设置为与所显示内容的宽度,高度相匹配
tableWidget.resizeColumnsToContents()
tableWidget.resizeRowsToContents()
#设置为可伸缩模式,即表格随着窗体大小而改变大小
tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
#设置固定单元格大小(第4行单元格高度为120)
tableWidget.setRowHeight(3,120)
#表格头的显示与隐藏
tableWidget.verticalHeader().setVisible(False)
conLayout.addWidget(tableWidget)
tableWidget.setHorizontalHeaderLabels(['姓名','性别','体重(kg)'])
#(0,0)单元格占据3行1列
#tableWidget.setSpan(0,0,3,1)
newItem=QTableWidgetItem('张三')
#设置单元格内容右对齐并与底部对齐
newItem.setTextAlignment(Qt.AlignRight|Qt.AlignBottom)
tableWidget.setItem(0,0,newItem)
newItem=QTableWidgetItem('160')
tableWidget.setItem(0,2,newItem)
newItem=QTableWidgetItem('170')
tableWidget.setItem(1,2,newItem)
newItem=QTableWidgetItem('180')
tableWidget.setItem(2,2,newItem)
newItem=QTableWidgetItem('李四')
#设置字体颜色和风格
newItem.setFont(QFont('Times',12,QFont.Black))
newItem.setForeground(QBrush(QColor(255,0,0)))
tableWidget.setItem(1,0,newItem)
newItem=QTableWidgetItem('王五')
newItem.setFont(QFont('Times',10,QFont.Black))
newItem.setForeground(QBrush(QColor(0,255,0)))
tableWidget.setItem(2,0,newItem)
#不显示分割线
tableWidget.setShowGrid(False)
#设置单元格的排序方式
#降序
tableWidget.sortItems(2,QtCore.Qt.DescendingOrder)
#升序
#tableWidget.sortItems(2,QtCore.Qt.AscendingOrder)
#在单元格中放置控件
comBox=QComboBox()
comBox.addItem('男')
comBox.addItem('女')
comBox.setStyleSheet('QComboBox[margin:3px];')
tableWidget.setCellWidget(0,1,comBox)
searchBtn=QPushButton('修改')
searchBtn.setDown(True)
searchBtn.setStyleSheet('QPushButton[margin:3px;]')
tableWidget.setCellWidget(3,2,searchBtn)
#为单元格添加图片
newItem=QTableWidgetItem(QIcon('C:/Users/PC/Desktop/快捷方式/test.ico'),'京剧人物')
tableWidget.setItem(3,0,newItem)
#改变单元格中显示的图片大小
item=QTableWidgetItem()
#用户点击表格时图片被选中
item.setFlags(Qt.ItemIsEnabled)
item.setIcon(QIcon(r'C:/Users/PC/Desktop/快捷方式/test.ico'))
tableWidget.setItem(3,1,item)
self.setLayout(conLayout)
#在表格中快速定位到指定行
#遍历表格查找对应的具体单元格
items=tableWidget.findItems('张三',QtCore.Qt.MatchExactly)
item=items[0]
#选中单元格
item.setSelected(True)
#获取其行号
row=item.row()
#模拟鼠标滚轮快速定位到指定行
tableWidget.verticalScrollBar().setSliderPosition(row)
def getitem(self,item):
print(item.text()+' is clicked')
if __name__=='__main__':
app=QApplication(sys.argv)
demo=Table()
demo.show()
sys.exit(app.exec_())
1.4.2.支持右键菜单
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
from PyQt5 import QtCore
class Table(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('QTableWidgetDemo')
self.resize(500,300)
conLayout=QHBoxLayout()
self.tableWidget=QTableWidget(5,3)
conLayout.addWidget(self.tableWidget)
self.tableWidget.setHorizontalHeaderLabels(['姓名','性别','体重'])
self.tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
newItem=QTableWidgetItem('张三')
self.tableWidget.setItem(0,0,newItem)
newItem=QTableWidgetItem('男')
self.tableWidget.setItem(0,1,newItem)
newItem=QTableWidgetItem('160')
self.tableWidget.setItem(0,2,newItem)
newItem=QTableWidgetItem('李四')
self.tableWidget.setItem(1,0,newItem)
newItem=QTableWidgetItem('女')
self.tableWidget.setItem(1,1,newItem)
newItem=QTableWidgetItem('170')
self.tableWidget.setItem(1,2,newItem)
#允许右键产生菜单
self.tableWidget.setContextMenuPolicy(Qt.CustomContextMenu)
#将右键菜单绑定到槽函数
self.tableWidget.customContextMenuRequested.connect(self.genMenu)
self.setLayout(conLayout)
def genMenu(self,pos):
row_num=-1
for i in self.tableWidget.selectionModel().selection().indexes():
row_num=i.row()
#表格中只有两条有效数据,索引只在前两行支持右键弹出菜单
if row_num<2:
menu=QMenu()
item1=menu.addAction(u'选项一')
item2=menu.addAction(u'选项二')
item3=menu.addAction(u'选项三')
action=menu.exec_(self.tableWidget.mapToGlobal(pos))
if action==item1:
print('选中了选项一,当前的文字内容是:',self.tableWidget.item(row_num,0).text(),self.tableWidget.item(row_num,1).text(),self.tableWidget.item(row_num,2).text())
elif action==item2:
print('选中了选项二,当前的文字内容是:',self.tableWidget.item(row_num,0).text(),self.tableWidget.item(row_num,1).text(),self.tableWidget.item(row_num,2).text())
elif action==item3:
print('选中了选项三,当前的文字内容是:',self.tableWidget.item(row_num,0).text(),self.tableWidget.item(row_num,1).text(),self.tableWidget.item(row_num,2).text())
else:
return
if __name__=='__main__':
app=QApplication(sys.argv)
demo=Table()
demo.show()
sys.exit(app.exec_())
1.QTreeView
QTreeView实现了树形结构,QTreeWidget类的常用方法:
setColumnWidth(int column,int width):将指定列的宽度设置为给定的值
insertTopLevelItems():在视图的顶层索引中插入项目列表
expandAll():展开所有的树形节点
selectedItems():返回所有选定的非隐藏项目的列表
QTreeWidgetItem类的常用方法:
addChild():将子项追加到子列表中
setText():设置显示的节点文本
Text():返回显示的节点文本
setCheckState(column,state):设置指定列的选中状态:Qt.Checked/Qt.Unchecked
setIcon(column,icon):在指定的列中显示图标
1.1.树形结构的实现
树形结构是通过QTreeWidget和QTreeWidgetItem类实现的,其中QTreeWidgetItem类实现了节点的添加
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
from PyQt5 import QtCore
class Tree(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('TreeDemo')
#self.resize(500,300)
self.tree=QTreeWidget()
#设置列数
self.tree.setColumnCount(2)
#设置树形控件头部标题
self.tree.setHeaderLabels(['Key','Value'])
#设置根节点
root=QTreeWidgetItem(self.tree)
root.setText(0,'root')
root.setIcon(0,QIcon('C:/Users/PC/Desktop/快捷方式/test.ico'))
#设置节点的背景颜色
brush_red=QBrush(Qt.red)
root.setBackground(0,brush_red)
brush_green=QBrush(Qt.green)
root.setBackground(1,brush_green)
#设置树形控件的列宽
self.tree.setColumnWidth(0,160)
#设置子节点1
child1=QTreeWidgetItem(root)
child1.setText(0,'child1')
child1.setText(1,'ios')
child1.setIcon(0,QIcon('C:/Users/PC/Desktop/快捷方式/test.ico'))
#设置节点为选中状态
child1.setCheckState(0,Qt.Checked)
#设置子节点2
child2=QTreeWidgetItem(root)
child2.setText(0,'child2')
child2.setText(1,'')
child2.setIcon(0,QIcon('C:/Users/PC/Desktop/快捷方式/test.ico'))
#设置子节点3
child3=QTreeWidgetItem(child2)
child3.setText(0,'child3')
child3.setText(1,'3')
self.tree.addTopLevelItem(root)
#添加响应事件
self.tree.clicked.connect(self.onTreeClicked)
#节点全部展开
self.tree.expandAll()
self.setCentralWidget(self.tree)
def onTreeClicked(self,qmodelindex):
item=self.tree.currentItem()
print('Key={},Value={}'.format(item.text(0),item.text(1)))
if __name__=='__main__':
app=QApplication(sys.argv)
demo=Tree()
demo.show()
sys.exit(app.exec_())
1.2.系统定制模式
QTreeWidgetItem类的节点一个个添加很不方便,特别是窗口中产生复杂的树形结构,一般是通过QTreeView类来实现的,而不是QTreeWidget类,QTreeView类与QTreeWidget类最大的区别就是QTreeView类可以使用操作系统提供的定制模式,比如文件系统盘的树列表
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
from PyQt5 import QtCore
if __name__=='__main__':
app=QApplication(sys.argv)
model=QDirModel()
#创建一个QTreeView控件
tree=QTreeView()
#为控件添加模式
tree.setModel(model)
tree.setWindowTitle('QTreeView例子')
tree.resize(640,480)
tree.show()
sys.exit(app.exec_())
2.容器
容器可以在现有的窗口空间内加载更多控件
2.1.QTabWidget
QTabWidget控件提供了一个选项卡和一个页面区域,默认显示第一个选项卡的页面,通过单击个选项卡可以查看对应的页面,常用方法:
addTab():将一个控件添加到Tab控件的选项卡中
insertTab():插入指定位置的控件
removeTab():根据索引删除Tab控件
setCurrentIndex():设置当前可见的选项卡所在的索引
setCurrentWidget():设置当前可见的页面
setTabBar():设置选项卡栏的小控件
setTabPosition():设置选项卡位置 QTabWidget.(North/South/West/East)
setTabText():定义选项卡的显示值
常用信号:currentChanged:切换当前页面时发射信号
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class TabDemo(QTabWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.tab1=QWidget()
self.tab2=QWidget()
self.tab3=QWidget()
self.addTab(self.tab1,'Tab1')
self.addTab(self.tab2,'Tab2')
self.addTab(self.tab3,'Tab2')
self.tab1UI()
self.tab2UI()
self.tab3UI()
self.setWindowTitle('Tab例子')
def tab1UI(self):
layout=QFormLayout()
layout.addRow('姓名',QLineEdit())
layout.addRow('地址',QLineEdit())
self.setTabText(0,'联系方式')
self.tab1.setLayout(layout)
def tab2UI(self):
layout=QFormLayout()
sex=QHBoxLayout()
sex.addWidget(QRadioButton('男'))
sex.addWidget(QRadioButton('女'))
layout.addRow(QLabel('性别'),sex)
layout.addRow('生日',QLineEdit())
self.setTabText(1,'个人详细信息')
self.tab2.setLayout(layout)
def tab3UI(self):
layout=QHBoxLayout()
layout.addWidget(QLabel('科目'))
layout.addWidget(QCheckBox('物理'))
layout.addWidget(QCheckBox('高数'))
self.setTabText(2,'教育程度')
self.tab3.setLayout(layout)
if __name__=='__main__':
app=QApplication(sys.argv)
demo=TabDemo()
demo.show()
sys.exit(app.exec_())
2.2.QStackedWidget
QStackedWidget是一个堆栈窗口控件,可以填充一些小控件,但同一时间只有一个控件可以显示,QStackedWidget使用QStackedLayout布局,QStackedWidget与QTabWidget类似,可以有效显示窗口中的控件
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class StackedDemo(QTabWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(300,50,10,10)
self.setWindowTitle('StackedWidget例子')
self.leftlist=QListWidget()
self.leftlist.insertItem(0,'联系方式')
self.leftlist.insertItem(1,'个人信息')
self.leftlist.insertItem(2,'教育程度')
self.stack1=QWidget()
self.stack2=QWidget()
self.stack3=QWidget()
self.stack1UI()
self.stack2UI()
self.stack3UI()
self.Stack=QStackedWidget(self)
self.Stack.addWidget(self.stack1)
self.Stack.addWidget(self.stack2)
self.Stack.addWidget(self.stack3)
hbox=QHBoxLayout(self)
hbox.addWidget(self.leftlist)
hbox.addWidget(self.Stack)
self.setLayout(hbox)
self.leftlist.currentRowChanged.connect(self.display)
def stack1UI(self):
layout=QFormLayout()
layout.addRow('姓名',QLineEdit())
layout.addRow('地址',QLineEdit())
self.stack1.setLayout(layout)
def stack2UI(self):
layout=QFormLayout()
sex=QHBoxLayout()
sex.addWidget(QRadioButton('男'))
sex.addWidget(QRadioButton('女'))
layout.addRow(QLabel('性别'),sex)
layout.addRow('生日',QLineEdit())
self.setTabText(1,'个人详细信息')
self.stack2.setLayout(layout)
def stack3UI(self):
layout=QHBoxLayout()
layout.addWidget(QLabel('科目'))
layout.addWidget(QCheckBox('物理'))
layout.addWidget(QCheckBox('高数'))
self.setTabText(2,'教育程度')
self.stack3.setLayout(layout)
def display(self,i):
self.Stack.setCurrentIndex(i)
if __name__=='__main__':
app=QApplication(sys.argv)
demo=StackedDemo()
demo.show()
sys.exit(app.exec_())
2.3.QDockWidget
QDockWidget是一个可以停靠在QMainWindow内的窗口控件,它可以保持在浮动状态或者在指定位置作为子窗口附加到主窗口中。QMainWindow类的主窗口对象保留有一个用于停靠窗口的区域,这个区域在控件的中央周围
QDockWidget控件在主窗口内可以移动到新的区域,QDockWidget类的常用方法:
setWidget():在Dock窗口区域设置QWidget
setFloating():设置Dock窗口石佛可以浮动
setAllowedAreas():设置可以停靠的区域 (Left/Right/Top/Bottom/No)DockWidgetArea
setFeatures():设置停靠窗口的功能属性:DockWidget(Closable/Movable/Floatable/VerticalTitleBar/Features/)/NoFeatures
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class QDockWidgetDemo(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(300,50,10,10)
self.setWindowTitle('QDockWidget例子')
layout=QHBoxLayout()
bar=self.menuBar()
file=bar.addMenu('File')
file.addAction('New')
file.addAction('Save')
file.addAction('Quit')
self.items=QDockWidget('Dockable',self)
self.listWidget=QListWidget()
self.listWidget.addItem('item1')
self.listWidget.addItem('item2')
self.listWidget.addItem('item3')
self.items.setWidget(self.listWidget)
self.items.setFloating(False)
self.setCentralWidget(QTextEdit())
self.addDockWidget(Qt.RightDockWidgetArea,self.items)
self.setLayout(layout)
if __name__=='__main__':
app=QApplication(sys.argv)
demo=QDockWidgetDemo()
demo.show()
sys.exit(app.exec_())
2.4.多文档界面
一个典型的GUI应用程序可能有多个窗口,选项卡控件和堆栈窗口控件允许依次使用其中的一个窗口,然而,很多时候这种方法不是很有用,因为其他窗口的视图是隐藏的。
一种同时显示多个窗口的办法是,创建多个独立的窗口,这些独立的窗口被称为SDL(单文档界面),每个窗口都可以由自己的菜单系统,工具栏等,这需要占用较多的内存资源
MDI(多文档界面)应用程序占用较少的内存资源,子窗口都可以放在主窗口的容器中,这个容器控件被称为QMdiArea
QMdiArea控件通常占据在QMainWindow对象的中央位置,子窗口在这个区域是QMdiSubWindow类的实例,可以设置任何QWidget作为子窗口对象的内部控件,子窗口在MDI区域内进行级联排列布局,QMdiArea和QMdiSubWindow的常用方法:
addSubWindow():将一个小控件添加在MDI区域作为一个新的子窗口
removeSubWindow():删除一个子窗口中的小控件
setActiveSubWindow():激活一个子窗口
casadcSubWindow():安排子窗口在MDI区域级联显示
titleSubWindows():安排子窗口在MDI区域内平铺显示
closeActiveSubWindow():关闭活动的子窗口
subWindowList():返回MDI区域的子窗口列表
setWidget():设置一个小控件作为QMdiSubWindow实例对象的内部控件
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class MainWindow(QMainWindow):
count=0
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.mdi=QMdiArea()
self.setCentralWidget(self.mdi)
bar=self.menuBar()
file=bar.addMenu('File')
file.addAction('New')
file.addAction('Cascade')
file.addAction('Tiled')
file.triggered[QAction].connect(self.windowwaction)
self.setWindowTitle('MDI Demo')
def windowwaction(self,q):
print('triggered')
if q.text()=='New':
MainWindow.count=MainWindow.count+1
sub=QMdiSubWindow()
sub.setWidget(QTextEdit())
sub.setWindowTitle('subwindow'+str(MainWindow.count))
self.mdi.addSubWindow(sub)
sub.show()
if q.text()=='Cascade':
self.mdi.cascadeSubWindows()
if q.text()=='Tiled':
self.mdi.tileSubWindows()
if __name__=='__main__':
app=QApplication(sys.argv)
demo=MainWindow()
demo.show()
sys.exit(app.exec_())
2.QScrollBar
QScrollBar控件提供了水平或垂直的滚动条,这样可以扩大当前窗口的有效装载面积,从而装载更多控件,常用信号:
valueChanged:当滑动条的值改变时发射
sliderMoved:当拖动滑块时发射
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class Example(QWidget):
count=0
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
hbox=QHBoxLayout()
self.l1=QLabel('拖动滑块改变颜色')
self.l1.setFont(QFont('Arial',16))
hbox.addWidget(self.l1)
self.s1=QScrollBar()
self.s1.setMaximum(255)
self.s1.sliderMoved.connect(self.sliderval)
self.s2=QScrollBar()
self.s2.setMaximum(255)
self.s2.sliderMoved.connect(self.sliderval)
self.s3=QScrollBar()
self.s3.setMaximum(255)
self.s3.sliderMoved.connect(self.sliderval)
hbox.addWidget(self.s1)
hbox.addWidget(self.s2)
hbox.addWidget(self.s3)
self.setGeometry(300,300,200,300)
self.setWindowTitle('QScrollBar例子')
self.setLayout(hbox)
def sliderval(self):
print(self.s1.value(),self.s2.value(),self.s3.value())
palette=QPalette()
c=QColor(self.s1.value(),self.s2.value(),self.s3.value(),255)
palette.setColor(QPalette.Foreground,c)
self.l1.setPalette(palette)
if __name__=='__main__':
app=QApplication(sys.argv)
demo=Example()
demo.show()
sys.exit(app.exec_())
3.多线程
一般情况应用程序都是单线程运行的,但是对于GUI程序来说,单线程可能满足不了需求,一般来说,多线程技术涉及三种方法,其中一种是使用计时器模块QTimer,一种是使用多线程模块QThread,还有一种是使用事件处理的功能
3.1.QTimer
如果要在应用程序中周期性地进行某项操作,如周期性地检测助记的CPU值,则需要用到QTimer(定时器),QTimer类提供了重复的和单次的计时器,要使用定时器,需要先创建一个QTimer实例,将timeout信号连接到相应的槽,并调用start(),然后定时器会以恒定的间隔(start(参数的时间))发出timeout()信号
当窗口控件收到timeout信号后,它就会停止这个定时器,这是在图形用户界面中实现复杂工作的一个典型方法,随着技术的进步,多线程在越来越多的平台上被使用,最终QTimer对象会被线程取代。QTimer类的常用方法:
start(milliseconds):启动或重新启动定时器,时间间隔为毫秒,如果定时器已经在运行,它将被停止被重新启动,如果singleShot信号为真,定时器将被仅激活一次
Stop():停止计时器
常用信号:
singleShot:在给定的时间间隔后调用一个槽函数时发射此信号
timeout:当定时器超时时发射此信号
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('QTimer例子')
self.listFile=QListWidget()
self.label=QLabel('显示当前时间')
self.sbn=QPushButton('开始')
self.ebn=QPushButton('结束')
layout=QGridLayout(self)
#初始化定时器
self.timer=QTimer(self)
self.timer.timeout.connect(self.showTime)
layout.addWidget(self.label,0,0,1,2)
layout.addWidget(self.sbn,1,0)
layout.addWidget(self.ebn,1,1)
self.sbn.clicked.connect(self.startTimer)
self.ebn.clicked.connect(self.endTimer)
self.setLayout(layout)
def showTime(self):
#获取当前系统时间
time=QDateTime.currentDateTime()
#设置时间显示格式
timeDisplay=time.toString('yyyy-MM-dd hh:mm:ss dddd')
#在标签上显示时间
self.label.setText(timeDisplay)
def startTimer(self):
self.timer.start(1000)
self.sbn.setEnabled(False)
self.ebn.setEnabled(True)
def endTimer(self):
self.timer.stop()
self.sbn.setEnabled(True)
self.ebn.setEnabled(False)
if __name__=='__main__':
app=QApplication(sys.argv)
demo=Example()
demo.show()
sys.exit(app.exec_())
3.2.QThread
要使用QThread开始一个线程,可以创建它的一个子类,然后覆盖其QThread.run()函数,在使用线程时可以直接得到Thread实例,调用其start()即可启动线程,线程启动之后会自动调用其run()函数。
业务的线程任务就写在run()函数中,当run()退出之后线程基本就结束了。QThread由started和finished信号,课堂为这两个信号指定槽函数,在线程启动和结束时执行一段代码进行资源的初始化和释放操作,更灵活的使用方法是,在自定义的QThread实例中自定义信号,并将信号连接到指定的槽函数,当满足一定的业务条件后发射此信号,QThread类的常用方法:
start():启动线程
wait():阻止线程,直到满足如下条件之一:
1.与QThread对象关联的线程已经完成(从run()返回),如果线程完成执行或尚未启动,此函数返回True
2.等待时间的单位是毫秒,如果时间是ULONG_MAX(默认值),则等待,永远不会超时(线程必须从run()中返回),若超时等待,返回False
sleep():强制当前线程睡眠数秒
常用信号:
started:在开始执行run()函数之前,从相关线程发射此信号
finished:线程完成业务逻辑时,从相关线程发送此信号
3.2.1.QThread实例
当在窗口中显示的数据比较简单时,可以把读取数据的业务逻辑放在窗口的初始化代码中,如果读取数据的时间较长,比如网络请求数据的时间比较长,则可以把这部分逻辑放在QThread线程中,实现界面的数据显示和数据读取的分离,以满足MVC(模型–视图–控制器)设计模式的要求
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class MainWidget(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('QThreed例子')
self.thread=Worker()
self.listFile=QListWidget()
self.sbn=QPushButton('开始')
layout=QGridLayout(self)
layout.addWidget(self.listFile,0,0,1,2)
layout.addWidget(self.sbn,1,1)
self.sbn.clicked.connect(self.slotStart)
self.thread.sinOut.connect(self.slotAdd) #第二步
def slotAdd(self,file_inf): #第三步
self.listFile.addItem(file_inf)
def slotStart(self):
self.sbn.setEnabled(False)
self.thread.start()
class Worker(QThread):
#自定义信号
sinOut=pyqtSignal(str)
def __init__(self):
super().__init__()
self.working=True
self.num=0
def __del__(self):
self.working=False
self.wait()
def run(self):
while self.working==True:
file_str='File Index{}'.format(self.num)
self.num=self.num+1
#发射信号
self.sinOut.emit(file_str) #第一步
#线程休眠两秒
self.sleep(2)
if __name__=='__main__':
app=QApplication(sys.argv)
demo=MainWidget()
demo.show()
sys.exit(app.exec_())
3.2.2.应用实例
上一个实例虽然解决了界面的数据显示和数据读取的分离,但是如果数据的读取非常耗费时间,则会造成界面卡死。下面是一个需要耗费很长时间读取数据的例子
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
global sec
sec=0
def setTime():
global sec
sec=sec+1
#LED显示数字加1
lcdNumber.display(sec)
def work():
#开始计时
timer.start(1000)
for i in range(2000000000):
pass
timer.stop()
if __name__=='__main__':
app=QApplication(sys.argv)
top=QWidget()
top.resize(300,120)
#垂直布局
layout=QVBoxLayout(top)
#添加一个显示面板
lcdNumber=QLCDNumber()
layout.addWidget(lcdNumber)
button=QPushButton('测试')
layout.addWidget(button)
timer=QTimer()
#每次计时结束触发setTime
timer.timeout.connect(setTime)
button.clicked.connect(work)
top.show()
sys.exit(app.exec_())
在PyQt中所有的窗口都在 UI 主线程中(就是执行了QApplication.exec()的线程),在这个线程中执行耗时很长的操作会阻塞 UI 线程,从而让窗口停止响应,为了避免出现这样的情况,要使用QThread开启一个新的线程,在这个线程中完成很耗时的操作
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
global sec
sec=0
class WorkThread(QThread):
trigger=pyqtSignal()
def __init__(self):
super().__init__()
def run(self):
print('这里是run函数')
for i in range(2000000000): #开始循环
pass
#循环完毕后发射信号
print('run函数循环结束,发射trigger信号')
self.trigger.emit() #
def countTime():
global sec
sec=sec+1
print('这里是countTime')
#LED显示数字加1
lcdNumber.display(sec) #显示数字加1
def work():
#计时器每秒计数
print('work函数收到按钮单击信号')
timer.start(1000) #开始计时,每隔1秒发射1次信号
#计时开始
WorkThread.start() #开始线程工作
#获得当前循环完毕的信号,停止计数
WorkThread.trigger.connect(timeStop)
def timeStop():
timer.stop()
print('运行结束用时',lcdNumber.value())
global sec
sec=0
if __name__=='__main__':
app=QApplication(sys.argv)
top=QWidget()
top.resize(300,120)
#垂直布局
layout=QVBoxLayout(top)
#添加一个显示面板
lcdNumber=QLCDNumber()
layout.addWidget(lcdNumber)
button=QPushButton('测试')
layout.addWidget(button)
timer=QTimer()
WorkThread=WorkThread()
#每次计时结束触发countTime
timer.timeout.connect(countTime) #触发计时
button.clicked.connect(work) #第一步触发工作
top.show()
sys.exit(app.exec_())
3.3.事件处理
PyQt为事件处理提供了两种机制:高级的信号与槽机制,以及低级的事件处理程序,低级的事件处理程序指的是processEvents()函数的使用方法,它的作用是处理事件,简单地说就是刷新界面
对于执行耗时很长的程序来说,由于PyQt需要等待程序执行完毕才能进行下一步,这个过程表现在界面上就是卡顿,而如果在执行这个耗时很长的程序时不断地运行QApplication.processEvents(),就可以实现一边执行耗时程序,一边刷新页面的功能,就会表现得更流畅,因此QApplication.processEvents()的使用方法就是,在主函数执行耗时很长的操作的地方,加入QApplication.processEvents()
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
import time
class WinForm(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('实时刷新页面例子')
self.listFile=QListWidget()
self.btnStart=QPushButton('开始')
layout=QGridLayout()
layout.addWidget(self.listFile,0,0,1,2)
layout.addWidget(self.btnStart,1,1)
self.btnStart.clicked.connect(self.slotAdd)
self.setLayout(layout)
def slotAdd(self):
for n in range(10):
str_n='File index {}'.format(n)
self.listFile.addItem(str_n)
QApplication.processEvents()
time.sleep(1)
if __name__=='__main__':
app=QApplication(sys.argv)
demo=WinForm()
demo.show()
sys.exit(app.exec_())
4.网页交互
PyQt使用QWebEngineView控件来展示HTML页面,WebEngine是基于谷歌的Chromium引擎开发的,也就是内部继承了谷歌的Chromium引擎。WebEngine框架是基于Chromium上的Content API 封装,投入成本较小可以很好地支持HTML5
QWebEngineView类中常用的方法:
load(QUrl url):加载指定的URL并展示
setHtml(QString &html):将网页视图的内容设置为指定的HTML内容
QWebEngineView控件使用load()函数加载一个Web页面,实际上就是使用HTTP GET方法加载Web页面,这个控件既可以加载本地的Web页面,也可以加载远程的外部Web页面,还可以使用setHtml()来加载本地的Web代码
4.1.加载并显示Web页面
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
from PyQtQtWebEngineWidgets import *
import sys
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('打开外部网页例子')
self.setGeometry(5,30,1355,730)
self.browser=QWebEngineView()
#加载外部的Web网页
self.browser.load(QUrl('http://www.baidu.com'))
self.setCentralWidget(self.browser)
if __name__=='__main__':
app=QApplication(sys.argv)
demo=MainWindow()
demo.show()
sys.exit(app.exec_())
4.2.PyQt调用 JavaScript 代码
通过QWebEnginePage类的 runJavaScript(str,Callable)函数 可以方便地实现PyQt和HTML/JavaScript的双向通信,也实现了Python代码和HTML/JavaScript代码的解耦,便于开发人员进行分工协作
四、PyQt5布局管理
在图形用户界面中,布局管理是一个重要的设计方面,布局管理是通过布局将各种不同功能的控件(Widget)放到程序主窗口中,常用的软件就是采用布局管理的方式来进行界面设计的
1.PyQt5中的布局管理方式
对PyQt5的界面进行布局管理主要有两种方法,即采用绝对位置和布局类,在PyQt5中有四种布局方式:水平(QHBox)/垂直(QVBox)/网格(QGrid)/表单布局(QForm)以及两种布局方法,addLayout() 和 addWidget() ,其中 addLayout() 用于在布局中插入子布局,addWidget()用于在布局中插入控件
2.PyQt5的绝对位置布局
绝对位置布局主要是通过在窗口程序中指定每一个控件的显示坐标和大小来实现的。
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
from PyQtQtWebEngineWidgets import *
import sys
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
la1=QLabel('欢迎',self)
la1.move(15,10)
la2=QLabel('学习',self)
la2.move(35,40)
la3=QLabel('PyQt5',self)
la3.move(55,70)
self.setGeometry(300,300,320,120)
self.setWindowTitle('绝对位置布局例子')
if __name__=='__main__':
app=QApplication(sys.argv)
demo=Example()
demo.show()
sys.exit(app.exec_())
绝对布局:
改变一个窗口的大小,窗口中的控件大小不会随之改变
生成的窗口在不同的操作系统下看起来可能不一样
在程序中改变字体可能会破坏布局
如果修改布局,比如新增一个控件就必须全部重新布局
3.QBoxLayout(框布局)
3.1.QHBoxLayout(水平布局)
采用QHBoxLayout类,按照从左到右的顺序来添加控件,常用方法:
addLayout(self,QLayout,stretch=0):在窗口的右边添加布局,使用stretch(伸缩量)进行伸缩,默认为0
addWidget(self,QWidget,stretch,Qt.Alignment alignment):在布局中添加控件:stretch(伸缩量)只适用于QBoxLayout,控件和窗口会随着伸缩量的变大而增大 alignment:Qt.Align(Left/Right/Center/Jsutify/Top/Bottom/VCenter)
addSpacing(self,int):设置各控件的上下间距,通过该方法可以增加额外的空间
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
from PyQtQtWebEngineWidgets import *
import sys
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('水平布局管理')
#水平布局按照从左到右的顺序添加按钮控件
hlayout=QHBoxLayout()
#水平居左,垂直靠上对齐
hlayout.addWidget(QPushButton('1'),0,Qt.AlignLeft|Qt.AlignTop)
hlayout.addWidget(QPushButton('2'),0,Qt.AlignLeft|Qt.AlignTop)
hlayout.addWidget(QPushButton('3'))
#水平居左,垂直考下对齐
hlayout.addWidget(QPushButton('4'),0,Qt.AlignLeft|Qt.AlignBottom)
hlayout.addWidget(QPushButton('5'),0,Qt.AlignLeft|Qt.AlignBottom)
#设置控件间距
hlayout.setSpacing(5)
self.setLayout(hlayout)
if __name__=='__main__':
app=QApplication(sys.argv)
demo=Example()
demo.show()
sys.exit(app.exec_())
3.2.QVBoxLayout(垂直布局)
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
from PyQtQtWebEngineWidgets import *
import sys
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('垂直布局管理')
#水平布局按照从左到右的顺序添加按钮控件
vlayout=QVBoxLayout()
#水平居左,垂直靠上对齐
vlayout.addWidget(QPushButton('1'))
vlayout.addWidget(QPushButton('2'))
vlayout.addWidget(QPushButton('3'))
#水平居左,垂直考下对齐
vlayout.addWidget(QPushButton('4'))
vlayout.addWidget(QPushButton('5'))
#设置控件间距
vlayout.setSpacing(5)
self.setLayout(vlayout)
if __name__=='__main__':
app=QApplication(sys.argv)
demo=Example()
demo.show()
sys.exit(app.exec_())
3.3.addStretch()函数的使用
在布局时要用到addStretch()函数。设置stretch伸缩量后,按比例分配剩余空间
QBoxLayout.addStretch(int stretch=0):addStretch()函数在布局管理器中增加可伸缩的空间(QSpaceItem),0为最小值,并且将stretch作为伸缩量添加到布局末尾,stretch参数表示均分的比例
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
from PyQtQtWebEngineWidgets import *
import sys
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
btn1=QPushButton(self)
btn2=QPushButton(self)
btn3=QPushButton(self)
btn1.setText('1')
btn2.setText('2')
btn3.setText('3')
hbox=QHBoxLayout()
#设置伸缩量为1
hbox.addStretch(1)
hbox.addWidget(btn1)
hbox.addStretch(2)
hbox.addWidget(btn2)
hbox.addStretch(4)
hbox.addWidget(btn3)
hbox.addStretch(1)
self.setLayout(hbox)
self.setWindowTitle('addStretch例子')
if __name__=='__main__':
app=QApplication(sys.argv)
demo=Example()
demo.show()
sys.exit(app.exec_())
addStretch()函数用于在按钮间设置伸缩量,伸缩量比例为1:2:4:1,如果在第一个空间前添加伸缩控件,所有的控件就会居右显示
4.QGridLayout(网格布局)
QGridLayout是将窗口分隔成行和列的网格来进行排列,常用方法:
addWidget(QWidget widget,int row,int col,int alignment),row和column是控件的行数和列数,默认从 0 开始
addWidget(QWidget widget,int fromRow,int fromColumn, int rowSpan,int columnSpan,Qt.Alignment.alignment=0)
fromRow/fromColumn是控件的起始行数和列数,rowSpan/columnSpan设置控件跨越的行数和列数
setSpacing(int spacing):设置控件在水平和垂直方向的间隔
4.1.单一的网格单元格
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
from PyQtQtWebEngineWidgets import *
import sys
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
grid=QGridLayout()
self.setLayout(grid)
names=['Cls','Back','','Close','7','8','9','/','4','5','6','*','1','2','3','-','0','.','=','+']
positions=[(i,j) for i in range(5) for j in range(4)]
for position,name in zip(positions,names):
if name=='':
continue
button=QPushButton(name)
grid.addWidget(button,*position)
self.move(300,150)
self.setWindowTitle('网格布局管理例子')
if __name__=='__main__':
app=QApplication(sys.argv)
demo=Example()
demo.show()
sys.exit(app.exec_())
4.2.跨越行和列的网格单元格
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
from PyQtQtWebEngineWidgets import *
import sys
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
grid=QGridLayout()
self.setLayout(grid)
grid.setSpacing(10)
title=QLabel('标题')
author=QLabel('提交人')
review=QLabel('申告内容')
titleEdit=QLineEdit()
authorEdit=QLineEdit()
reviewEdit=QTextEdit()
grid.addWidget(title,1,0)
grid.addWidget(titleEdit,1,1)
grid.addWidget(author,2,0)
grid.addWidget(authorEdit,2,1)
grid.addWidget(review,3,0)
grid.addWidget(reviewEdit,3,1,5,1)
self.setGeometry(300,300,350,300)
self.setWindowTitle('故障申报')
if __name__=='__main__':
app=QApplication(sys.argv)
demo=Example()
demo.show()
sys.exit(app.exec_())
5.QFormLayout(表单布局)
QFormLayout是label-field式的表单布局,表单是提示用户进行交互的一种模式,主要由显示信息列(label域)和用户选择和输入列(field域),label关联field
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
from PyQtQtWebEngineWidgets import *
import sys
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('表单布局例子')
self.resize(400,100)
formlayout=QFormLayout()
label1=QLabel('标签1')
lineEdit1=QLineEdit()
label2=QLabel('标签2')
lineEdit2=QLineEdit()
label3=QLabel('标签3')
lineEdit3=QLineEdit()
formlayout.addRow(label1,lineEdit1)
formlayout.addRow(label2,lineEdit2)
formlayout.addRow(label3,lineEdit3)
self.setLayout(formlayout)
if __name__=='__main__':
app=QApplication(sys.argv)
demo=Example()
demo.show()
sys.exit(app.exec_())
6.嵌套布局
6.1.在布局中添加其他布局
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
from PyQtQtWebEngineWidgets import *
import sys
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('嵌套布局例子')
self.resize(400,100)
#全局布局(2种):水平
wlayout=QHBoxLayout()
#局部布局(4种):水平,垂直,网格,表单
hlayout=QHBoxLayout()
vlayout=QVBoxLayout()
glayout=QGridLayout()
formlayout=QFormLayout()
#为局部布局添加控件
hlayout.addWidget(QPushButton('1'))
hlayout.addWidget(QPushButton('2'))
vlayout.addWidget(QPushButton('3'))
vlayout.addWidget(QPushButton('4'))
glayout.addWidget(QPushButton('5'),0,0)
glayout.addWidget(QPushButton('6'),0,1)
glayout.addWidget(QPushButton('7'),1,0)
glayout.addWidget(QPushButton('8'),1,1)
formlayout.addWidget(QPushButton('9'))
formlayout.addWidget(QPushButton('10'))
formlayout.addWidget(QPushButton('11'))
formlayout.addWidget(QPushButton('12'))
#准备四个控件
hwg=QWidget()
vwg=QWidget()
gwg=QWidget()
fwg=QWidget()
#使用四个控件设置布局局部布局
hwg.setLayout(hlayout)
vwg.setLayout(vlayout)
gwg.setLayout(glayout)
fwg.setLayout(formlayout)
#将四个控件添加到全局布局中
wlayout.addWidget(hwg)
wlayout.addWidget(vwg)
wlayout.addWidget(gwg)
wlayout.addWidget(fwg)
#将窗口本身设置为全局布局
self.setLayout(wlayout)
if __name__=='__main__':
app=QApplication(sys.argv)
demo=Example()
demo.show()
sys.exit(app.exec_())
6.2.在控件中添加布局
在布局中添加其他布局需要很多空白控件,比较麻烦,由一个空白控件,然后在这个空白控件中进行多种布局,就可以实现相同的效果
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
from PyQtQtWebEngineWidgets import *
import sys
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('嵌套布局实例')
self.resize(700,200)
#全局控件,注意参数(self),用于"承载"全局布局
wwg=QWidget(self)
#全局布局(注意参数wwg)
wlayout=QHBoxLayout()
hlayout=QHBoxLayout()
vlayout=QVBoxLayout()
glayout=QGridLayout()
formlayout=QFormLayout()
#为局部布局添加控件
hlayout.addWidget(QPushButton('1'))
hlayout.addWidget(QPushButton('2'))
vlayout.addWidget(QPushButton('3'))
vlayout.addWidget(QPushButton('4'))
glayout.addWidget(QPushButton('5'),0,0)
glayout.addWidget(QPushButton('6'),0,1)
glayout.addWidget(QPushButton('7'),1,0)
glayout.addWidget(QPushButton('8'),1,1)
formlayout.addWidget(QPushButton('9'))
formlayout.addWidget(QPushButton('10'))
formlayout.addWidget(QPushButton('11'))
formlayout.addWidget(QPushButton('12'))
#将局部布局添加到全局布局中
wlayout.addLayout(hlayout)
wlayout.addLayout(vlayout)
wlayout.addLayout(glayout)
wlayout.addLayout(formlayout)
self.setLayout(wlayout)
if __name__=='__main__':
app=QApplication(sys.argv)
demo=Example()
demo.show()
sys.exit(app.exec_())
7.QSplitter
除了Layout布局管理外,PyQt还提供了一个特殊的布局管理器QSplitter,它可以动态地拖动子空间直接的边界,算是一个动态的布局管理器。QSplitter允许用户拖动子控件的边界来控制子控件的大小,并提供了一个除了拖曳子控件的控制器,常用方法:
addWidget():添加子控件
indexOf():返回子控件的索引
insertWidget():根据索引插入控件
setOrientation():设置布局方法,默认为水平 Qt.(HorizontalQt.Vertical)
setSizes():设置控件的初始化大小
count():返回子控件的数量
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
from PyQtQtWebEngineWidgets import *
import sys
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('QSplitter')
self.setGeometry(300,300,300,200)
topleft=QFrame()
topleft.setFrameShape(QFrame.StyledPanel)
bottom=QFrame()
bottom.setFrameShape(QFrame.StyledPanel)
splitter1=QSplitter(Qt.Horizontal)
textedit=QTextEdit()
splitter1.addWidget(topleft)
splitter1.addWidget(textedit)
splitter1.setSizes([100,200])
splitter2=QSplitter(Qt.Vertical)
splitter2.addWidget(splitter1)
splitter2.addWidget(bottom)
hbox=QHBoxLayout(self)
hbox.addWidget(splitter2)
self.setLayout(hbox)
if __name__=='__main__':
app=QApplication(sys.argv)
demo=Example()
demo.show()
sys.exit(app.exec_())
五、信号与槽
信号(Signal)和槽(Slot)是Qt中的核心机制,也是在PyQt编程中对象之间进行通信的机制。在Qt中,每一个QObject对象和PyQt中所有继承自QWidget的控件都支持信号与槽机制,通过object.signal.connect()连接,PyQt的窗口控件有许多内置信号,也可以自定义信号:
- 一个信号可以连接多个槽
- 一个信号可以连接另一个信号
- 信号参数可以是任何Python类型
- 一个槽可以监听多个信号
- 信号与槽的连接方式可以是同步连接,也可以是异步连接
- 信号与槽的连接可能会跨线程
- 信号可能会断开
在GUI编程中,当改变一个控件的状态时(如单击了按钮),通常需要通知另一个控件,也就是实现了对象之间的通信
1.定义信号
PyQt的内置信号是自动定义的,使用PyQt.QtCore.pyqtSignal()函数可以为QObject创建一个信号,使用pyqtSignal()函数可以把信号定义为类的属性 pyqtSignal(types,name,revision,arguments)
为QObject对象创建信号时,信号必须在类创建时定义,不能再类创建后作为类的属性动态添加进来,types参数表示定义信号时的参数类型,name表示信号名字,未指定name时,使用类的属性的名字
使用pyqtSignal()函数创建信号时,信号可以传递多个参数,并指定类型,如字符串,日期,布尔类型,数字,列表,元组和字典
2.操作信号
使用connect()函数可以把信号绑定到槽函数上
使用disconnect()函数可以接触信号与槽函数的绑定
使用emit()函数可以发射信号
3.基础应用
信号与槽有三种使用方法:第一种是内置信号与槽的使用,第二种是自定义信号与槽的使用,第三种是装饰器的信号与槽的使用,第三种方法本质是第一种方法的衍生
3.1.内置信号与槽的使用
内置信号与槽的使用,是指再发射信号时,使用窗口控件的函数,而不是自定义的函数
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
from PyQtQtWebEngineWidgets import *
import sys
app=QApplication(sys.argv)
widget=QWidget()
def showMsg():
QMessageBox.information(widget,'消息提示框','ok,弹出测试消息')
btn=QPushButton('测试点击按钮',widget)
btn.clicked.connect(showMsg)
widget.show()
sys.exit(app.exec_())
3.2.自定义信号与槽的使用
使用pyqtSignal()类实例发射信号可以实现自定义信号与槽的使用,PyQt5编程中,信号与槽有多种写法,下面是Python风格的写法
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
from PyQtQtWebEngineWidgets import *
import sys
class QTypeSignal(QObject):
#定义一个信号
sendmsg=pyqtSignal(object)
def __init__(self):
super().__init__()
def run(self):
#发射信号
self.sendmsg.emit('Hello PyQt5')
#槽对象
class QTypeSlot(QObject):
def __init__(self):
super().__init__()
#槽对象中的槽函数
def get(self,msg):
print('QSlot get msg=>'+msg)
if __name__=='__main__':
send=QTypeSignal()
slot=QTypeSlot()
#1
print('-----把信号绑定到槽函数上-----')
send.sendmsg.connect(slot.get)
send.run()
#2
print('-----把信号与槽函数的连接断开-----')
send.sendmsg.disconnect(slot.get)
send.run()
'''
-------把信号绑定到槽函数上-----
QSlot get msg=>Hello PyQt5
-----把信号与槽函数的连接断开-----
'''
信号与槽的连接主要步骤如下:
生成信号 ---------> 将信号与槽函数绑定----------->槽函数接受数据-------------->发送信号的实现------------>把信号绑定到槽对象的槽函数
4.信号与槽的细分
4.1.内置信号和槽函数
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('内置信号与槽函数')
self.resize(330,50)
btn=QPushButton('关闭',self)
btn.clicked.connect(self.close)
if __name__=='__main__':
app=QApplication(sys.argv)
demo=Example()
demo.show()
sys.exit(app.exec_())
4.2.内置信号和自定义槽函数
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('内置信号与自定义槽函数')
self.resize(330,50)
btn=QPushButton('关闭',self)
btn.clicked.connect(self.btn_close)
def btn_close(self):
self.close()
if __name__=='__main__':
app=QApplication(sys.argv)
demo=Example()
demo.show()
sys.exit(app.exec_())
4.3.自定义信号和内置槽函数
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class Example(QWidget):
#自定义信号,不带参数
button_clicked_signal=pyqtSignal()
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('内置信号与自定义槽函数')
self.resize(330,50)
btn=QPushButton('关闭',self)
#连接信号与槽函数
btn.clicked.connect(self.button_clicked)
#接收信号,连接到槽函数
self.button_clicked_signal.connect(self.close)
def button_clicked(self):
#发送自定义信号,无参数
self.button_clicked_signal.emit()
if __name__=='__main__':
app=QApplication(sys.argv)
demo=Example()
demo.show()
sys.exit(app.exec_())
4.4.自定义信号和槽函数
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class Example(QWidget):
#自定义信号,不带参数
button_clicked_signal=pyqtSignal()
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('内置信号与自定义槽函数')
self.resize(330,50)
btn=QPushButton('关闭',self)
#连接信号与槽函数
btn.clicked.connect(self.button_clicked)
#接收信号,连接到槽函数
self.button_clicked_signal.connect(self.btn_close)
def button_clicked(self):
#发送自定义信号,无参数
self.button_clicked_signal.emit()
def btn_close(self):
#发送自定义信号,无参数
self.close()
if __name__=='__main__':
app=QApplication(sys.argv)
demo=Example()
demo.show()
sys.exit(app.exec_())
5.信号与槽的高级应用
所谓高级自定义信号与槽,指的是我们可以以自己喜欢的方式定义信号与槽函数,并传递参数。自定义信号的一般流程:
定义信号-------->定义槽函数------->连接信号与槽函数---------->发射信号
5.1.定义信号
class MyWidget(QWdiegt):
#无参数的信号
Signal_NoParameters=pyqtSignal()
#带一个参数(整数)的信号
Signal_OneParameter=pyqtSignal(int)
#带一个参数(整数或字符串)的重载版本的信号
Signal_OneParameter_Overload=pyqtSignal([int],[str])
#带两个参数(整数,字符串)的信号
Signal_TwoParameters=pyqtSignal(int,str)
#带两个参数([整数,整数]或者[整数,字符串])的重载版本的信号
Signal_TwoParameters_Overload=pyqtSignal([int,int],[int,str])
5.2.定义槽函数
class MyWidget(QWdiegt):
def setValue_NoParameters(self):
#无参数的槽函数
pass
def setValue_OneParameter(self,nIndex):
#带一个参数(整数)的槽函数
pass
def setValue_OneParameter_String(self,szIndex):
#带一个参数(字符串)的槽函数
pass
def setValue_TwoParameters(self,x,y):
#带两个参数(整数,整数)的槽函数
pass
def setValue_TwoParameters_String(self,x,szY):
#带两个参数(整数,字符串)的槽函数
pass
5.3.连接信号与槽函数
app=QApplication(sys.argv)
widget=MyWidget()
#连接无参数的信号
widget.Signal_NoParameters.connect(self.setValue_NoParameters)
#连接带一个整数参数的信号
widget.OneParameter.connect(self.setValue_OneParameter)
#连接带一个整数参数,经过重载的信号
widget.Signal_OneParameter_Overload[int].connect(self.setValue_OneParameter)
#连接带一个字符串参数,经过重载的信号
widget.Signal_OneParameter_Overload[str].connect(self.setValue_OneParameter_String)
#连接带两个参数(整数,整数)的重载版本的信号
widget.Signal_TwoParameters.Overload[int,int]connect(self.setValue_TwoParameters)
#连接带两个参数(整数,字符串)的重载版本的信号
widget.Signal_TwoParameters.Overload[int,str]connect(self.setValue_TwoParameters_String)
widget.show()
5.4.发射信号
class MyWidget(QWidget):
def mousePressEvent(self,event):
#发射无参数的信号
self.Signal_NoParameters.emit()
#发射带一个参数(整数)的信号
self.setValue_OneParameter.emit(1)
#发射带一个参数(整数)的重载版本的信号
self.Signal_OneParameter_Overload.emit(1)
#发射带一个参数(字符串)的重载版本的信号
self.Signal_OneParameter_Overload.emit('abc')
#发射带两个参数(整数,字符串)的信号
self.Signal_TwoParameters.emit(1,'abc')
#发射带两个参数(整数,整数)的信号
self.Signal_TwoParameters.emit(1,2)
#发射带两个参数(整数,字符串)的重载版本的信号
self.Signal_TwoParameter_Overload.emit(1,'abc')
5.5.实例
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class CustSingal(QObject):
s1=pyqtSignal()
s2=pyqtSignal(int)
s3=pyqtSignal(int,str)
s4=pyqtSignal(list)
s5=pyqtSignal(dict)
s6=pyqtSignal([int,str],[str])
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.s1.connect(self.s1call)
self.s2.connect(self.s2call)
self.s3.connect(self.s3call)
self.s4.connect(self.s4call)
self.sconnect(self.s5call)
self.s6[int,str].connect(self.s6call1)
self.s6[str].connect(self.s6call2)
self.s1.emit()
self.s2.emit(1)
self.s3.emit(1,'text')
self.s4.emit([1,2,3])
self.semit({'name':'ys','age':'20'})
self.s6[int,str].emit(1,'text')
self.s6[str].emit('text')
def s1call(self):
print('s1 emit')
def s2call(self,v):
print('s2 emit,value:',v)
def s3call(self,v,t):
print('s3 emit,value:',v,t)
def s4call(self,l):
print('s1 emit,value:',l)
def s5call(self,d):
print('s1 emit,value:',d)
def s6call1(self,v,t):
print('s1 emit,value:',v,t)
def s6call2(self,v):
print('s1 emit,value:',v)
if __name__=='__main__':
app=QApplication(sys.argv)
demo=CustSingal()
sys.exit(app.exec_())
'''
s1 emit
s2 emit,value: 1
s3 emit,value: 1 text
s1 emit,value: [1, 2, 3]
s1 emit,value: {'name': 'ys', 'age': '20'}
s1 emit,value: 1 text
s1 emit,value: text
'''
5.使用自定义参数
再PyQt编程时,经常遇到给定槽函数传递自定义参数的情况,如clicked信号没有参数,但可能槽函数希望接收信号,这时可以使用lambda表达式
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
import sys
class Example(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
b1=QPushButton('Button1')
b2=QPushButton('Button2')
b1.clicked.connect(lambda:self.onButtonClick(1))
b2.clicked.connect(lambda:self.onButtonClick(2))
layout=QHBoxLayout()
layout.addWidget(b1)
layout.addWidget(b2)
main_frame=QWidget()
main_frame.setLayout(layout)
self.setCentralWidget(main_frame)
def onButtonClick(self,v):
print('Button{}被按下'.format(v))
QMessageBox.information(self,'消息提示','Button{}'.format(v))
if __name__=='__main__':
app=QApplication(sys.argv)
demo=Example()
demo.show()
sys.exit(app.exec_())
另一种方法可以使用functools中的partial函数
b1.clicked.connect(partial(self.onButtonClick,1))
b2.clicked.connect(partial(self.onButtonClick,2))
5.7.装饰器信号与槽
所谓装饰器信号与槽,就是通过装饰器的方法来定义信号和槽函数,具体的使用方法如下:
@PyQt.QtCore.pyqtSlot(参数)
def on_发送者对象名称_发射信号名称(self,参数)
pass
这种方法有效的前提是下面的函数以及执行
QMetaObject.connectSlotsByName(QObject)
在上面的代码中,'发送者对象名称’就是使用setObjectName函数设置的名称,因此自定义槽函数的名词命名规则也可以看成:on+使用setObjectName设置的名称+信号名称
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
from PyQt5 import QtCore
import sys
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.okButton=QPushButton('OK',self)
#使用setObjectName设置对象名称
self.okButton.setObjectName('okButton')
l=QHBoxLayout()
self.setLayout(l)
#根据信号名称连接到槽函数,相当于把下面的self换成了okButton_clicked,函数on_okButton_clicked变成了okButton_clicked
QtCore.QMetaObject.connectSlotsByName(self)
@QtCore.pyqtSlot()
def on_okButton_clicked(self):
print('单击了ok按钮')
if __name__=='__main__':
app=QApplication(sys.argv)
demo=Example()
demo.show()
sys.exit(app.exec_())
5.8.信号与槽的断开和连接
有时基于某些原因,想要临时断开或永久断开信号与槽的连接,下面就是一个案例
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
from PyQt5 import QtCore
import sys
class SignalClass(QObject):
s1=pyqtSignal()
s2=pyqtSignal(int)
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.s1.connect(self.s1call)
self.s1.connect(self.s2call)
self.s2.connect(self.s1)
self.s1.emit()
#此时s1call和s2call都会产生反应,s2call缺少参数v而警告
self.s2.emit(1)
self.s1.disconnect(self,s1call)
self.s1.disconnect(self,s2call)
self.s2.disconnect(self,s1)
self.s1.connect(self.s1call)
self.s2.connect(self.s1call)
self.s1.emit()
self.s2.emit(1)
def s1call(self):
print('s1 emit')
def s2call(self,v):
print('s2 emit,v=',v)
if __name__=='__main__':
s=SignalClass()
Qt Designer的神助攻
在Qt Designer中设置布局,修改对象名之后转换为.py文件
另开文件实现具体逻辑
import sys
from PyQtQtWidgets import QApplication,QMainWindow
from PyQtQtCore import pyqtSignal,Qt
from MainPrintUI import Ui_Form
class MyMainWindow(QMainWindow,Ui_Form):
helpSignal=pyqtSignal(str)
printSignal=pyqtSignal(list)
#声明一个多重重载版本的信号,包含一个带int和str类型参数的信号,以及带str类型参数的信号
previewSignal=pyqtSignal([int,str],[str])
def __init__(self):
super().__init__()
self.setupUi(self)
self.initUI()
def initUI(self):
self.helpSignal.connect(self.showHelpMessage)
self.printSignal.connect(self.printPaper)
self.previewSignal[str].connect(self.previewPaper)
self.previewSignal[int,str].connect(self.previewPaperWithArgs)
self.printButton.clicked.connect(self.emitPrintSignal)
self.previewButton.clicked.connect(self.emitPreviewSignal)
#发射预览信号
def emitPreviewSignal(self):
if self.previewStatus.isChecked()==True:
self.previewSignal[int,str].emit(1080,'Full Screen')
elif self.previewStatus.isChecked()==False:
self.previewSignal[str].emit('Preview')
#发射打印信号
def emitPrintSignal(self):
plist=[]
plist.append(self.numberSpinBox.value())
plist.append(self.styleCombo.currentText())
self.printSignal.emit(plist)
def printPaper(self,list):
self.resultLabel.setText('打印份数:'+str(list[0])+',纸张:'+str(list[1]))
def previewPaperWithArgs(self,style,text):
self.resultLabel.setText(str(style)+text)
def previewPaper(self,text):
self.resultLabel.setText(text)
#重载按键事件
def keyPressEvent(self,event):
if event.key()==Qt.Key_F1:
self.helpSignal.emit('help message')
#显示帮助信息
def showHelpMessage(self,message):
self.resultLabel.setText(message)
self.statusBar().showMessage(message)
if __name__=='__main__':
app=QApplication(sys.argv)
main = MyMainWindow()
main.show()
sys.exit(app.exec_())
7.事件处理机制
PyQt为事件处理提供了两种机制,高级的信号与槽机制,以及低级的事件处理机制。信号与槽只能解决窗口空间按的某些特定行为,如果要对窗口控件做更深层次的研究,如自定义窗口,则需要用更低级的事件处理机制
事件处理机制本身很复杂,是PyQt底层的东西,一般可以把它作为信号与槽机制的补充
7.1.常见的事件类型
PyQt是对Qt的封装,Qt程序是事件驱动的,它的每个动作都由幕后的每个事件所触发,Qt的常见事件如下:
- 键盘事件:按键按下和松开
- 鼠标事件:鼠标指针移动,鼠标按键按下和松开
- 拖放事件:用鼠标进行拖放
- 绘屏事件:重绘屏幕的某些部分
- 定时事件:定时器到时
- 焦点事件:键盘焦点移动
- 进入和离开事件:鼠标指针移入Widget内或者移除
- 移动事件:Widget的位置改变
- 大小改变事件:Widget的大小改变
- 显示和隐藏事件:Widget显示和隐藏
- 窗口事件:窗口是否为当前窗口
- Socket事件/剪贴板事件/字体改变事件/布局改变事件
7.2.使用事件处理的方法
PyQt提供了如下5种事件处理和过滤的方法(由弱到强):其中只有前两种犯法使用较频繁:
1.重新实现事件函数
比如mousePressEvent(),keyPressEvent(),paintEvent(),这是最常规的事件处理方法
2.重新事件QObject.event()
一般用在PyQt没有提供该事件的处理函数的情况下,即增加新事件
3.安装事件过滤器
如果对Object调用installEventFilter,则相当于为这个QObject安装了一个事件过滤器,对于QObject的全部事件来说,它们都会先传递到事件过滤函数eventFilter中,在这个函数中,我们可以抛弃或修改这些事件,比如可以对自己感兴趣的事件使用自定义的事件处理机制,对其他事件使用默认的事件处理机制,但是过滤事件较多会降低程序的性能
4.在QApplication中安装事件过滤器
QApplication的事件过滤器将捕获所有QObject的所有事件进行处理,而且是第一个获得该事件
重新实现QApplication的notify()方法
PyQt使用notify()来分发事件,要想在任何事件处理器之气捕获事件,唯一的方法就是重新实现QApplication的notify()方法。在实践中,只有在调试时才会使用这种方法
7.3.经典案例
重新实现事件函数和重新事件QObject.event()案例
from PyQtQtWidgets import *
from PyQtQtGui import *
from PyQtQtCore import *
from PyQt5 import QtCore
import sys
class Widget(QWidget):
def __init__(self):
super().__init__()
self.justDoubleClicked =False
self.key=''
self.text=''
self.message=''
self.resize(400,300)
self.move(100,100)
self.setWindowTitle('Events')
QTimer.singleShot(0,self.giveHelp)
#避免窗口大小重绘事件的事件影响,可以把0改成3000(3秒),然后再运行,就可以理解这行代码的意思
def giveHelp(self):
self.text='请点击这里触发追组鼠标功能'
#重绘事件,也就是触发paintEvent函数
self.update()
#重新实现关闭事件
def closeEvent(self,event):
print('Closed')
#重新实现上下文菜单事件
def contextMenuEvent(self,event):
menu=QMenu(self)
oneAction=menu.addAction('&One')
twoAction=menu.addAction('&Two')
oneAction.triggered.connect(self.one)
twoAction.triggered.connect(self.two)
if not self.message:
menu.addSeparator()
threeAction=menu.addAction('&Three')
threeAction.triggered.connect(self.three)
menu.exec_(event.globalPos())
#上下文菜单槽函数
def one(self):
self.message='Menu option one'
self.update()
def two(self):
self.message='Menu option two'
self.update()
def three(self):
self.message='Menu option three'
self.update()
#重新实现绘制事件
def paintEvent(self,event):
text=self.text
i=text.find('\n\n')
if i>=0:
text=text[0:i]
#若触发了键盘按键,则在信息文本中记录这个按键信息
if self.key:
text=text+'\n\n你按下了:{}'.format(self.key)
painter=QPainter(self)
painter.setRenderHint(QPainter.TextAntialiasing)
#重新绘制文本信息
painter.drawText(self.rect(),Qt.AlignCenter,text)
#如果文本信息存在,则在底部居中绘制信息,5秒后清空信息文本
if self.message:
painter.drawText(self.rect(),Qt.AlignBottom|Qt.AlignCenter,self.message)
QTimer.singleShot(5000,self.clearMessage)
QTimer.singleShot(5000,self.update)
def clearMessage(self):
self.message=''
#重新实现调整窗口大小事件
def resizeEvent(self,event):
self.text='调整窗口大小为:Qsize{},{}'.format(event.size().width(),event.size().height())
self.update()
#重新实现鼠标释放事件
def mouseReleaseEvent(self,event):
#若为双击释放,则不追踪鼠标移动
#若为单击释放,则需要改变追踪功能的状态,如果开启跟踪功能就跟踪,否则不跟踪
if self.justDoubleClicked:
self.justDoubleClicked=False
else:
self.setMouseTracking(not self.hasMouseTracking()) #单击鼠标
if self.hasMouseTracking():
self.text='开启鼠标追踪功能\n请移动以下鼠标\n单击鼠标可以关闭这个功能'
else:
self.text='关闭鼠标跟踪功能\n单击鼠标开启功能'
self.update()
#重新实现鼠标移动事件
def mouseMoveEvent(self,event):
if not self.justDoubleClicked:
#将窗口坐标转换为屏幕坐标
globalPos=self.mapToGlobal(event.pos())
self.text="""鼠标位置:
窗口坐标为:QPoint({0}, {1})
屏幕坐标为:QPoint({2}, {3}) """.format(event.pos().x(), event.pos().y(), globalPos.x(), globalPos.y())
self.update()
#重新实现鼠标双击事件
def mouseDoubleClickEvent(self,event):
self.justDoubleClicked=True
self.text='你双击了鼠标'
self.update()
#实现键盘按下事件
def keyPressEvent(self,event):
self.key=''
if event.key()==Qt.Key_Home:
self.key='Home'
elif event.key==Qt.Key_End:
self.key='End'
elif event.key() == Qt.Key_PageUp:
if event.modifiers() & Qt.ControlModifier:
self.key = "Ctrl+PageUp"
else:
self.key = "PageUp"
elif event.key() == Qt.Key_PageDown:
if event.modifiers() & Qt.ControlModifier:
self.key = "Ctrl+PageDown"
else:
self.key = "PageDown"
elif Qt.Key_A <= event.key() <= Qt.Key_Z:
if event.modifiers() & Qt.ShiftModifier:
self.key = "Shift+"
self.key += event.text()
if self.key:
self.key = self.key
self.update()
else:
QWidget.keyPressEvent(self, event)
'''重新实现其他事件,适用于PyQt没有提供该事件的处理函数的情况,Tab键由于涉及焦点切换,不会传递给keyPressEvent,因此,需要在这里重新定义。'''
def event(self, event):
if (event.type() == QEvent.KeyPress and
event.key() == Qt.Key_Tab):
self.key = "在event()中捕获Tab键"
self.update()
return True
return QWidget.event(self, event)
if __name__=='__main__':
app=QApplication(sys.argv)
main = Widget()
main.show()
sys.exit(app.exec_())
事件过滤器
from PyQtQtGui import *
from PyQtQtCore import *
from PyQtQtWidgets import *
import sys
class EventFilter(QDialog):
def __init__(self, parent=None):
super(EventFilter, self).__init__(parent)
self.setWindowTitle("事件过滤器")
self.label1 = QLabel("请点击")
self.label2 = QLabel("请点击")
self.label3 = QLabel("请点击")
self.LabelState = QLabel("test")
self.image1 = QImage("C:/Users/PC/Desktop/快捷方式/test.ico")
self.image2 = QImage("C:/Users/PC/Desktop/快捷方式/test.ico")
self.image3 = QImage("C:/Users/PC/Desktop/快捷方式/test.ico")
self.width = 600
self.height = 300
self.resize(self.width, self.height)
self.label1.installEventFilter(self)
self.label2.installEventFilter(self)
self.label3.installEventFilter(self)
mainLayout = QGridLayout(self)
mainLayout.addWidget(self.label1, 500, 0)
mainLayout.addWidget(self.label2, 500, 1)
mainLayout.addWidget(self.label3, 500, 2)
mainLayout.addWidget(self.LabelState, 600, 1)
self.setLayout(mainLayout)
def eventFilter(self, watched, event):
if watched == self.label1: # 只对label1的点击事件进行过滤,重写其行为,其他的事件会被忽略
if event.type() == QEvent.MouseButtonPress: # 这里对鼠标按下事件进行过滤,重写其行为
mouseEvent = QMouseEvent(event)
if mouseEvent.buttons() == Qt.LeftButton:
self.LabelState.setText("按下鼠标左键")
elif mouseEvent.buttons() == Qt.MidButton:
self.LabelState.setText("按下鼠标中间键")
elif mouseEvent.buttons() == Qt.RightButton:
self.LabelState.setText("按下鼠标右键")
'''转换图片大小'''
transform = QTransform()
transform.scale(0.5, 0.5)
tmp = self.image1.transformed(transform)
self.label1.setPixmap(QPixmap.fromImage(tmp))
if event.type() == QEvent.MouseButtonRelease: # 这里对鼠标释放事件进行过滤,重写其行为
self.LabelState.setText("释放鼠标按钮")
self.label1.setPixmap(QPixmap.fromImage(self.image1))
return QDialog.eventFilter(self, watched, event) # 其他情况会返回系统默认的事件处理方法。
if __name__ == '__main__':
app = QApplication(sys.argv)
dialog = EventFilter()
dialog.show()
sys.exit(app.exec_())
8.窗口数据传递
对于多窗口的情况,一般有两种解决方法,其中一种是主窗口获取子窗口中控件的属性;另一种是通过信号与槽机制,一般是子窗口通过发射信号的形式传递数据,主窗口的槽函数获取这些数据
8.1.单一窗口数据传递
对于具有单一窗口的程序来说,一个控件的变化会影响另一个控件的变化,这种变化利用信号与槽机制十分容易解决
from PyQtQtGui import *
from PyQtQtCore import *
from PyQtQtWidgets import *
import sys
class WinForm(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
#创建滑块和LCD控件
lcd=QLCDNumber()
slider=QSlider(Qt.Horizontal,self)
vBox=QVBoxLayout()
vBox.addWidget(lcd)
vBox.addWidget(slider)
self.setLayout(vBox)
#valueChanged()是QSlider的一个信号函数
slider.valueChanged.connect(lcd.display)
self.setGeometry(300,300,350,150)
self.setWindowTitle('信号与槽:连接滑块')
if __name__ == '__main__':
app = QApplication(sys.argv)
dialog = WinForm()
dialog.show()
sys.exit(app.exec_())
8.2.多窗口数据传递:调用属性
在PyQt编程时,经常遇到输入或选择多个参数的问题,把多个参数写入到一个窗口中,主窗口会显得臃肿,一般是添加一个按钮,调用对话框,在对话框中进行参数的选择,关闭对话框时将参数值返回给主窗口
PyQt提供了一些标准的对话框类,用于输入数据,修改数据,更改应用的设置等,常见的有QFileDialog,QInputDialog,QColorDialog,QFontDialog等。在不同的窗口之间传参有两种常用的方法:一种是在自定义对话框之间通过属性传参;另一种是在窗口之间使用信号与槽机制传参。下面是前者的案例
from PyQtQtGui import *
from PyQtQtCore import *
from PyQtQtWidgets import *
import sys
class DateDialog(QDialog):
def __init__(self,parent=None):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('DateDialog')
#在布局中添加控件
layout=QVBoxLayout()
self.datetime=QDateTimeEdit(self)
self.datetime.setCalendarPopup(True)
self.datetime.setDateTime(QDateTime.currentDateTime())
layout.addWidget(self.datetime)
#使用两个按钮(Ok和Cancel)分别连接accept()和reject()函数
buttons=QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel,Qt.Horizontal,self)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
layout.addWidget(buttons)
self.setLayout(layout)
#从对话框中获取当前的时间和日期
def dateTime(self):
return self.datetime.dateTime()
#使用静态函数创建对话框并返回(date,time,accepted)
@staticmethod
def getDateTime(parent=None):
dialog=DateDialog(parent)
result=dialog.exec_()
date=dialog.dateTime()
return (date.date(),date.time(),result==QDialog.Accepted)
from PyQtQtGui import *
from PyQtQtCore import *
from PyQtQtWidgets import *
import sys
from pyqt5 import DateDialog
class WinForm(QWidget):
def __init__(self):
super().__init__()
self.resize(400,90)
self.setWindowTitle('对话框关闭时返回值给主窗口例子')
self.lineEdit=QLineEdit(self)
self.button1=QPushButton('弹出对话框1')
self.button1.clicked.connect(self.onButton1Click)
self.button2=QPushButton('弹出对话框2')
self.button2.clicked.connect(self.onButton2Click)
gridLayout=QGridLayout()
gridLayout.addWidget(self.lineEdit)
gridLayout.addWidget(self.button1)
gridLayout.addWidget(self.button2)
self.setLayout(gridLayout)
def onButton1Click(self):
dialog=DateDialog(self)
result=dialog.exec_()
date=dialog.dateTime()
self.lineEdit.setText(date.date().toString())
print('\n日期对话框的返回值')
print('date={}'.format(str(date.date())))
print('time={}'.format(str(date.time())))
print('result={}'.format(result))
dialog.destroy()
def onButton2Click(self):
date,time,result=DateDialog.getDateTime()
self.lineEdit.setText(date.toString())
print('\n日期对话框的返回值')
print('date={}'.format(str(date)))
print('time={}'.format(str(date)))
print('result={}'.format(result))
if __name__ == '__main__':
app = QApplication(sys.argv)
dialog = WinForm()
dialog.show()
sys.exit(app.exec_())
8.3.多窗口数据传递:信号与槽
对于多窗口的数据传递,一般是通过子窗口发射信号的,主窗口通过槽函数捕获这个信号,然后获取信号里的数据。子窗口发射的信号可以是PyQt的内置信号,也可以发射自定义信号。
from PyQtQtGui import *
from PyQtQtCore import *
from PyQtQtWidgets import *
import sys
from pyqt5 import DateDialog
class WinForm(QWidget):
def __init__(self):
super().__init__()
self.resize(400,90)
self.setWindowTitle('信号与槽连接传递参数')
self.open_btn=QPushButton('获取时间')
self.lineEdit_inner=QLineEdit(self)
self.lineEdit_emit=QLineEdit(self)
self.open_btn.clicked.connect(self.openDialog)
self.lineEdit_inner.setText('接收子窗口内置信号')
self.lineEdit_emit.setText('接收子窗口自定义信号')
grid=QGridLayout()
grid.addWidget(self.lineEdit_inner)
grid.addWidget(self.lineEdit_emit)
grid.addWidget(self.open_btn)
self.setLayout(grid)
def openDialog(self):
dialog=DateDialog()
#连接子窗口的内置信号与主窗口的槽函数
dialog.datetime_inner.dateTimeChanged.connect(self.deal_inner_slot)
#连接子窗口的自定义信号与主窗口的槽函数
dialog.Signal_OneParameter.connect(self.deal_emit_slot)
dialog.exec_()
def deal_inner_slot(self,date):
self.lineEdit_inner.setText(date.toString())
def deal_emit_slot(self,str):
self.lineEdit_emit.setText(str)
if __name__ == '__main__':
app = QApplication(sys.argv)
dialog = WinForm()
dialog.show()
sys.exit(app.exec_())
六、PyQt5图形和特效
使用PyQt实现的窗口样式,默认使用的是当前操作系统的原生窗口样式。在不同的操作系统下原生窗口样式的显示效果不一样,定制窗口样式可以实现统一的窗口风格和美化窗口界面
1.窗口风格
1.1.设置窗口风格
关于窗口风格的常用方法:
setStyle(QStyle style):为每个Widget都设置风格
QStyleFactory.keys():获得当前平台支持的原有的QStyle样式
QApplication.setStyle(QStyleFactory.creare(‘WindowsXP’)):对QApplication设置QStyle样式
如果其他Widget没有设置QStyle,则默认使用QApplication设置的QStyle
from PyQtQtGui import *
from PyQtQtCore import *
from PyQtQtWidgets import *
import sys
from PyQt5 import QtCore
class AppWidget(QWidget):
def __init__(self):
super().__init__()
horizontallayout=QHBoxLayout()
self.styleLabel=QLabel('set Style:')
self.styleComboBox=QComboBox()
#从QStyleFactory中增加多个显示样式
self.styleComboBox.addItems(QStyleFactory.keys())
#选择当前窗口风格
index=self.styleComboBox.findText(QApplication.style().objectName(),QtCore.Qt.MatchFixedString)
#设置当前窗口风格
self.styleComboBox.setCurrentIndex(index)
#通过comboBox控件选择窗口风格
self.styleComboBox.activated[str].connect(self.handleStyleChanged)
horizontallayout.addWidget(self.styleLabel)
horizontallayout.addWidget(self.styleComboBox)
self.setLayout(horizontallayout)
#改变窗口风格
def handleStyleChanged(self,style):
QApplication.setStyle(style)
if __name__ == '__main__':
app = QApplication(sys.argv)
dialog = AppWidget()
dialog.show()
sys.exit(app.exec_())
1.2.设置窗口样式
PyQt 使用setWindowFlags(Qt.WindowFlags)函数设置窗口样式,具体参数如下
PyQt有如下几种基本的窗口类型
- Qt.Widget:默认窗口,有最小化,最大化,关闭按钮
- Qt.Window:普通窗口,有最小化,最大化,关闭按钮
- Qt.Dialog:对话框窗口,有问号和关闭按钮
- Qt.Popup:弹出窗口,窗口无边框
- Qt.ToolTip:提示窗口,窗口无边框,无任务栏
- Qt.SplashScreen:闪屏,窗口无边框,无任务栏
- Qt.SubWindow:子窗口,窗口无按钮,有标题
自定义顶层窗口外观标志
- Qt.MSWindowFixedSizeDialogHint:窗口无法调整大小
- Qt.FramelessWindowHint:窗口无边框
- Qt.CustomizeWindowHint:有边框但无标题和按钮,不能移动和拖动
- Qt.WindowTitleHint:添加标题栏和一个关闭按钮
- Qt.WindowSystemMenuHint:添加系统目录和一个关闭按钮
- Qt.WindowMaximizeButtonHint:激活最大化和关闭按钮,禁止最小化按钮
- Qt.WindowMinimizeButtonHint:激活最小化和关闭按钮,禁止最大化按钮
- Qt.WindowMinMaxButtonHint:激活最小化,最大化和关闭按钮
- Qt.WindowCloseButtonHint:添加一个关闭按钮
- Qt.WindowContextHelpButtonHint:添加问号和关闭按钮,类似对话框
- Qt.WindowStaysOnTopHint:窗口始终处于顶层位置
- Qt.WindowStaysOnBottomHint:窗口始终处于底层位置
from PyQtQtGui import *
from PyQtQtCore import *
from PyQtQtWidgets import *
import sys
from PyQt5 import QtCore
class AppWidget(QWidget):
def __init__(self):
super().__init__()
self.resize(400,200)
self.setWindowTitle('设置窗口样式例子')
#设置无边框窗口样式
self.setWindowFlags(Qt.FramelessWindowHint)
if __name__ == '__main__':
app = QApplication(sys.argv)
dialog = AppWidget()
dialog.show()
sys.exit(app.exec_())
1.3.使用自定义的无边框
下面开发一个自定义的无边框窗口,它可以占用100%用户屏幕窗口
from PyQtQtGui import *
from PyQtQtCore import *
from PyQtQtWidgets import *
import sys
from PyQt5 import QtCore
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
#设置窗口标志(无边框)
self.setWindowFlags(Qt.FramelessWindowHint)
#设置窗口背景颜色
self.setStyleSheet('''background-color:blue;''')
def showMaximized(self):
#得到桌面图标
desktop=QApplication.desktop()
#得到屏幕的可显示尺寸
rect=desktop.availableGeometry()
#设置窗口尺寸
self.setGeometry(rect)
#显示窗口
self.show()
if __name__=='__main__':
app=QApplication(sys.argv)
window=MyWindow()
window.showMaximized()
sys.exit(app.exec_())
2.绘图
在PyQt中常用的图像类有:QPixmap,QImage,QPicture,QBitmap
QPixmap是专门为绘图而设计的,在绘制图片时需要使用QPixmap
QImage提供了一个与硬件无关的图像表示函数,可以用于图片的像素级访问
QPicture时一个绘图设备类,它继承自QPainter类,可以使用QPainter的begin()函数在QPicture上绘图,使用end()函数结束绘图,使用QPicture的save()函数将QPainter所使用过的绘图指令保存到其文件中
QBitmap是一个继承自QPixmap的简单类,它提供了 1bit 深度的二值图像类,QBitmap提供的单色图像,可以用来制作游标(QCursor)或者笔刷(QBrush)
2.1.简单绘图
import sys
from PyQtQtWidgets import QApplication,QWidget,QHBoxLayout,QPushButton,QLabel,QComboBox,QStyleFactory
from PyQtQtWebEngineWidgets import QWebEngineView
from PyQtQtCore import QUrl,QObject,Qt,QPoint
from PyQtQtGui import QPixmap,QPainter
class GuiDemo(QWidget):
def __init__(self):
super(GuiDemo, self).__init__()
self.startPoint=QPoint()
self.endPoint = QPoint()
self.initUi()
def initUi(self):
self.setWindowTitle('demo')
self.setGeometry(300,300,500,300)
#鼠标绘图流程:1,建立Qpixmap绘图面板2,将面板加入到绘制到主界面3,定义鼠标函数和绘制函数绘制到绘图面板
self.pix=QPixmap(200,200)
self.pix.fill(Qt.white)
def paintEvent(self, QPaintEvent):#自动调用该函数
# 将画布绘制到主界面上
main_painter = QPainter(self) #必须在paintEvent中使用才有效
main_painter.drawPixmap(0, 0, self.pix)
#将图型绘制到画布上
draw_painter=QPainter(self.pix)
draw_painter.drawLine(self.startPoint,self.endPoint)
self.startPoint=self.endPoint
def mousePressEvent(self, QMouseEvent):
if QMouseEvent.button()==Qt.LeftButton:
self.startPoint=QMouseEvent.pos()
self.endPoint=self.startPoint
def mouseMoveEvent(self, QMouseEvent):
if QMouseEvent.buttons() and Qt.LeftButton:
self.endPoint=QMouseEvent.pos()
self.update()
def mouseReleaseEvent(self, QMouseEvent):
if QMouseEvent.button()==Qt.LeftButton:
self.endPoint=QMouseEvent.pos()
self.update()
if __name__=='__main__':
app=QApplication(sys.argv)
demo=GuiDemo()
demo.show()
sys.exit(app.exec_())
2.2.双缓冲绘图
from PyQtQtGui import *
from PyQtQtCore import *
from PyQtQtWidgets import *
import sys
from PyQt5 import QtCore
class Winform(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('绘制矩形')
self.pix=QPixmap()
self.lastPoint=QPoint()
self.endPoint=QPoint()
self.initUI()
def initUI(self):
self.resize(600,500)
self.pix=QPixmap(400,400)
self.pix.fill(Qt.white)
def paintEvent(self,event):
painter=QPainter(self)
x=self.lastPoint.x()
y=self.lastPoint.y()
w=self.endPoint.x()-x
h=self.endPoint.y()-y
pp=QPainter(self.pix)
pp.drawRect(x,y,w,h)
painter.drawPixmap(0,0,self.pix)
def mousePressEvent(self,event):
if event.button()==Qt.LeftButton:
self.lastPoint=event.pos()
self.endPoint=self.lastPoint
def mouseMoveEvent(self,event):
if event.buttons() and Qt.LeftButton:
self.endPoint=event.pos()
self.update()
def mouseReleaseEvent(self,event):
if event.button()==Qt.LeftButton:
self.endPoint=event.pos()
self.update()
if __name__=='__main__':
app=QApplication(sys.argv)
demo=Winform()
demo.show()
sys.exit(app.exec_())
运行程序绘制矩形时,出现了许多重影,这是因为在拖动鼠标的过程中,屏幕已经刷新了很多次,也可以理解为paintEvent()函数执行了很多次,每执行一次就会绘制一个矩形
下面是利用双缓冲技术绘制矩形而避免出现重影
from PyQtQtGui import *
from PyQtQtCore import *
from PyQtQtWidgets import *
import sys
from PyQt5 import QtCore
class Winform(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('双缓冲绘制例子')
self.pix=QPixmap()
self.lastPoint=QPoint()
self.endPoint=QPoint()
#辅助画布,如果正在绘图,也就是还没有释放鼠标左键,就在辅助画布上进行,只有释放左键才会在辅助画布上绘图
self.tempix=QPixmap()
#标志是否正在绘图
self.isDrawing=False
self.initUI()
def initUI(self):
self.resize(600,500)
self.pix=QPixmap(400,400)
self.pix.fill(Qt.white)
def paintEvent(self,event):
painter=QPainter(self)
x=self.lastPoint.x()
y=self.lastPoint.y()
w=self.endPoint.x()-x
h=self.endPoint.y()-y
if self.isDrawing:
self.tempix=self.pix
pp=QPainter(self.tempix)
pp.drawRect(x,y,w,h)
painter.drawPixmap(0,0,self.tempix)
else:
pp=QPainter(self.pix)
pp.drawRect(x,y,w,h)
painter.drawPixmap(0,0,self.pix)
def mousePressEvent(self,event):
if event.button()==Qt.LeftButton:
self.lastPoint=event.pos()
self.endPoint=self.lastPoint
self.isDrawing=True
'''
def mouseMoveEvent(self,event):
if event.buttons() and Qt.LeftButton:
self.endPoint=event.pos()
self.update()
'''
def mouseReleaseEvent(self,event):
if event.button()==Qt.LeftButton:
self.endPoint=event.pos()
self.update()
self.isDrawing=False
if __name__=='__main__':
app=QApplication(sys.argv)
demo=Winform()
demo.show()
sys.exit(app.exec_())
双缓冲技术总结:
要实现绘制一个任意大小的矩形而不出现重影,需要两个画布,它们都是QPixmap实例,其中temPix作为临时缓冲区,当拖动鼠标绘制矩形时,将内容先绘制到temPix上,然后将temPix绘制到界面上,pix作为缓冲区,用来保存已经完成的绘制,当释放鼠标按键完成矩形的绘制后,则将temPix的内容复制到pix上,为了在绘制时不出现重影,而且保证以前绘制的内容不消失,那么每一次绘制都是在原来的图形上进行的,所有需要在绘制temPix之前,将pix的内容复制到temPix上,这样形成了两个缓冲区,称为 “双缓冲绘图”
3.QSS的UI美化
QSS即Qt样式表,时用来定义自定义控件外观的一种机制,QSS大量参考了CSS的内容,但QSS的功能比CSS要弱得多,而且并不是所有的属性都可以应用在PyQt的控件上
3.1.QSS的语法规则
QSS样式有两部分组成,一部分是选择器,一部分是声明,声明部分是一系列的键值对,使用分号风格,使用大括号将所有的声明包括在内,继承关系也会被选中
from PyQtQtGui import *
from PyQtQtCore import *
from PyQtQtWidgets import *
import sys
from PyQt5 import QtCore
class Winform(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('QSSDemo')
btn1=QPushButton(self)
btn1.setText('1')
btn2=QPushButton(self)
btn2.setText('2')
vbox=QVBoxLayout()
vbox.addWidget(btn1)
vbox.addWidget(btn2)
self.setLayout(vbox)
if __name__=='__main__':
app=QApplication(sys.argv)
demo=Winform()
qssStyle='''
QPushButton{
background-color:red
}
'''
demo.setStyleSheet(qssStyle)
demo.show()
sys.exit(app.exec_())
3.2.QSS选择器类型
QSS选择器有:
1.通配选择器:*,匹配所有控件
2.类型选择器:QPushButton,匹配所有的QPushButton类及其子类的实例
3.属性选择器:QPushButton[name=‘myBtn’],匹配所有name属性是myBtnd1QPushButton实例,属性可自定义
4.类选择器:**.**QPushButton,匹配所有QPushButton实例,但并不匹配其子类
ID选择器:#myButton,匹配所有ID为myButton的控件,这里的ID实际上就是objectName指定的值
后代选择器:QDialog QPushButton,匹配所有QDialog容器中包含的QPushButton,不管是直接的还是间接的
7.子选择器:QDialog>QPushButton,匹配所有QDialog容器的子QPushButton
另外,上面所有的选择器可以联合使用,并且支持一次设置多种选择器类型
3.3.QSS子控件
QSS子控件也是一种选择器,其应用在一些复合控件上,典型的如QComboBox,该控件的外观是,有一个矩形的外边框,右边有一个下拉箭头,点击之后会弹出下拉列表,例如:
QComboBox::drop-down{image:url(dropdown.png)}
上面的样式指定所有的QComboBox的下拉箭头的图片是自定义的,图片文件为dropdown.png
from PyQtQtGui import *
from PyQtQtCore import *
from PyQtQtWidgets import *
import sys
from PyQt5 import QtCore
class Winform(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('QComboBox样式')
combo=QComboBox(self)
combo.setObjectName('myQComboBox')
combo.addItem('Window')
combo.addItem('Ubuntu')
combo.addItem('Red Hat')
combo.move(50,50)
self.setGeometry(250,200,320,150)
if __name__=='__main__':
app=QApplication(sys.argv)
win=Winform()
qssStyle='''
QComboBox#myQComboBox::drop-down{
image:url(C:/Users/PC/Desktop/test.png)
}
'''
win.setStyleSheet(qssStyle)
win.show()
sys.exit(app.exec_())
3.4.QSS伪状态
QSS伪状态选择器是以冒号开头的一个选择表达式,例如:hover,可以表示当鼠标指针经过时的状态,伪状态选择器限制了控件处于某状态时才可以使用QSS规则,伪状态只能描述一个控件或者一个复合控件的子控件的状态,所有它只能放在选择器的最后面。伪状态还可以使用一个感叹号来表示状态,例如:!hover表示鼠标指针没有经过时的状态
4.设置窗口背景
窗口背景主要包括:背景色和背景图片,设置窗口背景的主要有三种方法
1.使用QSS设置窗口背景
2.使用QPalette设置窗口背景
3.实现paintEvent,使用QPainter绘制背景
4.1.使用QSS设置窗口背景
在QSS中,可以使用background或者background-color的方式来设置背景色。设置窗口背景色后,子控件默认会继承父窗口的背景色。如果想要为控件设计背景色或图标,可以使用setPixmap或者setIcon来完成
使用setStyleSheet()设置窗口背景图片
win=QMainWindow()
win.setObjectName('MainWindow')
win.setStyleSheet('#MainWindow{border-image:url(xx.jpg)}')
使用setStyleSheet()设置窗口背景颜色
win=QMainWindow()
win.setObjectName('MainWindow')
win.setStyleSheet('#MainWindow{background-color:yellow}')
4.2.使用QPalette设置窗口背景
使用QPalette设置窗口背景
win=QMainWindow()
palette=QPalette()
palette.setColor(QPalette.Background,Qt.red)
win.setPalette(palette)
使用QPalette设置窗口背景图片
当使用QPalette设置背景图片时,需要考虑背景图片的尺寸,当背景图片的宽度和高度大于窗口时,背景图片会平铺整个背景,反之会加载多个背景图片;还有查看图片的分辨率
win=QMainWindow()
palette=QPalette()
palette.setBrush(QPalette.Background,QBrush(QPixmap(xx.jpg)))
win.setPalette(palette)
win.resize(xx,xx)
4.3.使用paintEvent设置窗口背景
使用paintEvent设置窗口背景色
def painterEvent(self,event):
painter=QPainter(self)
painter.setBrush(Qt.black)
painter.drawRect(self.rect())
使用paintEvent设置窗口背景图片
def painterEvent(self,event):
painter=QPainter(self)
pixmap=QPixmap('xx.jpg')
painter.drawPixmap(sel.rect(),pixmap)
5.不规则窗口的显示
QWidget类中比较重要的绘图函数:
setMask(self,QBitmap/QRegion):为调用它的控件增加一个遮罩,遮住所选区域以外的部分,使之看起来是透明的。它的参数可以是QBitmap或QRegion对象,调用QPixmap的mask()可以获得图片自身的遮罩,是一个QBitmap对象
paintEvent(self,QPaintEvent):通过重载paintEvent()函数绘制窗口背景
1.实现不规则窗口的最简单方式就是图片素材不仅当遮罩层,还当背景图片,通过重载paintEvent()函数绘制窗口背景
import sys
from PyQtQtWidgets import QApplication,QWidget
from PyQtQtGui import QPixmap,QPainter,QBitmap
class MyForm(QWidget):
def __init__(self,parent=None):
super(MyForm, self).__init__(parent)
#设置标题与初始窗口大小
self.setWindowTitle('不规则窗口的实现例子')
def paintEvent(self, QPaintEvent):
painter=QPainter(self)
#在指定位置绘制图片
painter.drawPixmap(0,0,280,390,QPixmap(r'E:\\4k图片\\冬天雪山雪景小木屋4k风景壁纸.jpg'))
painter.drawPixmap(300,0,280,390,QBitmap(r'E:\\4k图片\\冬天雪山雪景小木屋4k风景壁纸.jpg'))
if __name__ == '__main__':
app=QApplication(sys.argv)
form=MyForm()
form.show()
sys.exit(app.exec_())
2.使用两张图片,一张用来做遮罩来控制窗口的大小,然后在利用paintEvent()函数重绘另一张为窗口的背景图。
import sys
from PyQtQtWidgets import QApplication,QWidget
from PyQtQtGui import QPixmap,QPainter,QBitmap
class MyForm(QWidget):
def __init__(self,parent=None):
super(MyForm, self).__init__(parent)
#设置标题与初始窗口大小
self.setWindowTitle('不规则窗口的实现例子')
self.pix=QBitmap(r'E:\\4k图片\\冬天雪山雪景小木屋4k风景壁纸.jpg')
self.resize(self.pix.size())
self.setMask(self.pix)
def paintEvent(self, QPaintEvent):
painter=QPainter(self)
#在指定位置绘制图片
painter.drawPixmap(0,0,self.pix.width(),self.pix.height(),QPixmap(r'E:\\4k图片\\美丽雪山湖泊风景4k壁纸3840x2160.jpg'))
if __name__ == '__main__':
app=QApplication(sys.argv)
form=MyForm()
form.show()
sys.exit(app.exec_())
3.可以拖动的不规则窗口
import sys
from PyQtQtWidgets import QApplication, QWidget
from PyQtQtGui import QPixmap, QPainter, QCursor, QBitmap
from PyQtQtCore import Qt
class ShapeWidget(QWidget):
def __init__(self, parent=None):
super(ShapeWidget, self).__init__(parent)
self.setWindowTitle("不规则的,可以拖动的窗体实现例子")
self.mypix()
# 显示不规则 pix
def mypix(self):
#获得图片自身的遮罩
self.pix = QBitmap("E:\\4k图片\\冬天雪山雪景小木屋4k风景壁纸.jpg")
#将获得的图片的大小作为窗口的大小
self.resize(self.pix.size())
#增加一个遮罩
self.setMask(self.pix)
#print(self.pix.size())
self.dragPosition = None
# 重定义鼠标按下响应函数mousePressEvent(QMouseEvent)
# 鼠标移动响应函数mouseMoveEvent(QMouseEvent),使不规则窗体能响应鼠标事件,随意拖动。
def mousePressEvent(self, event):
#鼠标左键按下
if event.button() == Qt.LeftButton:
self.m_drag = True
self.m_DragPosition = event.globalPos() - self.pos()
event.accept()
self.setCursor(QCursor(Qt.OpenHandCursor))
if event.button() == Qt.RightButton:
self.close()
def mouseMoveEvent(self, QMouseEvent):
if Qt.LeftButton and self.m_drag:
# 当左键移动窗体修改偏移值
self.move(QMouseEvent.globalPos() - self.m_DragPosition)
QMouseEvent.accept()
def mouseReleaseEvent(self, QMouseEvent):
self.m_drag = False
self.setCursor(QCursor(Qt.ArrowCursor))
# 一般 paintEvent 在窗体首次绘制加载, 要重新加载paintEvent
# 需要重新加载窗口使用 self.update() or self.repaint()
def paintEvent(self, event):
painter = QPainter(self)
#在指定位置绘制图片
painter.drawPixmap(0, 0, self.width(), self.height(), QPixmap("E:\\4k图片\\美丽雪山湖泊风景4k壁纸3840x2160.jpg"))
if __name__ == '__main__':
app = QApplication(sys.argv)
form = ShapeWidget()
form.show()
app.exec_()
5.1.不规则窗口实现动画效果
使用PyQt 设计不规则窗口的动画效果时要注意
1.pixmap.setMask()函数的作用是为调用它的控件增加一个遮罩,遮住所选区域以外的地方,使控件看起来是透明的,它的参数是一个QBitmap对象或一个QRegion对象
2.paintEvent()函数每次初始化窗口时只调用一次,所以没加载一次图片就要重新调用一次paintEvent()函数,即在更新窗口时调用这个函数,更新窗口的核心代码如下
3.定时器的时间到期后更新窗口代码
import sys
from PyQtQtCore import *
from PyQtQtGui import *
from PyQtQtWidgets import *
class ShapeWidget(QWidget):
def __init__(self,parent=None):
super(ShapeWidget, self).__init__(parent)
self.i=1
self.mypix()
self.timer=QTimer()
self.timer.setInterval(500)
self.timer.timeout.connect(self.timeChanged)
self.timer.start()
#显示不规则图片
def mypix(self):
self.update()
if self.i==5:
self.i=1
self.mypic={1:'E:\\4k图片\\美丽雪山湖泊风景4k壁纸3840x2160.jpg',2:'E:\\4k图片\\小米笔记本pro 4k风景壁纸.jpg',3:'E:\\4k图片\\新西兰埃格蒙特国家公园4k风景壁纸.jpg',4:'E:\\4k图片\\星空 海 石头 树 4k风景壁纸3840x2160.jpg'}
self.pix=QPixmap(self.mypic[self.i],'0',Qt.AvoidDither|Qt.ThresholdAlphaDither|Qt.ThresholdDither)
self.resize(self.pix.size())
self.setMask(self.pix.mask())
self.dragPosition=None
def mousePressEvent(self, QMouseEvent):
if QMouseEvent.button()==Qt.LeftButton:
self.m_drag=True
self.m_DragPosition=QMouseEvent.globalPos()-self.pos()
QMouseEvent.accept()
self.setCursor(QCursor(Qt.OpenHandCursor))
def mouseMoveEvent(self, QMouseEvent):
if Qt.LeftButton and self.m_drag:
self.move(QMouseEvent.globalPos()-self.m_DragPosition)
QMouseEvent.accept()
def mouseReleaseEvent(self, QMouseEvent):
self.m_drag=False
self.setCursor(QCursor(Qt.ArrowCursor))
def paintEvent(self, QPaintEvent):
painter=QPainter(self)
painter.drawPixmap(0,0,self.pix.width(),self.pix.height(),self.pix)
def mouseDoubleClickEvent(self, QMouseEvent):
if QMouseEvent.button()==1:
self.i+=1
self.mypix()
def timeChanged(self):
self.i+=1
self.mypix()
if __name__ == '__main__':
app=QApplication(sys.argv)
form=ShapeWidget()
form.show()
sys.exit(app.exec_())
5.2.加载GIF动画效果
import sys
from PyQtQtCore import *
from PyQtQtGui import *
from PyQtQtWidgets import *
class LoadingGifWin(QWidget):
def __init__(self,parent=None):
super(LoadingGifWin, self).__init__(parent)
#实例化标签到窗口中
self.label=QLabel('',self)
#设置标签的宽度与高度
self.setFixedSize(128,128)
#设置无边框
self.setWindowFlags(Qt.Dialog | Qt.CustomizeWindowHint)
self.movie=QMovie('C:\\Users\\PC\\Desktop\\test.gif')
self.label.setMovie(self.movie)
self.movie.start()
if __name__ == '__main__':
app=QApplication(sys.argv)
load=LoadingGifWin()
load.show()
sys.exit(app.exec_())
6.设置样式
6.1.为标签添加背景图片
label=QLabel(self)
label.setToopTil('这是一个文本标签')
label.setStyleSheet('QLabel{border-image:url(xx.jpg);}')
label.setFixedWidth(xx)
label.setFixedHeight(xx)
6.2.为按钮添加背景图片
btn=QPushButton(self)
btn.setObjectName('btn')
btn.setMaximumSize(64,64)
btn.setMinimumSize(64,64)
style='''
#btn{
border-radius:30px;
background-image:url('xxx.jpg')
}
#btn:hover{
border-radius:30px;
background-image:url('xxx.jpg')
}
#btn:Pressed{
border-radius:30px;
background-image:url('xxx.jpg')
}
btn.setStyleSheet(style)
6.3.缩放图片
filename=r'xxx.jpg'
image=QImage(filename)
#设置标签的宽度和高度为120像素,所加载的图片按照标签的高度和宽度等比例缩放
label=QLabel(self)
label.setFixedWidth(120)
label.setFixedHeight(120)
#缩放图片,以固定大小显示
result=img.sccaled(label.width(),label.height(),Qt.IgnoreAspectRatio,Qt.SmoothTransformation)
#在标签控件上显示图片
label.setPixmap(QPixmap.fromImage(result))
6.4.设置窗口透明
如果窗口是透明的,就可以通过窗口看到桌面的效果,想实现窗口的透明效果,需要设置窗口的透明度
win=QMainWindow()
win.setWindowOpacity(0.5)
6.5.加载QSS
在Qt中经常需要使用样式,为了使代码与逻辑分离,通常可以定义一个QSS文件,然后编写控件,最后用QApplication或QMainWindow来加载样式
1.编写QSS
MainWindow{
border-image:url('xx.jpg')
}
QToolTip{
border:1px solid rgb(45,45,45)
background:white;
color:red
}
2.加载QSS
Class CommonHelper:
def __init__(self):
pass
@staticmethod
def readQss(style):
with open(style,'r') as f:
return f.read()
app=QApplication(sys.argv)
win=MainWindow()
styleFile='./style.qss'
#换肤进行全局修改,只需修改不同的QSS文件即可
style=CommonHelper.readQss(styleFile)
win.setStyleSheet(style)
win.show()
sys.exit(app.exec_())
七、实战案例
7.1、获取城市天气预报
通过天气预报网站提供的 API 可以直接获取结构化数据,官网地址是 http://www.weather.com.cn/
获取不同城市的天气预报 API 的请求地址是 http://www.weather.com.cn/data/sk/101010100.html
城市代码可以在网上查找到,北京:101010100 天津 101030100 上海 101020100
用 requests 库尝试获取信息
import requests
city='101010100'
response=requests.get('http://www.weather.com.cn/data/sk/{}.html'.format(city))
response.encoding='utf-8'
print(response.json())
'''
{'weatherinfo': {'city': '北京', 'cityid': '101010100', 'temp': '27.9', 'WD': '南风', 'WS': '小于3级', 'SD': '28%', 'AP': '1002hPa', 'njd': '暂无实况', 'WSE': '<3', 'time': '17:55', 'sm': '2.1', 'isRadar': '1', 'Radar': 'JC_RADAR_AZ9010_JB'}}
'''
用Qt Designer 设计天气预报界面
编辑对象名,连接信号与槽
转换 UI 文件为 Python 文件
编写逻辑实现
import sys
from PyQtQtCore import *
from PyQtQtGui import *
from PyQtQtWidgets import *
from Weather import Ui_Form
import requests
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.ui=Ui_Form()
self.ui.setupUi(self)
def findweather(self):
cityName=self.ui.cityCombo.currentText()
cityCode=self.transCityName(cityName)
response=requests.get('http://www.weather.com.cn/data/sk/{}.html'.format(cityCode))
response.encoding='utf-8'
data=response.json()['weatherinfo']
msg1='城市:{}'.format(data['city'])+'\n'
msg2='风向:{}'.format(data['WD'])+'\n'
msg3='温度:{}'.format(data['temp'])+'\n'
msg4='湿度:{}'.format(data['SD'])+'\n'
msg5='风力:{}'.format(data['WSE'])+'\n'
result=msg1+msg2+msg3+msg4+msg5
self.ui.responseEdit.setText(result)
def transCityName(self,cityName):
cityCode=''
if cityName=='北京':
cityCode='101010100'
elif cityName=='天津':
cityCode='101030100'
elif cityName=='上海':
cityCode='101020100'
return cityCode
def clearweather(self):
self.ui.responseEdit.clear()
if __name__ == '__main__':
app=QApplication(sys.argv)
form=MainWindow()
form.show()
sys.exit(app.exec_())
7.2.复利计算
复利计算,是指没经过一个计息期后,都要将本金所生的利息加入本金中,以计算下期的利息。这就设计了复利现值,复利终值和复利计算公式
复利现值,是指在计算复利的情况下,要达到未来某一特定的资金金额,先择必须投入的本金。所谓复利,也称利上加利,是指一笔存款或者投资获得回报之后,再连本带利进行新一轮投资的方法
复利终值,是指本金再约定期限内获得利息后,将利息加入本金中再计算利息,逐期滚算到约定期末的本金之和
复利计算公式是 S=P(1+i) ^n,P为本金,i 为利率,n 为持有期限
下面只使用编码的方式实现
from __future__ import division
import sys
from PyQtQtCore import *
from PyQtQtGui import *
from PyQtQtWidgets import *
class Form(QDialog):
def __init__(self):
super().__init__()
principalLabel=QLabel('Principal:')
self.principalSpinBox=QDoubleSpinBox()
self.principalSpinBox.setRange(1,100000000)
self.principalSpinBox.setValue(1000)
self.principalSpinBox.setPrefix('RMB ')
rateLabel=QLabel('Rate:')
self.rateSpinBox=QDoubleSpinBox()
self.rateSpinBox.setRange(1,100)
self.rateSpinBox.setValue(5)
self.rateSpinBox.setSuffix('%')
yearsLabel=QLabel('Years:')
self.yearsComboBox=QComboBox()
self.yearsComboBox.addItem('1 year')
self.yearsComboBox.addItems(['{} years'.format(x) for x in range(2,31)])
amountLabel=QLabel('Amount')
self.amountLabel=QLabel()
grid=QGridLayout()
grid.addWidget(principalLabel,0,0)
grid.addWidget(self.principalSpinBox,0,1)
grid.addWidget(rateLabel,1,0)
grid.addWidget(self.rateSpinBox,1,1)
grid.addWidget(yearsLabel,2,0)
grid.addWidget(self.yearsComboBox,2,1)
grid.addWidget(amountLabel,3,0)
grid.addWidget(self.amountLabel,3,1)
self.setLayout(grid)
self.principalSpinBox.valueChanged.connect(self.updateUi)
self.rateSpinBox.valueChanged.connect(self.updateUi)
self.yearsComboBox.currentIndexChanged.connect(self.updateUi)
self.setWindowTitle('Interest')
self.updateUi()
def updateUi(self):
Principal=self.principalSpinBox.value()
rate=self.rateSpinBox.value()
years=self.yearsComboBox.currentIndex()+1
amount=Principal*((1+(rate/100.0))**years)
self.amountLabel.setText('RMB {:.2f}'.format(amount))
if __name__ == '__main__':
app=QApplication(sys.argv)
form=Form()
form.show()
sys.exit(app.exec_())
FixedWidth(xx)
label.setFixedHeight(xx)
6.2.为按钮添加背景图片
btn=QPushButton(self)
btn.setObjectName('btn')
btn.setMaximumSize(64,64)
btn.setMinimumSize(64,64)
style='''
#btn{
border-radius:30px;
background-image:url('xxx.jpg')
}
#btn:hover{
border-radius:30px;
background-image:url('xxx.jpg')
}
#btn:Pressed{
border-radius:30px;
background-image:url('xxx.jpg')
}
btn.setStyleSheet(style)
6.3.缩放图片
filename=r'xxx.jpg'
image=QImage(filename)
#设置标签的宽度和高度为120像素,所加载的图片按照标签的高度和宽度等比例缩放
label=QLabel(self)
label.setFixedWidth(120)
label.setFixedHeight(120)
#缩放图片,以固定大小显示
result=img.sccaled(label.width(),label.height(),Qt.IgnoreAspectRatio,Qt.SmoothTransformation)
#在标签控件上显示图片
label.setPixmap(QPixmap.fromImage(result))
6.4.设置窗口透明
如果窗口是透明的,就可以通过窗口看到桌面的效果,想实现窗口的透明效果,需要设置窗口的透明度
win=QMainWindow()
win.setWindowOpacity(0.5)
6.5.加载QSS
在Qt中经常需要使用样式,为了使代码与逻辑分离,通常可以定义一个QSS文件,然后编写控件,最后用QApplication或QMainWindow来加载样式
1.编写QSS
MainWindow{
border-image:url('xx.jpg')
}
QToolTip{
border:1px solid rgb(45,45,45)
background:white;
color:red
}
2.加载QSS
Class CommonHelper:
def __init__(self):
pass
@staticmethod
def readQss(style):
with open(style,'r') as f:
return f.read()
app=QApplication(sys.argv)
win=MainWindow()
styleFile='./style.qss'
#换肤进行全局修改,只需修改不同的QSS文件即可
style=CommonHelper.readQss(styleFile)
win.setStyleSheet(style)
win.show()
sys.exit(app.exec_())
七、实战案例
7.1、获取城市天气预报
通过天气预报网站提供的 API 可以直接获取结构化数据,官网地址是 http://www.weather.com.cn/
获取不同城市的天气预报 API 的请求地址是 http://www.weather.com.cn/data/sk/101010100.html
城市代码可以在网上查找到,北京:101010100 天津 101030100 上海 101020100
用 requests 库尝试获取信息
import requests
city='101010100'
response=requests.get('http://www.weather.com.cn/data/sk/{}.html'.format(city))
response.encoding='utf-8'
print(response.json())
'''
{'weatherinfo': {'city': '北京', 'cityid': '101010100', 'temp': '27.9', 'WD': '南风', 'WS': '小于3级', 'SD': '28%', 'AP': '1002hPa', 'njd': '暂无实况', 'WSE': '<3', 'time': '17:55', 'sm': '2.1', 'isRadar': '1', 'Radar': 'JC_RADAR_AZ9010_JB'}}
'''
用Qt Designer 设计天气预报界面
编辑对象名,连接信号与槽
转换 UI 文件为 Python 文件
编写逻辑实现
import sys
from PyQtQtCore import *
from PyQtQtGui import *
from PyQtQtWidgets import *
from Weather import Ui_Form
import requests
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.ui=Ui_Form()
self.ui.setupUi(self)
def findweather(self):
cityName=self.ui.cityCombo.currentText()
cityCode=self.transCityName(cityName)
response=requests.get('http://www.weather.com.cn/data/sk/{}.html'.format(cityCode))
response.encoding='utf-8'
data=response.json()['weatherinfo']
msg1='城市:{}'.format(data['city'])+'\n'
msg2='风向:{}'.format(data['WD'])+'\n'
msg3='温度:{}'.format(data['temp'])+'\n'
msg4='湿度:{}'.format(data['SD'])+'\n'
msg5='风力:{}'.format(data['WSE'])+'\n'
result=msg1+msg2+msg3+msg4+msg5
self.ui.responseEdit.setText(result)
def transCityName(self,cityName):
cityCode=''
if cityName=='北京':
cityCode='101010100'
elif cityName=='天津':
cityCode='101030100'
elif cityName=='上海':
cityCode='101020100'
return cityCode
def clearweather(self):
self.ui.responseEdit.clear()
if __name__ == '__main__':
app=QApplication(sys.argv)
form=MainWindow()
form.show()
sys.exit(app.exec_())
7.2.复利计算
复利计算,是指没经过一个计息期后,都要将本金所生的利息加入本金中,以计算下期的利息。这就设计了复利现值,复利终值和复利计算公式
复利现值,是指在计算复利的情况下,要达到未来某一特定的资金金额,先择必须投入的本金。所谓复利,也称利上加利,是指一笔存款或者投资获得回报之后,再连本带利进行新一轮投资的方法
复利终值,是指本金再约定期限内获得利息后,将利息加入本金中再计算利息,逐期滚算到约定期末的本金之和
复利计算公式是 S=P(1+i) ^n,P为本金,i 为利率,n 为持有期限
下面只使用编码的方式实现
from __future__ import division
import sys
from PyQtQtCore import *
from PyQtQtGui import *
from PyQtQtWidgets import *
class Form(QDialog):
def __init__(self):
super().__init__()
principalLabel=QLabel('Principal:')
self.principalSpinBox=QDoubleSpinBox()
self.principalSpinBox.setRange(1,100000000)
self.principalSpinBox.setValue(1000)
self.principalSpinBox.setPrefix('RMB ')
rateLabel=QLabel('Rate:')
self.rateSpinBox=QDoubleSpinBox()
self.rateSpinBox.setRange(1,100)
self.rateSpinBox.setValue(5)
self.rateSpinBox.setSuffix('%')
yearsLabel=QLabel('Years:')
self.yearsComboBox=QComboBox()
self.yearsComboBox.addItem('1 year')
self.yearsComboBox.addItems(['{} years'.format(x) for x in range(2,31)])
amountLabel=QLabel('Amount')
self.amountLabel=QLabel()
grid=QGridLayout()
grid.addWidget(principalLabel,0,0)
grid.addWidget(self.principalSpinBox,0,1)
grid.addWidget(rateLabel,1,0)
grid.addWidget(self.rateSpinBox,1,1)
grid.addWidget(yearsLabel,2,0)
grid.addWidget(self.yearsComboBox,2,1)
grid.addWidget(amountLabel,3,0)
grid.addWidget(self.amountLabel,3,1)
self.setLayout(grid)
self.principalSpinBox.valueChanged.connect(self.updateUi)
self.rateSpinBox.valueChanged.connect(self.updateUi)
self.yearsComboBox.currentIndexChanged.connect(self.updateUi)
self.setWindowTitle('Interest')
self.updateUi()
def updateUi(self):
Principal=self.principalSpinBox.value()
rate=self.rateSpinBox.value()
years=self.yearsComboBox.currentIndex()+1
amount=Principal*((1+(rate/100.0))**years)
self.amountLabel.setText('RMB {:.2f}'.format(amount))
if __name__ == '__main__':
app=QApplication(sys.argv)
form=Form()
form.show()
sys.exit(app.exec_())