前言
帶箭頭的線,在很多地方都會用到,以致於一開始我認爲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) # 畫出整個路徑
總結
- 箭頭相當於是這條線的額外部分,如果你對線的端點很敏感的話,要注意實際的長度 = 線原長 + 箭頭的長度。
- 三角形算是最簡單的圖形了,並沒有完全發揮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_())