如何在pyqt4中的行中添加箭头?
Posted
技术标签:
【中文标题】如何在pyqt4中的行中添加箭头?【英文标题】:How to add a arrow head to my line in pyqt4? 【发布时间】:2017-05-29 15:44:52 【问题描述】:我得到了这个代码:
from PyQt4 import QtGui, QtCore
class MyFrame(QtGui.QGraphicsView):
def __init__( self, parent = None ):
super(MyFrame, self).__init__(parent)
scene = QtGui.QGraphicsScene()
self.setScene(scene)
self.resize( 400, 240 )
# http://pyqt.sourceforge.net/Docs/PyQt4/qpen.html
pencil = QtGui.QPen( QtCore.Qt.black, 2)
pencil.setStyle( QtCore.Qt.SolidLine )
# pencil.setStyle( QtCore.Qt.UpArrow )
scene.addLine( QtCore.QLineF( 0, 0, 100, 100 ), pencil )
if ( __name__ == '__main__' ):
app = QtGui.QApplication([])
f = MyFrame()
f.show()
app.exec_()
哪个绘制了这个窗口:
当我用图像编辑器在最后一张图像上绘制这些图像时,如何在行的一端添加一个箭头:
我用这个伪代码找到了这个 C++ http://www.codeproject.com/Articles/3274/Drawing-Arrows 的教程:
// ARROWSTRUCT
//
// Defines the attributes of an arrow.
typedef struct tARROWSTRUCT
int nWidth; // width (in pixels) of the full base of the arrowhead
float fTheta; // angle (in radians) at the arrow tip between the two
// sides of the arrowhead
bool bFill; // flag indicating whether or not the arrowhead should be
// filled
ARROWSTRUCT;
// ArrowTo()
//
// Draws an arrow, using the current pen and brush, from the current position
// to the passed point using the attributes defined in the ARROWSTRUCT.
void ArrowTo(HDC hDC, int x, int y, ARROWSTRUCT *pArrow);
void ArrowTo(HDC hDC, const POINT *lpTo, ARROWSTRUCT *pArrow);
只需用所需属性填充 ARROWSTRUCT,确保当前 DC 位置正确(MoveTo() 等),然后调用两个 ArrowTo() 函数之一。尺寸参数(nWidth 和 fTheta)定义如下:
技术
这可以追溯到高中代数和三角学。 ArrowTo() 函数首先构建整行的向量。然后它根据您传递的 nWidth 和 fTheta 属性计算箭头两侧的点。棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒棒哒!
这是一些伪伪代码:
lineVector = toPoint - fromPoint
lineLength = length of lineVector
// calculate point at base of arrowhead
tPointOnLine = nWidth / (2 * (tanf(fTheta) / 2) * lineLength);
pointOnLine = toPoint + -tPointOnLine * lineVector
// calculate left and right points of arrowhead
normalVector = (-lineVector.y, lineVector.x)
tNormal = nWidth / (2 * lineLength)
leftPoint = pointOnLine + tNormal * normalVector
rightPoint = pointOnLine + -tNormal * normalVector
此外,我还可以找到另一个问题Drawing a polygon in PyQt,但它是针对 qt5 的。因此在pyqt4中用多边形绘制箭头是否更好?
【问题讨论】:
如果您已经有 2 个解决方案,您的问题是什么? 【参考方案1】:我遇到了同样的问题,所以经过一些工作我想出了这个。
import math, sys
from PyQt5 import QtWidgets, QtCore, QtGui
class Path(QtWidgets.QGraphicsPathItem):
def __init__(self, source: QtCore.QPointF = None, destination: QtCore.QPointF = None, *args, **kwargs):
super(Path, self).__init__(*args, **kwargs)
self._sourcePoint = source
self._destinationPoint = destination
self._arrow_height = 5
self._arrow_width = 4
def setSource(self, point: QtCore.QPointF):
self._sourcePoint = point
def setDestination(self, point: QtCore.QPointF):
self._destinationPoint = point
def directPath(self):
path = QtGui.QPainterPath(self._sourcePoint)
path.lineTo(self._destinationPoint)
return path
def arrowCalc(self, start_point=None, end_point=None): # calculates the point where the arrow should be drawn
try:
startPoint, endPoint = start_point, end_point
if start_point is None:
startPoint = self._sourcePoint
if endPoint is None:
endPoint = self._destinationPoint
dx, dy = startPoint.x() - endPoint.x(), startPoint.y() - endPoint.y()
leng = math.sqrt(dx ** 2 + dy ** 2)
normX, normY = dx / leng, dy / leng # normalize
# perpendicular vector
perpX = -normY
perpY = normX
leftX = endPoint.x() + self._arrow_height * normX + self._arrow_width * perpX
leftY = endPoint.y() + self._arrow_height * normY + self._arrow_width * perpY
rightX = endPoint.x() + self._arrow_height * normX - self._arrow_height * perpX
rightY = endPoint.y() + self._arrow_height * normY - self._arrow_width * perpY
point2 = QtCore.QPointF(leftX, leftY)
point3 = QtCore.QPointF(rightX, rightY)
return QtGui.QPolygonF([point2, endPoint, point3])
except (ZeroDivisionError, Exception):
return None
def paint(self, painter: QtGui.QPainter, option, widget=None) -> None:
painter.setRenderHint(painter.Antialiasing)
painter.pen().setWidth(2)
painter.setBrush(QtCore.Qt.NoBrush)
path = self.directPath()
painter.drawPath(path)
self.setPath(path)
triangle_source = self.arrowCalc(path.pointAtPercent(0.1), self._sourcePoint) # change path.PointAtPercent() value to move arrow on the line
if triangle_source is not None:
painter.drawPolyline(triangle_source)
class ViewPort(QtWidgets.QGraphicsView):
def __init__(self):
super(ViewPort, self).__init__()
self.setViewportUpdateMode(self.FullViewportUpdate)
self._isdrawingPath = False
self._current_path = None
def mousePressEvent(self, event: QtGui.QMouseEvent) -> None:
if event.button() == QtCore.Qt.LeftButton:
pos = self.mapToScene(event.pos())
self._isdrawingPath = True
self._current_path = Path(source=pos, destination=pos)
self.scene().addItem(self._current_path)
return
super(ViewPort, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
pos = self.mapToScene(event.pos())
if self._isdrawingPath:
self._current_path.setDestination(pos)
self.scene().update(self.sceneRect())
return
super(ViewPort, self).mouseMoveEvent(event)
def mouseReleaseEvent(self, event: QtGui.QMouseEvent) -> None:
pos = self.mapToScene(event.pos())
if self._isdrawingPath:
self._current_path.setDestination(pos)
self._isdrawingPath = False
self._current_path = None
self.scene().update(self.sceneRect())
return
super(ViewPort, self).mouseReleaseEvent(event)
def main():
app = QtWidgets.QApplication(sys.argv)
window = ViewPort()
scene = QtWidgets.QGraphicsScene()
window.setScene(scene)
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()
此代码适用于任何类型的路径,包括贝塞尔曲线、正方形等。如果要更改箭头位置,应将 path.PointAtPercent()
值更改为 0-1
之间的任意位置。例如,如果您想在行的中间绘制箭头,请使用self.arrowCalc(path.pointAtPercent(0.5), path.pointAtPercent(0.51))
。此外,当您将点传递给 arrowCalc
时,请确保源点和目标点靠近。
额外:
如果要测试正方形和贝塞尔路径(将直接路径方法替换为以下方法):
def squarePath(self):
s = self._sourcePoint
d = self._destinationPoint
mid_x = s.x() + ((d.x() - s.x()) * 0.5)
path = QtGui.QPainterPath(QtCore.QPointF(s.x(), s.y()))
path.lineTo(mid_x, s.y())
path.lineTo(mid_x, d.y())
path.lineTo(d.x(), d.y())
return path
def bezierPath(self):
s = self._sourcePoint
d = self._destinationPoint
source_x, source_y = s.x(), s.y()
destination_x, destination_y = d.x(), d.y()
dist = (d.x() - s.x()) * 0.5
cpx_s = +dist
cpx_d = -dist
cpy_s = 0
cpy_d = 0
if (s.x() > d.x()) or (s.x() < d.x()):
cpx_d *= -1
cpx_s *= -1
cpy_d = (
(source_y - destination_y) / math.fabs(
(source_y - destination_y) if (source_y - destination_y) != 0 else 0.00001
)
) * 150
cpy_s = (
(destination_y - source_y) / math.fabs(
(destination_y - source_y) if (destination_y - source_y) != 0 else 0.00001
)
) * 150
path = QtGui.QPainterPath(self._sourcePoint)
path.cubicTo(destination_x + cpx_d, destination_y + cpy_d, source_x + cpx_s, source_y + cpy_s,
destination_x, destination_y)
return path
输出:
【讨论】:
以上是关于如何在pyqt4中的行中添加箭头?的主要内容,如果未能解决你的问题,请参考以下文章