Qt學習筆記#9:畫一條帶箭頭的線


前言

帶箭頭的線,在很多地方都會用到,以致於一開始我認爲Qt會提供這樣一個類。。。沒想到的是Qt不僅沒有提供相關的類,自己實現的時候還頗爲複雜。。

其實我比較不理解。。爲什麼Qt不提供一個帶箭頭的線的類呢。。爲什麼爲什麼呢?這個應該不少人會用到吧。。。

廢話不多說,馬上開始。


方法1:常規實現

帶箭頭的線,簡單來講就是一條線加上一個三角形,我們可以通過自定義一個繼承自QGraphicsLineItem的類,並重寫他的paint()方法來實現:

1. 畫一條沒有箭頭的線

class MyWidget(QGraphicsView):
    def __init__(self):
        super(MyWidget, self).__init__()
        self.setFixedSize(300, 300)
        self.setSceneRect(0, 0, 250, 250)
        self.scene = QGraphicsScene()
        self.setScene(self.scene)
        self.scene.addItem(MyArrow())


class MyArrow(QGraphicsLineItem):
    def __init__(self):
        super(MyArrow, self).__init__()
        self.source = QPointF(0, 250)
        self.dest = QPointF(120, 120)
        self.line = QLineF(self.source, self.dest)
        self.line.setLength(self.line.length() - 20)

    def paint(self, QPainter, QStyleOptionGraphicsItem, QWidget_widget=None):
        # setPen
        pen = QPen()
        pen.setWidth(5)
        pen.setJoinStyle(Qt.MiterJoin) #讓箭頭變尖
        QPainter.setPen(pen)
        
		# draw line
        QPainter.drawLine(self.line)

if __name__ == '__main__':
    import sys

    app = QApplication(sys.argv)
    w = MyWidget()
    w.show()
    sys.exit(app.exec_())

效果如圖:

在這裏插入圖片描述


2. 在這條線的基礎上畫上箭頭

首先利用QLineF().unitVector()函數得到它的單位向量,並將它移到原線的終點位置,注意這裏的偏移量。

	v = self.line.unitVector()
    v.setLength(20) # 改變單位向量的大小,實際就是改變箭頭長度
    v.translate(QPointF(self.line.dx(), self.line.dy()))

這時候我們就得到這樣一條線:

在這裏插入圖片描述


然後我們利用normalVector()函數得到他的法向量,然後再利用normalVector()得到法向量的反方向的向量。

        n = v.normalVector() # 法向量
        n.setLength(n.length() * 0.5) # 這裏設定箭頭的寬度
        n2 = n.normalVector().normalVector() # 兩次法向量運算以後,就得到一個反向的法向量

在這裏插入圖片描述


然後我們取得 3 個向量的終點 爲箭頭的三個端點,並以這三點爲頂點畫出三角形

        p1 = v.p2()
        p2 = n.p2()
        p3 = n2.p2()        
        QPainter.drawPolygon(p1, p2, p3)

在這裏插入圖片描述


至此,箭頭就算完成了!
如果你喜歡,你還可以填充箭頭

        brush = QBrush()
        brush.setColor(Qt.black)
        brush.setStyle(Qt.SolidPattern)
        QPainter.setBrush(brush)

最後完成如下:

在這裏插入圖片描述在這裏插入圖片描述


方法2:利用QPainterPath實現

QPainterPath其實是一個容器,他可以包含一個或者多個不同的繪畫步驟,通過這些步驟組成較爲複雜的圖案,然後使用QPainter.drawPath()將這些圖案一次性畫出來。

實現的方式和普通方法的區別在於:

普通方法分兩步畫出圖形,先畫線,再畫箭頭

        QPainter.drawLine(self.line)
        QPainter.drawPolygon(p1, p2, p3)

而利用QPainterPath,則是先將整個繪製過程設置好,然後一次性畫出整個path

        arrow = QPolygonF([p1, p2, p3, p1])
        path = QPainterPath()
        path.moveTo(self.source) # 移動到線原點
	    path.lineTo(self.dest) # 添加線的路徑
        path.addPolygon(arrow) # 添加箭頭路徑
    
        QPainter.drawPath(path) # 畫出整個路徑

總結

  1. 箭頭相當於是這條線的額外部分,如果你對線的端點很敏感的話,要注意實際的長度 = 線原長 + 箭頭的長度。
  2. 三角形算是最簡單的圖形了,並沒有完全發揮QPainterPath的威力,事實上,你可以畫朵花兒在線上。

完整代碼

# coding:utf-8

from PyQt4.QtCore import *
from PyQt4.QtGui import *


class MyWidget(QGraphicsView):
    def __init__(self):
        super(MyWidget, self).__init__()
        self.setFixedSize(300, 300)
        self.setSceneRect(0, 0, 250, 250)
        self.scene = QGraphicsScene()
        self.setScene(self.scene)
        self.scene.addItem(MyArrow())


class MyArrow(QGraphicsLineItem):
    def __init__(self):
        super(MyArrow, self).__init__()
        self.source = QPointF(0, 250)
        self.dest = QPointF(120, 120)
        self.line = QLineF(self.source, self.dest)
        self.line.setLength(self.line.length() - 20)

    def paint(self, QPainter, QStyleOptionGraphicsItem, QWidget_widget=None):
        # setPen
        pen = QPen()
        pen.setWidth(5)
        pen.setJoinStyle(Qt.MiterJoin)
        QPainter.setPen(pen)

        # setBrush
        brush = QBrush()
        brush.setColor(Qt.black)
        brush.setStyle(Qt.SolidPattern)
        QPainter.setBrush(brush)

        v = self.line.unitVector()
        v.setLength(20)
        v.translate(QPointF(self.line.dx(), self.line.dy()))

        n = v.normalVector()
        n.setLength(n.length() * 0.5)
        n2 = n.normalVector().normalVector()

        p1 = v.p2()
        p2 = n.p2()
        p3 = n2.p2()
		
		# 方法1
        QPainter.drawLine(self.line)
        QPainter.drawPolygon(p1, p2, p3)

		# 方法2
        # arrow = QPolygonF([p1, p2, p3, p1])
        # path = QPainterPath()
        # path.moveTo(self.source)
        # path.lineTo(self.dest)
        # path.addPolygon(arrow)
        # QPainter.drawPath(path)


if __name__ == '__main__':
    import sys

    app = QApplication(sys.argv)
    w = MyWidget()
    w.show()
    sys.exit(app.exec_())
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章