《快速掌握PyQt5》第三十一章 绘图与打印

第三十一章 绘图与打印

31.1 画笔、画刷和字体

31.2 利用双缓冲技术实现实时绘图

31.3 解决重影问题

31.4 打印

31.5 小结


《快速掌握PyQt5》专栏已整理成书出版,书名为《PyQt编程快速上手》,详情请见该链接。感谢大家一直以来的支持!祝大家PyQt用得越来越顺!

绘图操作主要通过QPainter类来进行,通过该类我们可以绘制许多种几何图形(点、线、矩形、椭圆、饼状图等等),当然也可以用来绘制图像和文字。本章首先来介绍QPainter类,之后再结合QPrinter类来了解下如何给程序加上打印功能。

31.1 画笔、画刷和字体

绘图操作通常在paintEvent()事件函数中完成。在该函数中,我们一般会先对画笔、画刷或者字体进行设置然后再调用相关方法进行绘制。

画笔用来画线和边缘,我们可以设置其颜色,线型和宽度等,下图是Qt提供的几种内置线型风格:

画刷用来对几何图形进行填充,我们同样可以设置其颜色和填充风格。下图是Qt提供的画刷风格:

可以看出最后个Qt::TexturePattern风格可以让我们刷出给定的图像。

字体用来绘制文字,我们可以设置其字体种类和大小,就是调用QPainter类的setFont()方法传入QFont类型参数(之前章节其实已经了解过该方法)

首先来了解下画笔QPen:

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPainter, QPen
from PyQt5.QtWidgets import QApplication, QWidget


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.resize(600, 600)

        self.pen1 = QPen()                      # 1
        self.pen1.setColor(Qt.green)
        self.pen2 = QPen(Qt.SolidLine)
        self.pen2.setWidth(6)                   # 2
        # self.pen2.setWidthF(3.3)
        self.pen3 = QPen(Qt.DashLine)
        self.pen4 = QPen(Qt.DotLine)
        self.pen5 = QPen(Qt.DashDotLine)
        self.pen6 = QPen(Qt.DashDotDotLine)
        self.pen7 = QPen(Qt.CustomDashLine)     # 3
        self.pen7.setDashPattern([6, 2, 18, 2])

        self.pen8 = QPen(Qt.SolidLine)          # 4
        self.pen8.setWidth(6)
        self.pen8.setCapStyle(Qt.RoundCap)

        self.pen9 = QPen(Qt.SolidLine)          # 5
        self.pen9.setWidthF(6)
        self.pen9.setJoinStyle(Qt.MiterJoin)

    def paintEvent(self, QPaintEvent):
        painter = QPainter(self)                # 6
        painter.setPen(self.pen1)
        painter.drawLine(100, 10, 500, 10)      

        painter.setPen(self.pen2)
        painter.drawLine(100, 30, 500, 30)

        painter.setPen(self.pen3)
        painter.drawLine(100, 50, 500, 50)

        painter.setPen(self.pen4)
        painter.drawLine(100, 70, 500, 70)

        painter.setPen(self.pen5)
        painter.drawLine(100, 90, 500, 90)

        painter.setPen(self.pen6)
        painter.drawLine(100, 110, 500, 110)

        painter.setPen(self.pen7)
        painter.drawLine(100, 130, 500, 130)

        painter.setPen(self.pen8)
        painter.drawLine(100, 150, 500, 150)

        painter.setPen(self.pen2)
        painter.drawRect(100, 170, 400, 200)    # 7    

        painter.setPen(self.pen9)
        painter.drawRect(100, 390, 400, 200)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

1. 实例化QPen对象,可直接传入画笔样式(默认为Qt.SolidLine),调用setColor()方法可设置画笔颜色;

2. 调用setWidth()方法传入整型值,可设置画笔粗细(默认为1)。如果要传入浮点型的话可使用setWidthF();

3. 使用Qt.CustomDashLine自定义样式的话,我们之后还需要调用setDashPattern()方法来设置虚线模式。只要传入一个迭代器即可,这里我们传入[6, 2, 18, 2]这个列表,意思是我们想将第一个虚线长度设为6个像素,再设置空白间隔长度为2个像素,之后再画一条长度为18像素的虚线,最后再加个长度为2像素的空白间隔,如此循环。

我们现在看下文档是怎么描述该方法的:

有两点我们还需要注意:

  • 虽然文档上说元素数量必须为偶数,但笔者传入有5个元素的列表(例如[6, 2, 18, 2, 13])发现其实也可以。小伙伴可以自己去看下PyQt5是怎么绘制此类模式的。
  • 虚线长度跟画笔宽度和笔端样式(后面会讲到)相关。若画笔宽度为10的话,那长度值5就代表50个像素。若笔端样式为Qt.SquareCap(默认值)的话,那每条虚线都其实会在每一端延伸出【画笔宽度/2】个像素值。pen7画笔的宽度为1,笔端样式为Qt.SquareCap。那也就是说[6, 2, 18, 2]中的数字6和18所设置的虚线宽度其实是7和19。当然,这里只是了解下,如果程序绘图不需要这么精确的话,小伙伴们可以不需要考虑这点。

4. 调用setCapStyle()设置笔端样式为Qt.RoundCap,一共有以下三种笔端样式:

5. 调用setJoinStyle()设置线条连接方式为Qt.MiterJoin,一共有以下三种:

6. 在paintEvent()事件函数中实例化一个QPainter对象,调用setPen()方法设置画笔,之后再调用drawLine()方法传入坐标就可以画出一条直线了。

7. 为了对比连接方式,我们用pen2和pen9各画了一个矩形(直线是没有连接处的)。

运行截图如下:

接下来是画刷(样式太多,笔者挑一些来讲):

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPainter, QBrush, QPixmap, QLinearGradient, QRadialGradient, QConicalGradient
from PyQt5.QtWidgets import QApplication, QWidget


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.resize(600, 600)

        self.brush1 = QBrush(Qt.SolidPattern)                   # 1

        self.brush2 = QBrush(Qt.Dense6Pattern)                  # 2
        self.brush2.setColor(Qt.red)

        gradient1 = QLinearGradient(200, 200, 300, 300)         # 3
        gradient1.setColorAt(0.2, Qt.red)
        gradient1.setColorAt(0.8, Qt.green)
        gradient1.setColorAt(1, Qt.blue)
        self.brush3 = QBrush(gradient1)

        gradient2 = QRadialGradient(350, 350, 50, 350, 350)     # 4
        gradient2.setColorAt(0, Qt.red)
        gradient2.setColorAt(1, Qt.blue)
        self.brush4 = QBrush(gradient2)

        gradient3 = QConicalGradient(450, 450, 90)              # 5
        gradient3.setColorAt(0, Qt.red)
        gradient3.setColorAt(1, Qt.blue)
        self.brush5 = QBrush(gradient3)

        self.brush6 = QBrush(Qt.TexturePattern)                 # 6
        self.brush6.setTexture(QPixmap('images/smile.png'))

    def paintEvent(self, QPaintEvent):
        painter = QPainter(self)
        painter.setBrush(self.brush1)                           # 7
        painter.drawRect(0, 0, 100, 100)

        painter.setBrush(self.brush2)
        painter.drawRect(100, 100, 100, 100)

        painter.setBrush(self.brush3)
        painter.drawRect(200, 200, 100, 100)

        painter.setBrush(self.brush4)
        painter.drawRect(300, 300, 100, 100)

        painter.setBrush(self.brush5)
        painter.drawRect(400, 400, 100, 100)

        painter.setBrush(self.brush6)
        painter.drawRect(500, 500, 100, 100)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

1. 直接传入画刷样式进行实例化,默认的样式为Qt.NoBrush而不是Qt.SolidPattern;

2. 同样可调用setColor()方法设置画刷颜色;

3. Qt一共提供三种渐变色样式,分别是线性渐变QLinearGradientPattern,径向渐变QRadialGradientPattern以及锥形渐变QConicalGradientPattern。要使用这三种渐变效果我们需要先实例化QLinearGradient,QRadialGradient或QConicalGradient对象,然后再实例化QBrush对象并传入相应的渐变类型。

我们在文档中查阅QLinearGradient发现其实例化所需要传入的参数为需要进行渐变的区域坐标,那其实只要把我们在paintEvent()事件函数中要绘制的矩形区域的坐标输入即可(输入两点坐标,若输入对角坐标,则在对角方向上渐变,笔者这里就是输入的左上和右下对角坐标):

setColorAt()方法传入需两个参数,第一个参数代表颜色开始渐变的位置(大小范围为0-1),第二个参数为颜色值。比如gradient1.setColorAt(0.2, Qt.red)的意思就是说红色在渐变区域0.2(即20%)处的位置开始渐变到下一种颜色。

4-5. 其余两种渐变类型设置其实类似,这里我们就主要讲下实例化时所传入的参数意义,首先是QRadialGradient(350, 350, 50, 350, 350),阅读文档我们很容易知道,前两个参数为中心点坐标,50为半径(渐变范围),后两个为焦点坐标:

而QConicalGradient(450, 450, 90)中前两个值为中心点坐标,最后个为首个颜色开始处的角度值(范围为0-360):

6. Qt.TexturePattern为纹理样式,我们可通过调用setTexture()方法传入QPixmap值就可以设置好纹理图案。其实调用该方法的话会自动把样式设置为Qt.TexturePattern,所以我们在实例化的时候可以不需要传入该样式值。

图片下载地址为smile.png: https://www.easyicon.net/download/png/1203584/24/

7. 在painEvent()事件函数中调用setBrush()方法就可以设置画刷了。

运行截图如下:

字体的话就简单讲一下:

import sys
from PyQt5.QtGui import QPainter, QFont
from PyQt5.QtWidgets import QApplication, QWidget


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.resize(600, 600)

    def paintEvent(self, QPaintEvent):
        painter = QPainter(self)
        painter.setFont(QFont('Times New Roman', 30))
        painter.drawText(100, 100, 'Hello PyQt5!')


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

首先调用setFont设置好字体,然后调用drawText()方法传入坐标值以及要绘制的文字即可。

以上使用drawText()只是许多重载方法中的一种,Qt一共提供了以下几种(红框中的是我们使用的):

如果要修改文字颜色的话只需要设置下画笔颜色就行,小伙伴们可以自己动手试下。

运行截图如下:

31.2 利用双缓冲技术实现实时绘图

所谓的双缓冲技术,简单来说就是我们先在一张画布上画好我们想要的,然后再将这张画布上的内容在控件(屏幕)上呈现出来。稍微专业点来说就是先建立一个临时缓冲区用于存储我们要画的内容,之后拷贝到图像显示缓冲区中进行显示。相比直接在控件上绘图,双缓冲技术可以有效减少绘图时所产生的闪烁问题,也可以让绘制速度变得更快。自Qt4开始,控件就自带双缓冲绘图功能来消除闪烁问题了,所以没有必要再在paintEvent()事件函数中再编写双缓冲代码。

虽然如此,但有时在绘制复杂图形或需要不断刷新重绘时,我们无法依赖Qt自身提供的双缓冲功能,还是要自己构写。

我们通过实例来了解下:

import sys
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import QApplication, QWidget


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.resize(600, 600)               

        self.begin_point = QPoint()                             # 1
        self.end_point = QPoint()

    def paintEvent(self, QPaintEvent):                          # 2
        painter = QPainter(self)
        painter.drawLine(self.begin_point, self.end_point)
        self.begin_point = self.end_point

    def mousePressEvent(self, QMouseEvent):
        if QMouseEvent.button() == Qt.LeftButton:
            self.begin_point = QMouseEvent.pos()
            self.end_point = self.begin_point
            self.update()                                       # 3

    def mouseMoveEvent(self, QMouseEvent):
        if QMouseEvent.buttons() == Qt.LeftButton:
            self.end_point = QMouseEvent.pos()
            self.update()                                       # 3


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

1. 所谓实时绘图也就是说鼠标左键开始被按下那刻,鼠标移动到哪里,画笔就画到哪里,直到左键被释放。既然涉及到鼠标,那肯定要用到鼠标事件了。self.begin_point和self.end_point两个QPoint()坐标实例就是用于记录鼠标位置的。在鼠标按下那刻,即mousePressEvent中用这两个变量保存鼠标开始处的位置;self.end_point还用于在鼠标移动中,即mouseMoveEvent中实时保存鼠标当前的位置。

注:mouseMoveEvent事件函数用的是buttons()方法来获取当前被按下的按钮。详细内容请查阅文档。

2. 在paintEvent()事件函数中通过调用drawLine()方法传入坐标来绘制,注意如果要画出随意的图案的话,一定要加上这行:self.begin_point = self.end_point

3. self.update()会让窗口重新调paintEvent()函数,这样鼠标的实时位置坐标才可以被不断传入paintEvent()函数中进行绘制。如果不加的话那paintEvent()只会在程序初始化时被调用一次,这样是不能让我们完成实时绘画的目的功能的。

此时运行程序,当鼠标左键被按下并移动时,发现只有一个点在跟着鼠标移动而移动,并没有画下鼠标的轨迹。

其实原因出在self.update()方法这里,当该方法被调用后,窗口会调用paintEvent()事件函数进行绘制,但在这之前,所有原先绘制的内容都会被清除!也就是说我们现在每次绘制的就是一个点而已。如果需要保留之前所绘制的内容,需要对窗口进行属性设置。我们在类的初始化函数中加入以下#1处代码即可(Qt文档搜索WA_OpaquePaintEvent可找到对该属性的解释):

class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.resize(600, 600)
        self.setAttribute(Qt.WA_OpaquePaintEvent)  # 1

现在运行,我们发现已经可以画画了:

但是其实是有问题的,在MacOS系统上我们每次小化窗口再显示时,所画的都会消失。

而以上代码在Windows和Linux(Ubuntu)系统中运行起来甚至还是下图这样的。一片漆黑,根本画不了:

这不是我们想要的,还是自己编写双缓冲代码,加个画布。代码修改如下:

import sys
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtGui import QPainter, QPixmap
from PyQt5.QtWidgets import QApplication, QWidget


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.resize(600, 600)

        self.begin_point = QPoint()
        self.end_point = QPoint()

        self.pix = QPixmap(600, 600)            # 1
        self.pix.fill(Qt.white)

    def paintEvent(self, QPaintEvent):          # 2
        painter = QPainter(self.pix)
        painter.drawLine(self.begin_point, self.end_point)
        self.begin_point = self.end_point
        painter2 = QPainter(self)
        painter2.drawPixmap(0, 0, self.pix)

    def mousePressEvent(self, QMouseEvent):
        if QMouseEvent.button() == Qt.LeftButton:
            self.begin_point = QMouseEvent.pos()
            self.end_point = self.begin_point
            self.update()

    def mouseMoveEvent(self, QMouseEvent):
        if QMouseEvent.buttons() == Qt.LeftButton:
            self.end_point = QMouseEvent.pos()
            self.update()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

1. 实例化一个QPixmap()类作为画布,大小跟窗口一样。再调用fill()放法传入Qt.white让画布变白。

2. 在paintEvent()事件函数中,我们先实例化一个以self.pix为绘画设备的QPainter实例,在这个画布上先画出自己想要的图案。再实例化一个以窗口为绘画设备的QPainter实例,然后调用drawPixmap()方法将self.pix画布一次性画在窗口(屏幕)上。

运行后我们发现已经可以正常作画了:

我们把画线改画矩形再来看下:

import sys
from PyQt5.QtCore import Qt, QPoint, QRect
from PyQt5.QtGui import QPainter, QPixmap
from PyQt5.QtWidgets import QApplication, QWidget


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.resize(600, 600)

        self.begin_point = QPoint()
        self.end_point = QPoint()

        self.pix = QPixmap(600, 600)
        self.pix.fill(Qt.white)

    def paintEvent(self, QPaintEvent):
        painter = QPainter(self.pix)
        rect = QRect(self.begin_point, self.end_point)  # 1
        painter.drawRect(rect)
        painter2 = QPainter(self)
        painter2.drawPixmap(0, 0, self.pix)

    def mousePressEvent(self, QMouseEvent):
        if QMouseEvent.button() == Qt.LeftButton:
            self.begin_point = QMouseEvent.pos()
            self.end_point = self.begin_point
            self.update()

    def mouseMoveEvent(self, QMouseEvent):
        if QMouseEvent.buttons() == Qt.LeftButton:
            self.end_point = QMouseEvent.pos()
            self.update()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

需要改的就是#1处的代码,我们实例化一个QRect对象传入鼠标起始点和结束点坐标。接着再调用drawRect()方法把这个QRect对象传进去就可以绘制了。

然而运行后发现会有很多重影,移动越慢重影越多(左边是移动慢的):

造成这样的原因是随着鼠标不断移动,self.update()不断被调用,也就是说paintEvent()事件函数在画布上不断的绘制矩形并被保留下来,接着再在窗口上被显示出来(这种代码逻辑对画线来说并没有什么影响),所以我们需要修改下代码,让程序能够在鼠标释放时只画出一个矩形来。

31.3 解决重影问题

解决思路就是:我们在鼠标移动时,让矩形直接绘制在窗口中(因为self.update()调用会清除窗口内容,可消除没确定好的矩形),而在鼠标释放时,才将我们确定好的矩形画在画布上。代码如下:

import sys
from PyQt5.QtCore import Qt, QPoint, QRect
from PyQt5.QtGui import QPainter, QPixmap
from PyQt5.QtWidgets import QApplication, QWidget


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.resize(600, 600)

        self.begin_point = QPoint()
        self.end_point = QPoint()

        self.pix = QPixmap(600, 600)
        self.pix.fill(Qt.white)

    def paintEvent(self, QPaintEvent):
        painter = QPainter(self)                                # 2
        painter.drawPixmap(0, 0, self.pix)                      

        if self.begin_point and self.end_point:                 # 3
            rect = QRect(self.begin_point, self.end_point)
            painter.drawRect(rect)

    def mousePressEvent(self, QMouseEvent):
        if QMouseEvent.button() == Qt.LeftButton:
            self.begin_point = QMouseEvent.pos()
            self.end_point = self.begin_point
            self.update()

    def mouseMoveEvent(self, QMouseEvent):
        if QMouseEvent.buttons() == Qt.LeftButton:
            self.end_point = QMouseEvent.pos()
            self.update()

    def mouseReleaseEvent(self, QMouseEvent):                   # 1
        if QMouseEvent.button() == Qt.LeftButton:
            painter = QPainter(self.pix)
            rect = QRect(self.begin_point, self.end_point)
            painter.drawRect(rect)
            self.begin_point = self.end_point = QPoint()
            self.update()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

1. 在mouseReleaseEvent()鼠标释放事件函数中,我们实例化一个以画布self.pix为绘图设备的QPainter对象,再将矩形绘制出来,此时鼠标已经释放,坐标都已经确定,所以只可能有一个矩形被绘制在画布上。释放后要设置self.begin_point和self.end_point变量为原点坐标(即设置为空)。

2. 在paintEvent()事件函数中,实例化以窗口为绘图设备的QPainter对象,再调用drawPixmap()方法将画布呈现在窗口上;

3. 首先要判断self.begin_point和self.end_point是否存在(即判断坐标是否都是(0, 0)),没有这句的话我们刚开始运行程序时会发现左上角被绘制了一个点。接着调用drawRect()方法绘制矩形就可以了(注:这里是直接绘制在窗口上)。

运行截图如下:

有些小伙伴可能不明白为什么在paintEvent()中要先写painter.drawPixmap(0, 0, self.pix),可不可以将这行代码放在最后,变成如下这样(好像还更符合逻辑一点):

def paintEvent(self, QPaintEvent):
    painter = QPainter(self)                     

    if self.begin_point and self.end_point:                 
        rect = QRect(self.begin_point, self.end_point)
        painter.drawRect(rect)
        
    painter.drawPixmap(0, 0, self.pix) 

我们不妨来模拟下这样写的话程序的运行逻辑:

  1. 首先程序初始化运行,调用paintEvent()绘制窗口,此时什么都是空白的。
  2. 接着鼠标按下,调用paintEvent(),此时self.begin_point和self.end_point坐标相同且不为空,painter.drawRect(rect)照理来说应该可以绘制出一个点。但是却还是一片空白,因为之后又马上调用了drawPixmap(0, 0, self.pix) ,画布还是空白的,所以屏幕上什么都没有。
  3. 鼠标移动,情况同上,还是一片空白。
  4. 鼠标释放,在mouseReleaseEvent()中,画布上多了一个矩形,所以在paintEvent()中painter.drawPixmap(0, 0, self.pix) 这行代码终于绘制了一个矩形。

这不是实时绘图,我们要的是在鼠标拖动过程中,矩形是随时可见的,所以必须要把painter.drawPixmap(0, 0, self.pix)这行代码放在前面。

31.4 打印

我们给上述画矩形的程序左上角加个按钮,当用户点击这个按钮时就可以打开打印设置框然后打印:

代码现修改如下,只需要实例化一个按钮并设置图标然后完成布局即可:

图片下载地址,printer.png: https://www.easyicon.net/download/png/40007/24/

import sys
from PyQt5.QtCore import Qt, QPoint, QRect
from PyQt5.QtGui import QPainter, QPixmap, QIcon
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QHBoxLayout, QVBoxLayout


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.resize(600, 600)

        self.begin_point = QPoint()
        self.end_point = QPoint()

        self.pix = QPixmap(600, 600)
        self.pix.fill(Qt.white)

        self.print_btn = QPushButton(self)              # 1
        self.print_btn.setIcon(QIcon('printer.png'))

        self.h_layout = QHBoxLayout()
        self.v_layout = QVBoxLayout()
        self.h_layout.addWidget(self.print_btn)
        self.h_layout.addStretch(1)
        self.v_layout.addLayout(self.h_layout)
        self.v_layout.addStretch(1)

        self.setLayout(self.v_layout)

    def paintEvent(self, QPaintEvent):
        painter = QPainter(self)                                
        painter.drawPixmap(0, 0, self.pix)

        if self.begin_point and self.end_point:                 
            rect = QRect(self.begin_point, self.end_point)
            painter.drawRect(rect)

    def mousePressEvent(self, QMouseEvent):
        if QMouseEvent.button() == Qt.LeftButton:
            self.begin_point = QMouseEvent.pos()
            self.end_point = self.begin_point
            self.update()

    def mouseMoveEvent(self, QMouseEvent):
        if QMouseEvent.buttons() == Qt.LeftButton:
            self.end_point = QMouseEvent.pos()
            self.update()

    def mouseReleaseEvent(self, QMouseEvent):                   
        if QMouseEvent.button() == Qt.LeftButton:
            painter = QPainter(self.pix)
            rect = QRect(self.begin_point, self.end_point)
            painter.drawRect(rect)
            self.begin_point = self.end_point = QPoint()
            self.update()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

接着我们要在类初始化函数中实例化一个用于打印的QPrinter对象,并将self.printer_btn按钮的信号和槽函数连接起来,分别是下面#1和#2处代码:

import sys
from PyQt5.QtCore import Qt, QPoint, QRect
from PyQt5.QtGui import QPainter, QPixmap, QIcon
from PyQt5.QtPrintSupport import QPrintDialog, QPrinter
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QHBoxLayout, QVBoxLayout


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.resize(600, 600)

        self.begin_point = QPoint()
        self.end_point = QPoint()

        self.pix = QPixmap(600, 600)
        self.pix.fill(Qt.white)

        self.printer = QPrinter()                               # 1

        self.print_btn = QPushButton(self)
        self.print_btn.setIcon(QIcon('printer.png'))
        self.print_btn.clicked.connect(self.open_printer_func)  # 2

        self.h_layout = QHBoxLayout()
        self.v_layout = QVBoxLayout()
        self.h_layout.addWidget(self.print_btn)
        self.h_layout.addStretch(1)
        self.v_layout.addLayout(self.h_layout)
        self.v_layout.addStretch(1)

        self.setLayout(self.v_layout)

槽函数实现如下:

def open_printer_func(self):
    printer_dialog = QPrintDialog(self.print_btn)
    if printer_dialog.exec_():
        painter = QPainter(self.printer)
        painter.drawPixmap(0, 0, self.pix)

首先传入self.printer实例化一个QPrintDialog打印设置对话框, 调用exec_()使之成为模态对话框,若成功打开,则传入self.printer作为绘制设备用于实例化一个QPainter对象,接着将要打印的内容绘制到self.printer上。

此时运行代码,我们先在窗口中随便画点什么,再点击打印按钮,显示如下:

此时再点击打印就可以了,当然打印前我们可以先预览下:

上述完整代码如下:

import sys
from PyQt5.QtCore import Qt, QPoint, QRect
from PyQt5.QtGui import QPainter, QPixmap, QIcon
from PyQt5.QtPrintSupport import QPrintDialog, QPrinter
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QHBoxLayout, QVBoxLayout


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.resize(600, 600)

        self.begin_point = QPoint()
        self.end_point = QPoint()

        self.pix = QPixmap(600, 600)
        self.pix.fill(Qt.white)

        self.printer = QPrinter()                               

        self.print_btn = QPushButton(self)
        self.print_btn.setIcon(QIcon('printer.png'))
        self.print_btn.clicked.connect(self.open_printer_func)  

        self.h_layout = QHBoxLayout()
        self.v_layout = QVBoxLayout()
        self.h_layout.addWidget(self.print_btn)
        self.h_layout.addStretch(1)
        self.v_layout.addLayout(self.h_layout)
        self.v_layout.addStretch(1)

        self.setLayout(self.v_layout)

    def open_printer_func(self):
        printer_dialog = QPrintDialog(self.printer)
        if printer_dialog.exec_():
            painter = QPainter(self.printer)
            painter.drawPixmap(0, 0, self.pix)

    def paintEvent(self, QPaintEvent):
        painter = QPainter(self)
        painter.drawPixmap(0, 0, self.pix)

        if self.begin_point and self.end_point:
            rect = QRect(self.begin_point, self.end_point)
            painter.drawRect(rect)

    def mousePressEvent(self, QMouseEvent):
        if QMouseEvent.button() == Qt.LeftButton:
            self.begin_point = QMouseEvent.pos()
            self.end_point = self.begin_point
            self.update()

    def mouseMoveEvent(self, QMouseEvent):
        if QMouseEvent.buttons() == Qt.LeftButton:
            self.end_point = QMouseEvent.pos()
            self.update()

    def mouseReleaseEvent(self, QMouseEvent):
        if QMouseEvent.button() == Qt.LeftButton:
            painter = QPainter(self.pix)
            rect = QRect(self.begin_point, self.end_point)
            painter.drawRect(rect)
            self.begin_point = self.end_point = QPoint()
            self.update()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

有些控件自带print()方法,直接传入QPrinter对象即可,所以我们不需要再配合QPainter类先进行绘制。下面有个实例可以看下:

import sys
from PyQt5.QtPrintSupport import QPrintDialog, QPrinter
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QTextEdit, QVBoxLayout


class Demo(QWidget):
    def __init__(self):
        super(Demo, self).__init__()
        self.text_edit = QTextEdit(self)
        self.print_btn = QPushButton('Print', self)
        self.print_btn.clicked.connect(self.open_printer_func)

        self.printer = QPrinter()

        self.v_layout = QVBoxLayout()
        self.v_layout.addWidget(self.text_edit)
        self.v_layout.addWidget(self.print_btn)
        self.setLayout(self.v_layout)

    def open_printer_func(self):
        printer_dialog = QPrintDialog(self.printer)
        if printer_dialog.exec_():
            self.text_edit.print(self.printer)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

运行截图如下:

31.5 小结

1. Qt在绘画方面的知识点远不止笔者上面讲的那些,更多的需要读者们自己去探索,多查阅文档。读者们可以自己来编写一个画图工具,做到切换画笔样式、粗细和颜色,也可以用来绘制各种形状;

2. 注意self.update()的用法,其实类似的还有一个repaint(),读者可以去文档中看下两者的区别;

3. 解决重影其实就是在鼠标点击和移动过程中将矩形绘制在窗口上,但是释放后会绘制到画布,并且再将画布绘制到窗口(屏幕)上,所以还是运用到了双缓冲技术的;

4. 用没有带print()方法的控件时,我们可以将QPainter和QPrinter结合起来完成打印功能;对于带print()方法的控件,我们直接调用并传入QPrinter对象即可实现打印功能。

  • 6
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

la_vie_est_belle

谢谢支持

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

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

打赏作者

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

抵扣说明:

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

余额充值