Qt中直线的自定义子类(支持缩放、拖拽,父级场景无拖影)

在QT编程中,我发现画线的处理最复杂了。如果只是用QPaint的drawLine来做,自然简单。可是,实际项目中会希望线段可以拖拽、缩放。而拖拽功能,有QGraphicsLineItem已经为我们实现了,所以,我们会用这个类来创建线段。可惜,这个类没有为我们实现缩放功能,网上找到文章:《QGraphicsItem选中后,出现边框,可以拉伸》,首先感谢这篇文章的作者“LiuZnHi”,帮我解决了拖拽的算法。
在按照上述文章的代码,整理出了我自己的代码,并改进了其中的问题。
我现在将代码贴出来,供大家参考。
因为直线的缩放其实没有必要,只要修改两端点的位置即可。另外,还支持了按住shift键缩放时会将形状按相等宽高处理。
备注:直线要支持拖拽改变大小,那么shape必须修改为一个矩形,否则线段的轮廓要考虑额外添加的小矩形,会很困难。
依赖的eresizefocus在最后面。

#ifndef MYGRAPHICSLINEITEM_H
#define MYGRAPHICSLINEITEM_H

#include <QGraphicsLineItem>
#include <QGraphicsRectItem>
#include <QFocusEvent>
#include <QList>
#include <QPointF>
#include <QPainter>
#include <QGraphicsScene>
#include <QStyleOptionGraphicsItem>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsView>
#include "eresizefocus.h"


class MyGraphicsLineItem : public QGraphicsLineItem
{
public:
    enum { Type = UserType + 101 };
    enum MouseMode{MOVE, RESIZE};
    MyGraphicsLineItem(qreal x1, qreal x2, qreal y1, qreal y2,
                       QGraphicsItem *parent = nullptr,
                       QGraphicsView* window = nullptr);

    virtual int type() const {return Type;}
    QRectF boundingRect() const;
    QPainterPath shape() const;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);

protected:
    void mousePressEvent(QGraphicsSceneMouseEvent *event);
    void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
    void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
    void showResizeFocus(bool visible);

private:
    QGraphicsView* parentWindow;
    MouseMode myMode;
    QColor myColor;
    QPointF lastPoint;
    qreal width;
    qreal height;
    qreal margin;

    QList<EResizeFocus*> resizeFocus;
    EResizeFocus *curResizeFocus;
    QGraphicsLineItem *dashLine;

    QPointF getToPoints(QGraphicsSceneMouseEvent *event, QPointF fromPoint); // 根据是否按了shift键,获取鼠标终点位置
    void checkLinePointsAndPos(qreal &width, qreal &height,
                           qreal& x1, qreal& y1,
                           qreal& x2, qreal& y2,
                           qreal& posX, qreal& posY);
    void createResizeFocus();
    void updateResizeFocus();
};

#endif // MYGRAPHICSLINEITEM_H

#include "mygraphicslineitem.h"

/**
 * @brief MyGraphicsLineItem 构造函数,继承于QGraphicsLineItem,但要扩展缩放。
 * @param x1 鼠标在画布上按下时确定的X点,距离画布左边的长度
 * @param y1 鼠标在画布上按下时确定的X点,距离画布顶端的长度
 * @param x2 鼠标在画布上松开时确定的X点,距离画布左边的长度
 * @param y2 鼠标在画布上松开时确定的Y点,距离画布顶端的长度
 * @param parent 父图形指针,可以为NULL。
 * @param window 父窗口指针,可以为NULL,我这里传入了父窗口指针,为了调用父窗口的update,解决拖影问题。
 */
MyGraphicsLineItem::MyGraphicsLineItem(qreal x1, qreal y1, qreal x2, qreal y2,
                                       QGraphicsItem *parent, QGraphicsView *window):
    QGraphicsLineItem(parent),
    parentWindow(window),
    margin(EResizeFocus::getXyWidth())
{
    qreal posX = x1, posY = y1;
    // 因为画线方式各异,要将起始位置确定,并确定线段两端点坐标的相对位置
    checkLinePointsAndPos(width, height, x1, y1, x2, y2, posX, posY);
    setLine(x1, y1, x2, y2);
    setPos(QPointF(posX, posY));
    setAcceptDrops(true);
    setAcceptHoverEvents(true);
    createResizeFocus();
}

void MyGraphicsLineItem::checkLinePointsAndPos(qreal& width, qreal& height,
                                           qreal& x1, qreal& y1,
                                           qreal& x2, qreal& y2,
                                           qreal& posX, qreal& posY)
{
    width = qAbs(x2 - x1);
    height = qAbs(y2 - y1);
    if (x1 > x2) {
        // 如果起点(鼠标按下)比终点(鼠标松开)更靠右,交换起点和终点
        // 交换后,x1,y1一定是左边那个点
        std::swap(x1, x2);
        std::swap(y1, y2);
    }
    // 计算图形的位置坐标
    posX = x1; // 左边的那个点(无论在另一个点上方还是下方)是起点的x
    posY = y1 <= y2 ? y1 : y2; // 上面那个点的y值是起点的y

    // 计算相两个点对于pos的坐标位置
    x1 = 0;
    x2 = width;
    y1 = y1 <= y2 ? 0 : height;
    y2 = y1 <=1e-6 ? height : 0; // y2和y1总有一个是height(注意浮点数判断为0的方法)
}

QRectF MyGraphicsLineItem::boundingRect() const
{
    qreal penWidth = pen().width();
    return QRectF(0 - penWidth / 2 - margin / 2, 0 - penWidth / 2 - margin / 2,
                  width + margin + penWidth, height + margin + penWidth);
}

QPainterPath MyGraphicsLineItem::shape() const
{
    QPainterPath path;
    qreal penWidth = pen().width();
    path.addRect(0 - penWidth / 2 - margin / 2, 0 - penWidth / 2 - margin / 2,
                 width + margin + penWidth, height + margin + penWidth);
    return path;
}

void MyGraphicsLineItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(widget)

    painter->setPen(pen());
    painter->drawLine(line());
    static int count = 0;
    count++;

    if(option->state & QStyle::State_Selected) {
        showResizeFocus(true);
    } else {
        showResizeFocus(false);
    }
    if (parentWindow) {
        parentWindow->update();
    }
}

void MyGraphicsLineItem::createResizeFocus()
{
    QLineF line = this->line();
    qreal y1 = line.y1(),
            y2 = line.y2();
    if (y1 >= y2) { // x1 <= x2是肯定成立的,在构造函数里已经调整好了
        EResizeFocus *north_east = new EResizeFocus(EResizeFocus::NORTH_EAST, this);
        resizeFocus.append(north_east);
        EResizeFocus *south_west = new EResizeFocus(EResizeFocus::SOUTH_WEST, this);
        resizeFocus.append(south_west);
    } else {
        EResizeFocus *north_west = new EResizeFocus(EResizeFocus::NORTH_WEST, this);
        resizeFocus.append(north_west);
        EResizeFocus *south_east = new EResizeFocus(EResizeFocus::SOUTH_EAST, this);
        resizeFocus.append(south_east);
    }
}

void MyGraphicsLineItem::showResizeFocus(bool visible)
{
    qreal penWidth = pen().width();
    for(int i = 0; i < resizeFocus.count(); i++) {
        resizeFocus.at(i)->locateInHost(penWidth);
        resizeFocus.at(i)->setVisible(visible);
    }
}

void MyGraphicsLineItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    QPointF posScene = event->scenePos();
    QTransform transform;
    transform.rotate(+0.0);
    curResizeFocus = qgraphicsitem_cast<EResizeFocus*>(scene()->itemAt(posScene, transform));
    if(curResizeFocus) {
        myMode = RESIZE;
        lastPoint = posScene;
        dashLine = new QGraphicsLineItem(line(), this);
        dashLine->setPen(QPen(Qt::DashLine));
        QGraphicsLineItem::mousePressEvent(event);
    } else {
        myMode = MOVE;
        QGraphicsLineItem::mousePressEvent(event);
    }
}
QPointF MyGraphicsLineItem::getToPoints(QGraphicsSceneMouseEvent *event, QPointF fromPoint)
{
    QPointF newPoint = event->scenePos() - this->pos();
    if (event->modifiers() & Qt::ShiftModifier) {
        qreal negativeFlagX = newPoint.x() - fromPoint.x() > 0 ? 1 : -1;
        qreal negativeFlagY = newPoint.y() - fromPoint.y() > 0 ? 1 : -1;
        qreal width = qAbs(newPoint.x() - fromPoint.x());
        qreal height = qAbs(newPoint.y() - fromPoint.y());
        qreal rate = qAbs(2 * (width - height) / (width + height));
        if (rate < 0.2) { // 45°
            qreal maxLength = std::max(width, height);
            newPoint = fromPoint + QPointF(negativeFlagX*maxLength, negativeFlagY*maxLength);
        } else {
            if (width > height) {
                newPoint = fromPoint + QPointF(negativeFlagX*width, 0);
            } else {
                newPoint = fromPoint + QPointF(0, negativeFlagY*height);
            }
        }
    }
    return newPoint;
}
void MyGraphicsLineItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    if(myMode == MOVE) {
        return QGraphicsLineItem::mouseMoveEvent(event);
    }
    EResizeFocus::PosInHost pos = curResizeFocus->getInHost();
    QPointF newPoint;
    if (pos == EResizeFocus::NORTH_EAST || pos == EResizeFocus::SOUTH_EAST) {
        newPoint = getToPoints(event, dashLine->line().p1());
        dashLine->setLine(dashLine->line().p1().x(), dashLine->line().p1().y(),
                          newPoint.x(), newPoint.y());
    } else {
        newPoint = getToPoints(event, this->line().p2());
        dashLine->setLine(newPoint.x(), newPoint.y(),
                          dashLine->line().p2().x(), dashLine->line().p2().y());
    }

}

void MyGraphicsLineItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    if(myMode == MOVE) {
        return QGraphicsLineItem::mouseReleaseEvent(event);
    } else {
        QLineF lineDash = dashLine->line();
        qreal x1 = lineDash.x1(), y1 = lineDash.y1(),
                x2 = lineDash.x2(), y2 = lineDash.y2();
        qreal posX, posY;
        // 这里posX, posY计算出来的是一个相对图形的pos的坐标
        checkLinePointsAndPos(width, height, x1, y1, x2, y2, posX, posY);
        posX += pos().x();
        posY += pos().y();
        setPos(QPointF(posX, posY));
        setLine(x1, y1, x2, y2);
        delete dashLine;
    }
}

#ifndef ERESIZEFOCUS_H
#define ERESIZEFOCUS_H

#include <QGraphicsRectItem>
#include <QCursor>

class EResizeFocus: public QGraphicsRectItem
{
public:
    enum PosInHost{NORTH_MIDDLE,NORTH_EAST,EAST_MIDDLE,SOUTH_EAST,SOUTH_MIDDLE,SOUTH_WEST,WEST_MIDDLE,NORTH_WEST};
    enum { Type = UserType + 1 };

    EResizeFocus(PosInHost pos, QGraphicsItem *parent);
    ~EResizeFocus();

    int type() const {return Type;}
    void setInHost(PosInHost pos){posInHost = pos;}
    PosInHost getInHost(){return posInHost;}

    void locateInHost(qreal penWidth = 1);
    static qreal getXyWidth(){return xyWidth;}

protected:
    void hoverEnterEvent ( QGraphicsSceneHoverEvent * event ) ;
    void hoverLeaveEvent ( QGraphicsSceneHoverEvent * event ) ;

private:
    PosInHost posInHost;
    static qreal xyWidth;
};

#endif // ERESIZEFOCUS_H

#include "eresizefocus.h"

qreal EResizeFocus::xyWidth = 6;

EResizeFocus::EResizeFocus(PosInHost pos, QGraphicsItem *parent) :
    QGraphicsRectItem(0, 0, xyWidth, xyWidth, parent),
    posInHost(pos)
{
    setAcceptHoverEvents(true);
    setVisible(false);
}

EResizeFocus::~EResizeFocus()
{
}

void EResizeFocus::hoverEnterEvent ( QGraphicsSceneHoverEvent * event )
{
    switch(posInHost){
    case NORTH_MIDDLE:
        setCursor(Qt::SizeVerCursor);
        break;
    case SOUTH_MIDDLE:
        setCursor(Qt::SizeVerCursor);
        break;
    case EAST_MIDDLE:
        setCursor(Qt::SizeHorCursor);
        break;
    case WEST_MIDDLE:
        setCursor(Qt::SizeHorCursor);
        break;
    case NORTH_WEST:
        setCursor(Qt::SizeFDiagCursor);
        break;
    case SOUTH_EAST:
        setCursor(Qt::SizeFDiagCursor);
        break;
    case NORTH_EAST:
        setCursor(Qt::SizeBDiagCursor);
        break;
    case SOUTH_WEST:
        setCursor(Qt::SizeBDiagCursor);
        break;
    }
    QGraphicsRectItem::hoverEnterEvent(event);
}

void EResizeFocus::hoverLeaveEvent ( QGraphicsSceneHoverEvent * event )
{
    QGraphicsRectItem::hoverLeaveEvent(event);
}

void EResizeFocus::locateInHost(qreal penWidth)
{
    const QRectF parentRect = this->parentItem()->boundingRect();
    const qreal parentWidth = parentRect.width() - xyWidth;
    const qreal parentHeight = parentRect.height() - xyWidth ;
    qreal x = 0, y = 0;

    switch(posInHost){
    case NORTH_MIDDLE:
        x = parentWidth / 2 - xyWidth / 2;
        y = -xyWidth / 2;
        break;
    case SOUTH_MIDDLE:
        x = parentWidth / 2 - xyWidth / 2;
        y = parentHeight - xyWidth / 2;
        break;
    case EAST_MIDDLE:
        x = parentWidth - xyWidth / 2;
        y = parentHeight / 2 - xyWidth / 2;
        break;
    case WEST_MIDDLE:
        x = -xyWidth / 2;
        y = parentHeight / 2 - xyWidth / 2;
        break;
    case NORTH_WEST:
        x = -xyWidth / 2;
        y = -xyWidth / 2;
        break;
    case SOUTH_EAST:
        x = parentWidth - xyWidth / 2;
        y = parentHeight - xyWidth / 2;
        break;
    case NORTH_EAST:
        x = parentWidth - xyWidth / 2;
        y = -xyWidth / 2;
        break;
    case SOUTH_WEST:
        x = -xyWidth / 2;
        y = parentHeight - xyWidth / 2;
        break;
    }
    setPos(x - penWidth / 2, y - penWidth / 2);
}

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值