查找QGraphicsItem的大纲

时间:2012-12-20 13:07:50

标签: c++ qt4 geometry qgraphicsitem

我有自己的类型为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 );
}

这画一个如下箭头:

enter image description here

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

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

我确信这很容易计算,但我的数学技能非常差 - 我尝试通过以下方式创建一条平行线:

  1. 存储线角。
  2. 将角度设置为0。
  3. 复制该行。
  4. 在副本上使用QLineF :: translate()。
  5. 将两个线条角度设置回您存储在1中的值 - 这会导致每条线条的开始和结束位置不对齐。
  6. 希望有人可以让我走上正确的轨道,从这条线创建一个厚QPolygonF(或任何其他有意义的东西),然后可以有一个轮廓和填充集来进行绘画。

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

    enter image description here

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

2 个答案:

答案 0 :(得分:1)

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

arrow.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

arrow.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);
}

答案 1 :(得分:0)

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

类ArrowItem(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