0

我有自己的 QGraphicsLineItem 类型的派生类,我在其中重写paint() 以将其呈现为箭头。

我的测试线是160、130、260、230

我的paint() 实现:

void MyQGraphicsLineItem::paint( QPainter* aPainter, const QStyleOptionGraphicsItem*     aOption, QWidget* aWidget /*= nullptr*/ )
{
Q_UNUSED( aWidget );

aPainter->setClipRect( aOption->exposedRect );

// Get the line and its angle
QLineF cLine = line();
const qreal cLineAngle = cLine.angle();

// Create two copies of the line
QLineF head1 = cLine;
QLineF head2 = cLine;

// Shorten each line and set its angle relative to the main lines angle
// this gives up the "arrow head" lines
head1.setLength( 12 );
head1.setAngle( cLineAngle+-32 );

head2.setLength( 12 );
head2.setAngle( cLineAngle+32 );

// Draw shaft
aPainter->setPen( QPen( Qt::black, 1, Qt::SolidLine ) );
aPainter->drawLine( cLine );

// Draw arrow head
aPainter->setPen( QPen( Qt::red, 1, Qt::SolidLine ) );
aPainter->drawLine( head1 );
aPainter->setPen( QPen( Qt::magenta, 1, Qt::SolidLine ) );
aPainter->drawLine( head2 );
}

这会绘制一个如下所示的箭头:

在此处输入图像描述

我想做的是能够计算这个项目的“轮廓”,这样我就可以从数据中绘制一个填充的 QPolygon。

我不能使用任何快捷方式,例如用不同的笔宽绘制两条线,因为我希望轮廓是动画的“虚线”(又名行军蚂蚁)。

我确信这很容易计算,但我的数学技能非常糟糕 - 我尝试通过执行以下操作来创建平行线:

  1. 存储线角度。
  2. 将角度设置为 0。
  3. 复制该行。
  4. 在副本上使用 QLineF::translate()。
  5. 将两条线的角度设置回您存储在 1 中的值 - 这会导致每条线的起点和终点位置错位。

希望有人能让我走上正确的轨道,从这条线创建一个厚的 QPolygonF(或其他任何有意义的东西),然后可以为绘画设置轮廓和填充。

此外,我计划在我的场景中拥有 1000 个这样的解决方案,因此理想情况下,我还想要一个不会花费太多执行时间或具有简单优化方法的解决方案。

在此处输入图像描述

这张图片是我想要实现的 - 想象一下红线是一条 qt 虚线,而不是我非常糟糕的 mspaint 尝试绘制它!

4

2 回答 2

1

即使稍后在场景中移动和旋转箭头,此解决方案也有效:

箭头.h

#ifndef ARROW_H
#define ARROW_H

#include <QGraphicsLineItem>
#include <QObject>
#include <QtCore/qmath.h>

class Arrow : public QGraphicsLineItem, public QObject
{
public:
    Arrow(qreal x1, qreal y1, qreal x2, qreal y2, QGraphicsItem* parent = 0);
    virtual ~Arrow();

    QPointF      objectEndPoint1();
    QPointF      objectEndPoint2();

    void setObjectEndPoint1(qreal x1, qreal y1);
    void setObjectEndPoint2(qreal x2, qreal y2);

protected:
    void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*);
    void timerEvent(QTimerEvent* event);

private:
    inline qreal pi() { return (qAtan(1.0)*4.0); }
    inline qreal radians(qreal degrees) { return (degrees*pi()/180.0); }

    void createArrow(qreal penWidth);

    QPainterPath arrowPath;
    QPainterPath strokePath;
    QPainterPath fillPath;

    int timerID_Anim;
    int animFrame;
    qreal animLength;
    QVector<qreal> dashPattern;
};

#endif

箭头.cpp

#include "arrow.h"
#include <QPen>
#include <QPainter>
#include <QTimerEvent>

Arrow::Arrow(qreal x1, qreal y1, qreal x2, qreal y2, QGraphicsItem* parent) : QGraphicsLineItem(0, 0, x2, y2, parent)
{
    setFlag(QGraphicsItem::ItemIsSelectable, true);

    setObjectEndPoint1(x1, y1);
    setObjectEndPoint2(x2, y2);

    qreal dashLength = 3;
    qreal dashSpace = 3;
    animLength = dashLength + dashSpace;
    dashPattern << dashLength << dashSpace;

    createArrow(1.0);

    animFrame = 0;
    timerID_Anim = startTimer(100);
}

Arrow::~Arrow()
{
}

void Arrow::timerEvent(QTimerEvent* event)
{
    if(event->timerId() == timerID_Anim)
    {
        animFrame++;
        if(animFrame >= animLength) animFrame = 0;
    }

    update(); //This forces a repaint, even if the mouse isn't moving
}

void Arrow::createArrow(qreal penWidth)
{
    QPen arrowPen = pen();
    arrowPen.setWidthF(penWidth);
    arrowPen.setDashPattern(dashPattern);
    setPen(arrowPen);

    QPointF p1 = line().p1();
    QPointF p2 = line().p2();
    qreal angle = line().angle();
    qreal arrowHeadAngle = 32.0;
    qreal length = line().length();
    qreal arrowHeadLength = length/10.0;
    QLineF arrowLine1(p1, p2);
    QLineF arrowLine2(p1, p2);
    arrowLine1.setAngle(angle + arrowHeadAngle);
    arrowLine2.setAngle(angle - arrowHeadAngle);
    arrowLine1.setLength(arrowHeadLength);
    arrowLine2.setLength(arrowHeadLength);

    QPainterPath linePath;
    linePath.moveTo(p1);
    linePath.lineTo(p2);
    QPainterPath arrowheadPath;
    arrowheadPath.moveTo(arrowLine1.p2());
    arrowheadPath.lineTo(p1);
    arrowheadPath.lineTo(arrowLine2.p2());
    arrowheadPath.lineTo(p1);
    arrowheadPath.lineTo(arrowLine1.p2());

    arrowPath = QPainterPath();
    arrowPath.addPath(linePath);
    arrowPath.addPath(arrowheadPath);
}

void Arrow::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/)
{
    QPen paintPen = pen();

    QPainterPathStroker stroker;
    stroker.setWidth(paintPen.widthF());
    stroker.setCapStyle(Qt::FlatCap);
    stroker.setJoinStyle(Qt::MiterJoin);

    strokePath = stroker.createStroke(arrowPath);
    strokePath = strokePath.simplified();

    stroker.setDashOffset(animFrame);
    stroker.setDashPattern(dashPattern);
    fillPath = stroker.createStroke(strokePath);

    paintPen.setDashOffset(animFrame);
    painter->fillPath(fillPath, QBrush(QColor(255,0,0)));
    painter->fillPath(strokePath, QBrush(QColor(0,255,0)));  
}

QPointF Arrow::objectEndPoint1()
{
    return scenePos();
}

QPointF Arrow::objectEndPoint2()
{
    QLineF lyne = line();
    qreal rot = radians(rotation());
    qreal cosRot = qCos(rot);
    qreal sinRot = qSin(rot);
    qreal x2 = lyne.x2();
    qreal y2 = lyne.y2();
    qreal rotEnd2X = x2*cosRot - y2*sinRot;
    qreal rotEnd2Y = x2*sinRot + y2*cosRot;

    return (scenePos() + QPointF(rotEnd2X, rotEnd2Y));
}

void Arrow::setObjectEndPoint1(qreal x1, qreal y1)
{
    QPointF endPt2 = objectEndPoint2();
    qreal x2 = endPt2.x();
    qreal y2 = endPt2.y();
    qreal dx = x2 - x1;
    qreal dy = y2 - y1;
    setRotation(0);
    setLine(0, 0, dx, dy);
    setPos(x1, y1);
}

void Arrow::setObjectEndPoint2(qreal x2, qreal y2)
{
    QPointF endPt1 = scenePos();
    qreal x1 = endPt1.x();
    qreal y1 = endPt1.y();
    qreal dx = x2 - x1;
    qreal dy = y2 - y1;
    setRotation(0);
    setLine(0, 0, dx, dy);
    setPos(x1, y1);
}
于 2013-05-06T13:01:53.703 回答
0

我几乎忘记了这个问题,这是我的 PyQt 解决方案,我不确定是否有任何方法可以提高它的性能。

类箭头项(QGraphicsLineItem):

def __init__(self,  x, y , w, h,  parent = None):
    super(ArrowItem, self).__init__( x, y, w, h,  parent)
    self.init()

def paint(self, painter, option, widget):
    painter.setClipRect( option.exposedRect )
    painter.setBrush( Qt.yellow )

    if self.isSelected():
        p = QPen( Qt.red, 2, Qt.DashLine )
        painter.setPen( p )
    else:
        p = QPen( Qt.black, 2, Qt.SolidLine )
        p.setJoinStyle( Qt.RoundJoin )
        painter.setPen( p )

    painter.drawPath( self.shape() )

def shape(self):
    # Calc arrow head lines based on the angle of the current line
    cLine = self.line()

    kArrowHeadLength = 13
    kArrowHeadAngle = 32

    cLineAngle = cLine.angle()
    head1 = QLineF(cLine)
    head2 = QLineF(cLine)
    head1.setLength( kArrowHeadLength )
    head1.setAngle( cLineAngle+-kArrowHeadAngle )
    head2.setLength( kArrowHeadLength )
    head2.setAngle( cLineAngle+kArrowHeadAngle )

    # Create paths for each section of the arrow
    mainLine = QPainterPath()
    mainLine.moveTo( cLine.p2() )
    mainLine.lineTo( cLine.p1() )

    headLine1 = QPainterPath()
    headLine1.moveTo( cLine.p1() )
    headLine1.lineTo( head1.p2() )

    headLine2 = QPainterPath()
    headLine2.moveTo( cLine.p1() )
    headLine2.lineTo( head2.p2() )

    stroker = QPainterPathStroker()
    stroker.setWidth( 4 )

    # Join them together
    stroke = stroker.createStroke( mainLine )
    stroke.addPath( stroker.createStroke( headLine1 ) )
    stroke.addPath( stroker.createStroke( headLine2 ) )

    return stroke.simplified()

def boundingRect(self):
    pPath = self.shape()
    bRect = pPath.controlPointRect()
    adjusted = QRectF( bRect.x()-1, bRect.y()-1, bRect.width()+2, bRect.height()+2 )
    return adjusted

..当然将项目设置为可移动/可选择。

所以你可以看到获得“轮廓”所需的类是 QPainterPathStroker。

http://doc.qt.io/qt-5/qpainterpathstroker.html#details

于 2013-05-06T17:43:08.013 回答