PyQt5学习笔记(5)拖拽,绘图,自定义控件

参考:PyQt5 教程, 该文档翻译自PyQt5 tutorial

PyQt 拖拽

在这部分PyQt5教程中,我们将讨论拖拽相关操作。

在计算机图形用户界面中,拖放的操作(或支持的作用):点击虚拟对象和拖动到另一个位置或到另一个虚拟对象。

一般来说,它可以用于调用多种行动,或创建各种类型的两个抽象对象之间的关联。

拖放是图形用户界面的一部分。拖拽操作让用户直观地做复杂的事情。

通常,我们可以拖放:数据或一些图形对象。

  • 如果我们把一个图像从一个应用程序到另一个地方,我们拖拽二进制数据。
  • 如果我们把一个在Firefox中的标签移动到另一个地方,我们拖拽一个图形组件。

1.简单拖放

在这个例子中,我们有一个QLineEdit控件和一个QPushButton控件。

我们从单行文本编辑控件中将输入的文本选中后拖到按钮控件上后松开鼠标,按钮的标签将发生变化。

示例32:

# -*- coding: utf-8 -*-

import sys
from PyQt5.QtWidgets import (QPushButton, QWidget,
                             QLineEdit, QApplication)


class Button(QPushButton):
    # 需要重新实现某些方法才能使QPushButton接受拖放操作。
    # 因此我们创建了继承自QPushButton的Button类。
    def __init__(self, title, parent):
        super().__init__(title, parent)
        # 使该控件接受drop(放下)事件。
        self.setAcceptDrops(True)

    def dragEnterEvent(self, e):
        # 我们重新实现了dragEnterEvent()方法,
        # 并设置可接受的数据类型(在这里是普通文本)。
        if e.mimeData().hasFormat('text/plain'):
            e.accept()
        else:
            e.ignore()

    def dropEvent(self, e):
        # 通过重新实现dropEvent()方法,
        # 我们定义了在drop事件发生时的行为。这里我们改变了按钮的文字。
        self.setText(e.mimeData().text())


class Example(QWidget):
    def __init__(self):
        super().__init__()

        self.initUI()

    def initUI(self):
        edit = QLineEdit('', self)
        # QLineEdit内置了对drag(拖动)操作的支持。
        # 我们只需要调用setDragEnabled()方法就可以了。
        edit.setDragEnabled(True)
        edit.move(30, 65)

        button = Button("Button", self)
        button.move(190, 65)

        self.setWindowTitle('Simple drag & drop')
        self.setGeometry(300, 300, 300, 150)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    ex.show()
    app.exec_()  

2.拖放一个按钮

在下面的示例中我们将演示如何对一个按钮控件进行拖放。

示例33:

# -*- coding: utf-8 -*-

"""
PyQt5 tutorial 

This is a simple drag and
drop example.

author: py40.com
last edited: 2017年3月
"""
import sys
from PyQt5.QtWidgets import QPushButton, QWidget, QApplication
from PyQt5.QtCore import Qt, QMimeData
from PyQt5.QtGui import QDrag


class Button(QPushButton):
    def __init__(self, title, parent):
        super().__init__(title, parent)

    # 从QPushButton派生了一个Button类
    # 重新实现了mouseMoveEvent()与mousePressEvent()方法。
    # mouseMoveEvent()方法是拖放操作产生的地方。
    def mouseMoveEvent(self, e):
        # 只在鼠标右击时才执行拖放操作。鼠标左击用于按钮的点击事件。
        if e.buttons() != Qt.RightButton:
            return

        mimeData = QMimeData()

        # QDrag提供了对基于MIME的拖放的数据传输的支持。
        drag = QDrag(self)
        drag.setMimeData(mimeData)
        drag.setHotSpot(e.pos() - self.rect().topLeft())

        # Drag对象的exec_()方法用于启动拖放操作。
        drag.exec_(Qt.MoveAction)

    def mousePressEvent(self, e):

        QPushButton.mousePressEvent(self, e)

        if e.button() == Qt.LeftButton:
            # 在控制台打印 press
            # 调用了父按钮的mousePressEvent()方法。
            # 否则会看不到按钮的按下效果。
            print('press')


class Example(QWidget):
    def __init__(self):
        super().__init__()

        self.initUI()

    def initUI(self):
        self.setAcceptDrops(True)

        self.button = Button('Button', self)
        self.button.move(100, 65)

        self.setWindowTitle('Click or Move')
        self.setGeometry(300, 300, 280, 150)

    def dragEnterEvent(self, e):
        e.accept()

    def dropEvent(self, e):
        # 释放右键后调用dropEvent()方法中,
        # 找出鼠标指针的当前位置,并将按钮移动过去。
        position = e.pos()
        self.button.move(position)

        # 我们可以对指定的类型放弃行动。
        # 在我们的例子中它是一个移动动作。
        e.setDropAction(Qt.MoveAction)
        e.accept()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    ex.show()
    app.exec_()  

PyQt5绘图

PyQt5绘画系统能够呈现矢量图形,图像,和大纲font-based文本。我们也可以在程序中调用系统api自定义绘图控件。

绘图要在paintEvent()方法中实现。在QPainter对象的begin()与end()方法间编写绘图代码。它会在控件或其他图形设备上进行低级的图形绘制。

1.绘制文本

我们先以窗体内Unicode文本的绘制为例。

在我们的示例中,我们绘制一些Cylliric文本。文本垂直和水平对齐。

示例34:

# -*- coding: utf-8 -*-

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

class Example(QWidget):
    def __init__(self):
        super().__init__()

        self.initUI()

    def initUI(self):
        self.text = u'\u041b\u0435\u0432 \u041d\u0438\u043a\u043e\u043b\u0430\
\u0435\u0432\u0438\u0447 \u0422\u043e\u043b\u0441\u0442\u043e\u0439: \n\
\u0410\u043d\u043d\u0430 \u041a\u0430\u0440\u0435\u043d\u0438\u043d\u0430'

        self.setGeometry(300, 300, 280, 170)
        self.setWindowTitle('Draw text')
        self.show()

    # 绘制工作在paintEvent的方法内部完成。
    def paintEvent(self, event):
        # QPainter类负责所有的初级绘制。
        # 之间的所有绘画方法去start()和end()方法。
        # 实际的绘画被委托给drawText()方法。
        qp = QPainter()
        qp.begin(self)
        self.drawText(event, qp)
        qp.end()

    def drawText(self, event, qp):
        # 我们定义一个画笔和一个字体用于绘制文本。
        qp.setPen(QColor(168, 34, 3))
        qp.setFont(QFont('Decorative', 10))
        # drawText()方法将文本绘制在窗体,显示在中心
        qp.drawText(event.rect(), Qt.AlignCenter, self.text)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

2.画点

点是可以绘制的最简单的图形对象。

在这例子中,我们在窗口上随机绘制了1000个红点

示例35:

# -*- coding: utf-8 -*-

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


class Example(QWidget):
    def __init__(self):
        super().__init__()

        self.initUI()

    def initUI(self):
        self.setGeometry(300, 300, 280, 170)
        self.setWindowTitle('Points')
        self.show()

    def paintEvent(self, e):
        qp = QPainter()
        qp.begin(self)
        self.drawPoints(qp)
        qp.end()

    def drawPoints(self, qp):
        # 设置画笔为红色,我们使用了预定义的Qt.red常量
        qp.setPen(Qt.red)
        # 每次我们改变窗口的大小,生成一个 paint event 事件。
        # 我们得到的当前窗口的大小size。
        # 我们使用窗口的大小来分配点在窗口的客户区。
        size = self.size()

        for i in range(1000):
            x = random.randint(1, size.width() - 1)
            y = random.randint(1, size.height() - 1)
            # 通过drawpoint绘制圆点
            qp.drawPoint(x, y)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

3.颜色

颜色是一个对象代表红、绿、蓝(RGB)强度值。有效的RGB值的范围从0到255。

我们可以用不同的方法定义了一个颜色。最常见的是RGB十进制或十六进制值的值。我们也可以使用一个RGBA值代表红色,绿色,蓝色,透明度。

我们添加一些额外的信息透明度。透明度值255定义了完全不透明,0是完全透明的,例如颜色是无形的。

实例中我们绘制了3个不同颜色的矩形

示例36:

# -*- coding: utf-8 -*-

"""
PyQt5 tutorial

This example draws three rectangles in three
#different colours.

author: py40.com
last edited: 2017年3月
"""
import sys
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QColor


class Example(QWidget):
    def __init__(self):
        super().__init__()

        self.initUI()

    def initUI(self):
        self.setGeometry(300, 300, 350, 100)
        self.setWindowTitle('Colours')
        self.show()

    def paintEvent(self, e):
        qp = QPainter()
        qp.begin(self)
        self.drawRectangles(qp)
        qp.end()

    def drawRectangles(self, qp):
        col = QColor(0, 0, 0)
        # 定义一个使用十六进制符号颜色。
        col.setNamedColor('#d4d4d4')
        qp.setPen(col)

        # 为QPainter设置了一个笔刷(Bursh)对象并用它绘制了一个矩形。
        # 笔刷是用于绘制形状背景的基本图形对象。
        qp.setBrush(QColor(200, 0, 0))
        # drawRect()方法接受四个参数,
        # 前两个是起点的x,y坐标,后两个是矩形的宽和高。
        # 这个方法使用当前的画笔与笔刷对象进行绘制。
        qp.drawRect(10, 15, 90, 60)

        qp.setBrush(QColor(255, 80, 0, 160))
        qp.drawRect(130, 15, 90, 60)

        qp.setBrush(QColor(25, 0, 90, 200))
        qp.drawRect(250, 15, 90, 60)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

4.QPen(画笔)

QPen是一个基本的图形对象。用于绘制线条、曲线和轮廓的矩形、椭圆、多边形或其他形状。

示例中我们画六行线条勾勒出了六个不同的画笔风格。有五个预定义的钢笔样式。我们也可以创建自定义的钢笔样式。最后一行使用一个定制的钢笔绘制风格。

示例37:

# -*- coding: utf-8 -*-

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


class Example(QWidget):
    def __init__(self):
        super().__init__()

        self.initUI()

    def initUI(self):
        self.setGeometry(300, 300, 280, 270)
        self.setWindowTitle('Pen styles')
        self.show()

    def paintEvent(self, e):
        qp = QPainter()
        qp.begin(self)
        self.drawLines(qp)
        qp.end()

    def drawLines(self, qp):
        # 创建一个QPen对象。颜色是黑色的。
        # 宽度设置为5像素,这样我们可以看到笔风格之间的差异。
        # Qt.SolidLine是预定义的钢笔样式。
        pen = QPen(Qt.black, 5, Qt.SolidLine)

        qp.setPen(pen)
        qp.drawLine(20, 40, 250, 40)

        pen.setStyle(Qt.DashLine)
        qp.setPen(pen)
        qp.drawLine(20, 80, 250, 80)

        pen.setStyle(Qt.DashDotLine)
        qp.setPen(pen)
        qp.drawLine(20, 120, 250, 120)

        pen.setStyle(Qt.DotLine)
        qp.setPen(pen)
        qp.drawLine(20, 160, 250, 160)

        pen.setStyle(Qt.DashDotDotLine)
        qp.setPen(pen)
        qp.drawLine(20, 200, 250, 200)

        # 这里我们定义了一个画笔风格。
        # 我们设置了Qt.CustomDashLine并调用了setDashPattern()方法,
        # 它的参数(一个数字列表)定义了一种风格,必须有偶数个数字;
        # 其中奇数表示绘制实线,偶数表示留空。
        # 数值越大,直线或空白就越大。
        # 这里我们定义了1像素的实线,4像素的空白,5像素实线,4像素空白。。。
        pen.setStyle(Qt.CustomDashLine)
        pen.setDashPattern([1, 4, 5, 4])
        qp.setPen(pen)
        qp.drawLine(20, 240, 250, 240)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

5.QBrush(笔刷)

QBrush是一个基本的图形对象。它用于油漆的背景图形形状,如矩形、椭圆形或多边形。可以使用三种不同类型的笔刷:一个预定义的刷,一个梯度,或纹理模式。

示例中绘制九个不同的矩形

示例38:

# -*- coding: utf-8 -*-

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


class Example(QWidget):
    def __init__(self):
        super().__init__()

        self.initUI()

    def initUI(self):
        self.setGeometry(300, 300, 355, 280)
        self.setWindowTitle('Brushes')
        self.show()

    def paintEvent(self, e):
        qp = QPainter()
        qp.begin(self)
        self.drawBrushes(qp)
        qp.end()

    def drawBrushes(self, qp):
        # 我们定义了一个笔刷对象,
        # 然后将它设置给QPainter对象,
        # 并调用painter的drawRect()方法绘制矩形。
        brush = QBrush(Qt.SolidPattern)
        qp.setBrush(brush)
        qp.drawRect(10, 15, 90, 60)

        brush.setStyle(Qt.Dense1Pattern)
        qp.setBrush(brush)
        qp.drawRect(130, 15, 90, 60)

        brush.setStyle(Qt.Dense2Pattern)
        qp.setBrush(brush)
        qp.drawRect(250, 15, 90, 60)

        brush.setStyle(Qt.DiagCrossPattern)
        qp.setBrush(brush)
        qp.drawRect(10, 105, 90, 60)

        brush.setStyle(Qt.Dense5Pattern)
        qp.setBrush(brush)
        qp.drawRect(130, 105, 90, 60)

        brush.setStyle(Qt.Dense6Pattern)
        qp.setBrush(brush)
        qp.drawRect(250, 105, 90, 60)

        brush.setStyle(Qt.HorPattern)
        qp.setBrush(brush)
        qp.drawRect(10, 195, 90, 60)

        brush.setStyle(Qt.VerPattern)
        qp.setBrush(brush)
        qp.drawRect(130, 195, 90, 60)

        brush.setStyle(Qt.BDiagPattern)
        qp.setBrush(brush)
        qp.drawRect(250, 195, 90, 60)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

PyQt5自定义控件

PyQt5包含种类丰富的控件。但能满足所有需求的控件库是不存在的。通常控件库只提供了像按钮、文本控件、滑块等最常用的控件。但如果需要某种特殊的控件,我们只能自己动手来实现。 自定义控件需要使用工具库提供的绘图工具,可能有两种方式:在已有的控件上进行拓展或从头开始创建自定义控件。

示例39:Burning widget(烧录控件)

这个控件可能会在Nero,K3B或其他CD/DVD烧录软件中见到。在示例中我们使用了滑块与一个自定义控件。自定义控件受滑块控制。控件显示了媒体介质的容量和剩余空间。该控件的最小值为1,最大值为750。在值超过700时颜色变为红色。这通常意味着超刻(即实际写入光盘的容量超过刻录盘片官方标称容量的一种操作)。

# -*- coding: utf-8 -*-

import sys
from PyQt5.QtWidgets import (QWidget, QSlider, QApplication,
                             QHBoxLayout, QVBoxLayout)
from PyQt5.QtCore import QObject, Qt, pyqtSignal
from PyQt5.QtGui import QPainter, QFont, QColor, QPen


class Communicate(QObject):
    updateBW = pyqtSignal(int)


class BurningWidget(QWidget):
    # 烧录的控件,它基于QWidget
    def __init__(self):
        super().__init__()

        self.initUI()

    def initUI(self):
        # 改变了控件的最小大小(高度),默认值为有点小。
        self.setMinimumSize(1, 30)
        self.value = 75
        self.num = [75, 150, 225, 300, 375, 450, 525, 600, 675]

    def setValue(self, value):

        self.value = value

    def paintEvent(self, e):

        qp = QPainter()
        qp.begin(self)
        self.drawWidget(qp)
        qp.end()

    def drawWidget(self, qp):
        # 使用一个比默认要小的字体。
        font = QFont('Serif', 7, QFont.Light)
        qp.setFont(font)

        # 控件采用了动态绘制技术。窗体越大,控件也随之变大;反之亦然。
        # 这也是我们需要计算自定义控件的载体控件(即窗体)尺寸的原因。
        size = self.size()
        w = size.width()
        h = size.height()

        step = int(round(w / 10.0))

        # till参数定义了需要绘制的总尺寸,
        # 它根据slider控件计算得出,是整体区域的比例值。
        till = int(((w / 750.0) * self.value))
        # full参数定义了红色区域的绘制起点。
        # 注意在绘制时为取得较大精度而使用的浮点数运算。
        full = int(((w / 750.0) * 700))

        if self.value >= 700:

            qp.setPen(QColor(255, 255, 255))
            qp.setBrush(QColor(255, 255, 184))
            qp.drawRect(0, 0, full, h)
            qp.setPen(QColor(255, 175, 175))
            qp.setBrush(QColor(255, 175, 175))
            qp.drawRect(full, 0, till - full, h)

        else:

            qp.setPen(QColor(255, 255, 255))
            qp.setBrush(QColor(255, 255, 184))
            qp.drawRect(0, 0, till, h)

        pen = QPen(QColor(20, 20, 20), 1,
                   Qt.SolidLine)

        qp.setPen(pen)
        qp.setBrush(Qt.NoBrush)
        qp.drawRect(0, 0, w - 1, h - 1)

        j = 0

        for i in range(step, 10 * step, step):
            # 实际的绘制分三个步骤。
            # 黄色或红黄矩形的绘制,
            # 然后是刻度线的绘制,最后是刻度值的绘制。
            # 我们使用字体度量来绘制文本。
            # 我们必须知道文本的宽度,以中心垂直线。
            qp.drawLine(i, 0, i, 5)
            metrics = qp.fontMetrics()
            fw = metrics.width(str(self.num[j]))
            qp.drawText(i - int(fw / 2), int(h / 2), str(self.num[j]))
            j = j + 1


class Example(QWidget):
    def __init__(self):
        super().__init__()

        self.initUI()

    def initUI(self):
        sld = QSlider(Qt.Horizontal, self)
        sld.setFocusPolicy(Qt.NoFocus)
        sld.setRange(1, 750)
        sld.setValue(75)
        sld.setGeometry(30, 40, 150, 30)

        self.c = Communicate()
        self.wid = BurningWidget()
        self.c.updateBW[int].connect(self.wid.setValue)

        sld.valueChanged[int].connect(self.changeValue)
        hbox = QHBoxLayout()
        # BurningWidget控件通过QHBoxLayout与QVBoxLayout置于窗体的底部
        hbox.addWidget(self.wid)
        vbox = QVBoxLayout()
        vbox.addStretch(1)
        vbox.addLayout(hbox)
        self.setLayout(vbox)

        self.setGeometry(300, 300, 390, 210)
        self.setWindowTitle('Burning widget')
        self.show()

    def changeValue(self, value):
        # 当滑块发生移动时,changeValue()方法会被调用。
        # 在方法内我们触发了一个自定义的updateBW信号,
        # 其参数是当前滚动条的值。
        # 该值被用于计算Burning widget的容量值。然后对控件进行重绘。
        self.c.updateBW.emit(value)
        self.wid.repaint()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())
© Licensed under CC BY-NC-SA 4.0

你自己的代码如果超过6个月不看,再看的时候也一样像是别人写——伊格尔森定律

发表我的评论
取消评论
表情

Hi,您需要填写昵称和邮箱!