如何在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中的行中添加箭头?的主要内容,如果未能解决你的问题,请参考以下文章

ExtJS 3:如何根据行中的值对不同的行定义不同的操作?

如何在动态创建的行中添加日期选择器

如何在 SED 或 AWK 的行中添加双引号?

Obiee 如何在报告中的行中引入指标?

如何在Oracle Apex交互式报表的行中添加更新按钮?

如何在javascript中使用从数据库返回的行中的数据