如何在pyqt4中添加箭头到我的行?

问题描述 投票:0回答:3

我得到了这个代码:

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 属性计算箭头两侧的点。 Badda-boom-badda-bing,你得到了箭头。

这是一些伪伪代码:

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中用多边形绘制箭头是不是更好的方法呢?

python pyqt pyqt4
3个回答
11
投票

我遇到了同样的问题,所以经过一番工作后我想出了这个。

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_width * 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

输出:

enter image description here


2
投票

@Art 的答案提供了一个绘制像 --> 这样的箭头的解决方案,受到他的代码的启发,我找到了一个解决方案来绘制像这样的箭头 ð。希望这对您有帮助。

from PyQt5 import QtWidgets, QtCore, QtGui
import math

#  draw an arrow like this
#                           |\
#                ___   _____| \
#    length_width |   |        \  _____
#                _|_  |_____   /    |
#                           | /     | arrow_width
#                           |/    __|__
#
#                           |<->|
#                        arrow_height    
class Arrow(QtWidgets.QGraphicsPathItem):
    def __init__(self, source: QtCore.QPointF, destination: QtCore.QPointF, arrow_height, arrow_width, length_width, *args, **kwargs):
        super(Arrow, self).__init__(*args, **kwargs)

        self._sourcePoint = source
        self._destinationPoint = destination

        self._arrow_height = arrow_height
        self._arrow_width = arrow_width
        self._length_width = length_width

    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
            
            # parallel vector (normX, normY)
            # perpendicular vector (perpX, perpY)
            perpX = -normY
            perpY = normX
            
            
            #           p2
            #           |\
            #    p4____p5 \
            #     |        \ endpoint
            #    p7____p6  /
            #           | /
            #           |/
            #          p3
            point2 = endPoint + QtCore.QPointF(normX, normY) * self._arrow_height + QtCore.QPointF(perpX, perpY) * self._arrow_width
            point3 = endPoint + QtCore.QPointF(normX, normY) * self._arrow_height - QtCore.QPointF(perpX, perpY) * self._arrow_width

            point4 = startPoint + QtCore.QPointF(perpX, perpY) * self._length_width
            point5 = endPoint + QtCore.QPointF(normX, normY) * self._arrow_height + QtCore.QPointF(perpX, perpY) * self._length_width
            point6 = endPoint + QtCore.QPointF(normX, normY) * self._arrow_height - QtCore.QPointF(perpX, perpY) * self._length_width
            point7 = startPoint - QtCore.QPointF(perpX, perpY) * self._length_width

            return QtGui.QPolygonF([point4, point5, point2, endPoint, point3, point6, point7])

        except (ZeroDivisionError, Exception):
            return None

    def paint(self, painter: QtGui.QPainter, option, widget=None) -> None:
        painter.setRenderHint(painter.Antialiasing)

        my_pen = QtGui.QPen()
        my_pen.setWidth(1)
        my_pen.setCosmetic(False)
        my_pen.setColor(QtGui.QColor(255, 0, 0, 100))
        painter.setPen(my_pen)

        arrow_polygon = self.arrowCalc()
        if arrow_polygon is not None:
            # painter.drawPolyline(arrow_polygon)
            painter.drawPolygon(arrow_polygon)

0
投票

@art 的 C++ 版本答案:

void drawArrow(QPainter *painter, const QPointF &p1, const QPointF &p2,
               float width) {
  const auto brush = painter->brush();
  painter->save();
  const auto kArrowWidth = width;
  const auto kArrowHeight = width;
  const float dx = p1.x() - p2.x();
  const float dy = p1.y() - p2.y();
  const float len = std::sqrt(std::pow(dx, 2) + std::pow(dy, 2));
  const float normalX = dx / len;
  const float normalY = dy / len;
  const float perpX = -normalY;
  const float perpY = normalX;
  const float leftX = p2.x() + kArrowHeight * normalX + kArrowWidth * perpX;
  const float leftY = p2.y() + kArrowHeight * normalY + kArrowWidth * perpY;
  const float rightX = p2.x() + kArrowHeight * normalX - kArrowWidth * perpX;
  const float rightY = p2.y() + kArrowHeight * normalY - kArrowWidth * perpY;
  const QPolygonF polygon({QPointF(leftX, leftY), p2, QPointF(rightX, rightY)});
  painter->setRenderHint(QPainter::Antialiasing, true);
  painter->setBrush(brush);
  painter->drawPolygon(polygon);
  painter->restore();
}
© www.soinside.com 2019 - 2024. All rights reserved.