学习PYTHON(PYQT5)发的第二篇有关多线程的原创代码,主要功能是在主窗体上创建一标签控件作为画板,在画板上通过多线程来画出多彩文字和同心圆等复杂图形,本示例代码可以参考以下实现方式:
1、如何在窗体上创建独立的画布(实际为标签控件)
2、标签控件的绘图等事件重载
3、在多线程中来实际复杂的计算并将图形画在画板中
4、多线程同窗体间的数据通信(类似我第一篇文章)
5、自定义信号槽的使用方法(窗体控件同窗体,多线程同窗体)
代码运行效果如下图:
# -*- coding: utf-8 -*-
import sys
import PyQt5
from PyQt5 import *
from PyQt5 import QtCore #不加此行要报错,上行不是全导入了嘛,没懂
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
#from turtle import * #本示例没有使用turtle库无需导入
import time
import math
import copy
import random
##########################################################################################################
class MyMainFrm(QMainWindow):
def __init__(self, parent=None):
super(MyMainFrm, self).__init__(parent)
self.setWindowIcon(QIcon("1.png"))
self.resize(900, 900)
self.setMaximumSize(910,910)
self.setWindowTitle('多线程标签画板示例')
#########################################################################################################################################
self.label_Demo = MyLabelDemo(self)
# 设置label的尺寸
self.label_Demo.setMaximumSize(1000,1000)
self.label_Demo.setGeometry(50, 5, 1000,1000)
self.threadMaxCount=10 #本程序最大可开多线程的数量
self.demoClickNum=0 #DEMO标签控件被单击的次数,超出threadMaxCount归0循环(0值即线程全停)
#定义槽函数,标签DEMO被单击的次数决定启动线程的数量
#定义线程数组
self.thread={}
self.threadOpen=[] #0索引起用,0对应首个线程
for i in range(self.threadMaxCount):
self.threadOpen.append(False) #定义n个线程打开的状况,供计时器函数中取线程值时使用
self.thread[i] = ThreadClass(parent=None,index=i)
self.threadOpen[i]=False #设置线程暂不打开,单击标签画板后打开
#self.thread[i].start()
self.setThreadObj(i,self.label_Demo) #向线程中传入标签控件实例对象
self.thread[i].signal_ID.connect(self.my_function) #将线程1中的自定义信号signal_ID绑定槽函数self.my_function
self.label_Demo.signal_clicked.connect(self.labDemoClick) #将自定义的DEMO标签类信号
#DEMO绘图标签被点击时
def labDemoClick(self):
"""if(self.demoClickNum>=self.threadMaxCount):
self.demoClickNum=0
for n in range(self.threadMaxCount):
self.threadOpen[n]=False
self.thread[n].stop() #关闭全部线程,原线程发送的信号,对应槽函数未必已经处理完毕,可能还在接收信号
time.sleep(1)
for i in range(self.threadMaxCount):
if(i <= (self.demoClickNum)):
if(self.threadOpen[i]==False): #对应线程还没打开,打开此序号线程
self.threadOpen[i]=True
print(f'单击标签画板:打开线程{i}')
self.thread[i].start()
self.demoClickNum = self.demoClickNum + 1 #DEMO标签控件单击次数+1,好同线程打开数量对应"""
#上面的行功能没法实现,故又回到只运行一个线程来看绘图演示,多个线程不能实现同时绘图?
self.thread[0].start() #线程0中画同心圆DEMO示例
self.threadOpen[0]=True
#self.thread[1].start() #线程1中画彩色文本DEMO示例(只能在一个多线程中依次画,同时用两个及以上线程作画没成功??)
#self.threadOpen[1]=True
#将标签控件画板实例对象传入线程,这样仍会在线程中占用窗体资源,想办法对内存中的绘图实例进行操作,界面只作反馈
def setThreadObj(self,index,obj):
self.thread[index].setObj(obj)
#绑定线程类中的signal_ID信号对应的槽函数,得到各线程中的变量ID的值(仅示例线程同窗体数据交互,同本示例无关)
def my_function(self,counter):
ID = counter
index = self.sender().index #在槽函数中被调用,用于获取发出信号的对象,的索引号
print(f'主窗体接收线程槽函数:{index} 返回整数值{ID}')
if index == 0:
pass
elif index == 1:
pass
elif index == 2:
pass
#......
#重载标签类,把标签区域作为画板DEMO学示绘画区域
class MyLabelDemo(QLabel):
#鼠标起点,鼠标终点
lastPoint = QPoint(0,0) #在类函数体外可以不加self前缀,但在函数体名类对象引用时,必须要加self的前缀
endPoint = QPoint(0,0)
bDrawOK=True #处理因重载绘图事件过快,可能会多画一此不可预料的杂图
signal_clicked = QtCore.pyqtSignal(str) #自定标签类的信号,即标签被单击时发出(传回参数为标签文本)
#线条或文本的颜色:实际可在界面用颜色选择框来选择
lst_col = ["black", "red", "green", "blue", "purple", "orange", "MediumSlateBlue", "CornflowerBlue",
"DodgerBlue", "DeepskyBlue", "LightSkyBlue", "SkyBlue", "LightBlue"]
#字体名称:实际可在界面中用字体选择框来选择
lst_fontname=['宋体','仿宋','黑体','楷体','方正行楷简体','微软雅黑'] #初始化
def __init__(self, text=''):
super(MyLabelDemo, self).__init__(text)
cursor = QCursor(Qt.CrossCursor) #光标类型
self.MyDrawText="本示例为多线程在画板(标签控件)上画出同心圆及多彩文字,近期推出可画直线、圆、椭圆、矩形、填充矩形等图形的画板PYTHON+PQ5示例, 敬请关注我哦!!!"
self.lst_drawText=[[]] #将 MyDrawText文本分解此列表中,每个列表对象中的数据仍是列表,格式为['多','宋体',16,'red',True......][.......] 示例只使用了部份属性功能
self.splitDrawText(self.MyDrawText) #调用函数分解字符
#定义当前绘画类型
self.pix = QPixmap(800,800) #实例化QPixmap类
self.pix.fill(Qt.white)
self.setPixmap(self.pix) # 把pix_img传递给label
pp0 = QPainter(self.pix)
pp0.drawText(QRect(0,0,500,35),0,'多线程画同心圆和彩色文本的示例,请单击标签画板绘图区域打开多线程查看演示。')
self.noPatter = QPainter(self.pix).brush()
#重载绘图函数:根据选择设置,画不同的图形 #0=非绘画模式 1=画线模式 2=画矩形模式 3=画填充矩形模式 4=画圆模式 5=画椭圆模式 6=随手画模式 7=画文本模式
def paintEvent(self, event):
if(self.bDrawOK==False): #处理因此函数调用过于频凡造成多画的现象
return
x1 = self.lastPoint.x()
y1 = self.lastPoint.y()
x2 = self.endPoint.x()
y2 = self.endPoint.y()
point1=QPoint(x1,y1)
point2=QPoint(x2,y2)
#以下语句运行正常,修改成全局变量
bPaint=True #窗体要执行以下代码,线程中暂时规避对painter操作
painter0 = QPainter(self) #此QPainter只能有paintEvent中定义,不能定义成类的self成员对象,也不能在其地方(如其他窗口,线程中)定义,否则没有绘画功能显示
#绘制画布到窗口指定位置处
#painter0.begin(self)
pp0 = QPainter(self.pix)
painter0.drawPixmap(0, 0, self.pix)
#painter0.end()
bPaint=False
#鼠标按下事件重载
def mousePressEvent(self, event):
bDrawOK=True
print(f'当前鼠标压下坐标:x={event.pos().x()},y={event.pos().y()}')
# 鼠标左键按下
if event.button() == Qt.LeftButton:
self.lastPoint = event.pos()
self.endPoint = self.lastPoint
self.signal_clicked.emit(self.text()) #在DEMO标签上按下鼠标键后,发送此信号出去,主窗体接收此信号,调用对应定义的槽函数响应
print(' MyLabelDemo标签类的鼠标被按下,发出信号signal_clicked')
# 鼠标左键释放
def mouseReleaseEvent(self, event):
print(f'当前鼠标释放坐标:x={event.pos().x()},y={event.pos().y()}')
if event.button() == Qt.LeftButton:
self.endPoint = event.pos()
# 进行重新绘制
self.update()
bDrawOK=True
#分解要画出的文本到列表中
def splitDrawText(self,drawStr):
count = len(drawStr)
x=5 #文字绘制起始点坐标
y=30
row=0 #文字当前行数
rowMaxY=10 #本行文字中最大的字高
sumposX=x #绘制文本累计已占用当前行位置像素
sumposY=y
spaceX=3 #文字间横向间隔
spaceY=6 #文字竖向间隔
dpi=96
# 内容 字号 颜色 加粗 倾斜 下划线 删除线,文字矩形左上角x,y,文字矩形宽度,高度,对齐方式.....
# 0 1 2 3 4 5 6 7 8 9 10 11 12...
lst_one=['','宋体',16,'red',True,True,True,True,0,0,0,0,'AlignLeft']
if count>0:
self.lst_drawText.clear()
lst_str = list(drawStr) #按单字分解字符串到列表对象
for i in range(count):
lst_one[0]=lst_str[i]
lst_one[1]=self.lst_fontname[random.randint(0, 5)] #随机得到字体名称
#仅示例:对字体的x,y,w,h进行处理
lst_one[2]=random.randint(15, 24) #随机得到字号
lst_one[8]=sumposX
lst_one[9]=sumposY
lst_one[10]=self.points_to_pixels(lst_one[2],dpi)+spaceX #没有详细分析字号同屏幕像素及DPI的关系。。。
lst_one[11]=lst_one[10]
if(rowMaxY<lst_one[11]):
rowMaxY = lst_one[11]
lst_one[12]='AlignLeft'
sumposX=sumposX+lst_one[2]+spaceX
if(sumposX>770): #本画布宽度是800,故设成790换行,
row=row+1
sumposX=0
x=5
y=row*40+spaceY
sumposY=sumposY+rowMaxY+spaceY
if(i==2 or(i>=8 and i<=11)): #这几个是数字要处理
pass
elif(i>=4 and i<=7): #这几个是bool值要处理
pass
else:
pass
self.lst_drawText.append(copy.deepcopy(lst_one)) #必须用深度copy防止列表中的内容都是最后一个字的内容现象
print(self.lst_drawText)
#字号同像素点的转换
def points_to_pixels(self,points, dpi):
return points * dpi / 72.0
def pixels_to_points(self,pixels, dpi):
return pixels * 72.0 / dpi
#########################################################################################################################
#自定义线程类(继承QT的多线程类QtCore.QThread,不是PYTHON的线程类)
class ThreadClass(QtCore.QThread):
signal_ID = QtCore.pyqtSignal(int) #自定义线程中的信号,名称为signal_ID
ID=0
x0=400
y0=400
bRealseAll=False
def __init__(self,parent=None,index=0):
super(ThreadClass,self).__init__(parent)
self.index = index
self.is_running = True
self.ID=0
#重载开始线程对应的run函数:本例根据鼠标点击画板(标签控件)的次数来决定运行几个多线程DEMO
def run(self):
self.obj.bDrawOK=True
print(f'开始线程...:线程索引号:{self.index}')
self.is_running = True
while(True): #线程重复不断的循环来
if self.ID>99: self.ID=0 #ID对本代码无用,仅为信号槽传回整数作示例
self.signal_ID.emit(self.ID) #将本线程中的ID值(0-99)通过信号signal_ID槽发送,接收端通过
if(self.index==0): #线程0画以下DEM0代码
self.dem1_drawText() #画彩色文本
self.dem0_drawCircle() #画同心圆
self.dem1_RealseText(QColor(255,255,255)) #用白色画笔擦除彩色文本(擦除不是太干净待优化)
time.sleep(3)
self.obj.pix.fill(Qt.white) #全部擦除画板
elif(self.index==1): #线程1画以下DEM0代码,只能在一个线程中依次画,两个及以上线程同时画没成功??)
time.sleep(1)
elif(self.index==2): #线程2画以下DEM0代码
time.sleep(1)
#自行扩展对各线程的响应代码......
else:
time.sleep(1) #1000毫秒间隔
#多线程绘画DEMO1:参数bRealseAll表示调用前是否擦除全部画布内容
def dem0_drawCircle(self):
print('多线程0绘画DEM0:绘制同心圆')
#painter = QPainter(self.obj) #paniter对象只能在重绘事件中,在多线程中不会作用
pp = QPainter(self.obj.pix)
pencol = QColor(255,0,255)
pp.setPen(pencol)
pLeft=0
pRight=0
cx0=0
cy0=0
#绘制画布到窗口指定位置处:以下为在序号为0的线程中绘制一复杂图像代码
#先擦除窗口上的图(本例擦除全部)
#self.obj.pix = QPixmap(800,800)
R=30 #绘圆半径
count = 60 #第一圈绘60个,以后每圈翻倍
for j in range(12): #绘制的圈数
pencol=QColor(self.obj.lst_col[j])
pp.setPen(pencol)
for i in range(count*(j+1)): #每圈绘制的圆个数
time.sleep(0.01) #画每个圆的时间间隔为10毫秒
#计算每个圆的矩形左上角位置
dx0,dy0=self.getRectPoint(R*j,i,count*(j+1),R)
pp.drawEllipse(dx0,dy0,2*R,2*R)
self.obj.update() #刷新标签画板控件
self.ID+=self.ID #本线程绘的图形计数
self.obj.update() #刷新调用画板的重绘事件函数
#多线程绘画DEM1
def dem1_drawText(self):
print('多线程1绘画DEM1:绘制彩色文本')
#绘制画布到窗口指定位置处:以下为在序号为0的线程中绘制一复杂图像代码
#先擦除窗口上的图(本例擦除全部)
#self.obj.pix = QPixmap(800,800)
pp = QPainter(self.obj.pix)
pencol = QColor(255,0,255)
pp.setPen(pencol)
pLeft=0
pRight=0
cx0=0
cy0=0
self.ID=0
#self.obj.pix.fill(Qt.white)
for j in range(len(self.obj.lst_drawText)): #绘制的文字个数
time.sleep(0.3)
pencol=QColor(self.obj.lst_col[random.randint(0,12)]) #颜色随机并没有用字的列表中的属性,请自行完善
pp.setFont(QFont(self.obj.lst_drawText[j][1], int(self.obj.lst_drawText[j][2])))
pp.setPen(pencol)
#pp.drawText(self.obj.lst_drawText[j][0])
pp.drawText(QRect(self.obj.lst_drawText[j][8],self.obj.lst_drawText[j][9],self.obj.lst_drawText[j][10],self.obj.lst_drawText[j][11]), Qt.AlignLeft, self.obj.lst_drawText[j][0])
self.obj.update() #刷新标签画板控件
self.ID+=self.ID #本线程绘的图形计数
self.obj.update() #刷新调用画板的重绘事件函数
#将dem1_drawText画的文本擦除(默认采用白色擦除,即擦除色同画板背景色一致),擦除的不是太干净未处理。。。
def dem1_RealseText(self,pencol=QColor(255,255,255)):
print('多线程1绘画DEM1:绘制彩色文本')
#绘制画布到窗口指定位置处:以下为在序号为0的线程中绘制一复杂图像代码
#先擦除窗口上的图(本例擦除全部)
#self.obj.pix = QPixmap(800,800)
pp = QPainter(self.obj.pix)
pp.setPen(pencol)
pLeft=0
pRight=0
cx0=0
cy0=0
self.ID=0
#self.obj.pix.fill(Qt.white)
for j in range(len(self.obj.lst_drawText)): #绘制的文字个数
time.sleep(0.1)
pp.setFont(QFont(self.obj.lst_drawText[j][1], int(self.obj.lst_drawText[j][2])))
#pp.drawText(self.obj.lst_drawText[j][0])
pp.drawText(QRect(self.obj.lst_drawText[j][8],self.obj.lst_drawText[j][9],self.obj.lst_drawText[j][10],self.obj.lst_drawText[j][11]), Qt.AlignLeft, self.obj.lst_drawText[j][0])
self.obj.update() #刷新标签画板控件
self.ID+=self.ID #本线程绘的图形计数
self.obj.update() #刷新调用画板的重绘事件函数
#停止指定线程
def stop(self):
self.is_running=False
print('停止线程...',self.index)
self.terminate()
#根据圆心轨迹半径,序号,圆数量,圆半径计算矩形左上角坐标
def getRectPoint(sef,dR,index,count,r):
a=float(360/count)
ang1=math.radians(360/count)
curAng=ang1*index
rectLeft=0
rectTop=0
L=float(dR*math.sin(curAng))
H=float(dR*math.cos(curAng))
cx0=400+L
cy0=400-H
rectLeft=cx0-r
rectTop=cy0-r
return rectLeft,rectTop #返回画圆的左角坐标点
#线程中自定义函数供外部调用线程中的变量值
def getID(self):
return self.index,self.ID
#在线程中导入需要操作的对象
def setObj(self,frmobj):
self.obj = frmobj
if __name__=="__main__":
app = QApplication(sys.argv)
myWin = MyMainFrm()
#ta = TableDemo()
myWin.show()
sys.exit(app.exec())