多线程标签画板示例

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

  • 24
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mr_LuoWei2009

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值