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

本文介绍如何在Qt中创建一个自定义的矩形图元,该图元支持缩放和拖拽操作,同时在缩放时能够保持长宽比例一致。通过使用QGraphicsRectItem作为基类进行扩展,实现了一个名为MyGraphicsRectItem的类,该类还提供了拖动过程中的辅助显示和防止拖影的功能。
摘要由CSDN通过智能技术生成

直线的自定义子类见我之前的文章:Qt中直线的自定义子类(支持缩放、拖拽,父级场景无拖影)
支持缩放,支持按住shift键拖动鼠标时保持宽高相同。

#ifndef MYGRAPHICSRECTITEM_H
#define MYGRAPHICSRECTITEM_H

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

class MyGraphicsRectItem : public QGraphicsRectItem
{
public:
    enum { Type = UserType + 101 };
    enum MouseMode{MOVE, RESIZE};
    MyGraphicsRectItem(qreal x1, qreal y1, qreal x2, 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;
    QGraphicsRectItem *dashRect;

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

#endif // MYGRAPHICSRECTITEM_H

#include "mygraphicsrectitem.h"

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

void MyGraphicsRectItem::checkRectPointsAndPos(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);
    }
    if (y1 > y2) {
        // 同x,但直线不可这样处理,矩形、椭圆、弧形都必须这样处理,
        // 因为画图一定是左上往右下画,不像直线,可以左下往右上画!
        std::swap(y1, y2);
    }

    // 计算图形的位置坐标
    posX = x1; // 左边的那个点(无论在另一个点上方还是下方)是起点的x
    posY = y1; // 上面那个点的y值是起点的y

    // 计算相两个点对于pos的坐标位置
    x1 = 0;
    x2 = width;
    y1 = 0;
    y2 = height;
}

QRectF MyGraphicsRectItem::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 MyGraphicsRectItem::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);
    //qDebug("shape: %f,%f", width + margin * 2, height + margin * 2);
    return path;
}

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

    painter->setPen(pen());
    painter->setBrush(brush());
    painter->drawRect(rect());
    static int count = 0;
    count++;

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

void MyGraphicsRectItem::createResizeFocus()
{
    EResizeFocus *north_middle = new EResizeFocus(EResizeFocus::NORTH_MIDDLE, this);
    resizeFocus.append(north_middle);
    EResizeFocus *north_east = new EResizeFocus(EResizeFocus::NORTH_EAST, this);
    resizeFocus.append(north_east);
    EResizeFocus *north_west = new EResizeFocus(EResizeFocus::NORTH_WEST, this);
    resizeFocus.append(north_west);
    EResizeFocus *south_middle = new EResizeFocus(EResizeFocus::SOUTH_MIDDLE, this);
    resizeFocus.append(south_middle);
    EResizeFocus *south_east = new EResizeFocus(EResizeFocus::SOUTH_EAST, this);
    resizeFocus.append(south_east);
    EResizeFocus *south_west = new EResizeFocus(EResizeFocus::SOUTH_WEST, this);
    resizeFocus.append(south_west);
    EResizeFocus *east_middle = new EResizeFocus(EResizeFocus::EAST_MIDDLE, this);
    resizeFocus.append(east_middle);
    EResizeFocus *west_middle = new EResizeFocus(EResizeFocus::WEST_MIDDLE, this);
    resizeFocus.append(west_middle);
}

void MyGraphicsRectItem::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 MyGraphicsRectItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    QPointF posScene = event->scenePos();
    qDebug("posScene: %f, %f", posScene.x(), posScene.y());
    QTransform transform;
    transform.rotate(+0.0);
    curResizeFocus = qgraphicsitem_cast<EResizeFocus*>(scene()->itemAt(posScene, transform));
    if(curResizeFocus) {
        myMode = RESIZE;
        lastPoint = posScene;
        dashRect = new QGraphicsRectItem(rect(), this);
        dashRect->setPen(QPen(Qt::DashLine));
        QGraphicsRectItem::mousePressEvent(event);
    } else {
        myMode = MOVE;
        QGraphicsRectItem::mousePressEvent(event);
    }
}
void MyGraphicsRectItem::checkNewWidthHeight(QGraphicsSceneMouseEvent *event, qreal& width, qreal& height)
{
    if (event->modifiers() & Qt::ShiftModifier) {
        width = height = std::max(width, height);
    }
}
void MyGraphicsRectItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    if(myMode == MOVE) {
        return QGraphicsRectItem::mouseMoveEvent(event);
    }
    QPointF curPoint(event->scenePos());
    qreal widthMoving = curPoint.x() - lastPoint.x(),
            heightMoving = curPoint.y() - lastPoint.y();
    QRectF rect = this->rect();
    qreal x1 = rect.x();
    qreal y1 = rect.y();
    qreal newWidth = width;
    qreal newHeight = height;
    /**
     * 下面的各个点的移动,可以考虑当widthMoving,
     * heightMoving均大于0时,
     * posX, posY是图形左上角点对应的位置(所以画图的时候相对位置时左上角是原点,参数是0,0)
     * 到底posX, posY, width, height是增加了还是减少了。
     * 比如右上角,鼠标往右下角移动的时候,posY增加了,但height减少了,posX不变,且width也增加了。
     * 增加了就是加号,否则是减号。
     */
    EResizeFocus::PosInHost pos = curResizeFocus->getInHost();
    // 经过构造函数的处理,我们能确认:x1 <= x2, y1 <= y2(矩形嘛,更简单)
    // 所以下面就不必判断了,东影响x2,南影响y2,西影响x1,北影响y1
    switch(pos){
    case EResizeFocus::EAST_MIDDLE:
        // 正右边
        newWidth += widthMoving;
        break;
    case EResizeFocus::SOUTH_MIDDLE:
        // 正下方
        newHeight += heightMoving;
        break;
    case EResizeFocus::WEST_MIDDLE:
        // 正左边
        x1 += widthMoving;
        newWidth -= widthMoving;
        break;
    case EResizeFocus::NORTH_MIDDLE:
        // 正上方
        y1 += heightMoving;
        newHeight -= heightMoving;
        break;
    case EResizeFocus::NORTH_EAST:
        // 右上方
        y1 += heightMoving;
        newWidth += widthMoving;
        newHeight -= heightMoving;
        break;
    case EResizeFocus::NORTH_WEST:
        // 左上方
        x1 += widthMoving;
        y1 += heightMoving;
        newWidth -= widthMoving;
        newHeight -= heightMoving;
        break;
    case EResizeFocus::SOUTH_EAST:
        // 右下方
        newWidth += widthMoving;
        newHeight += heightMoving;
        break;
    case EResizeFocus::SOUTH_WEST:
        // 左下方
        x1 += widthMoving;
        newWidth -= widthMoving;
        newHeight += heightMoving;
        break;
    }
    if (newWidth < 0 || newHeight < 0) return;
    if(newWidth < 20 && newHeight <20) return; // !minimal size
    checkNewWidthHeight(event, newWidth, newHeight);
    dashRect->setRect(x1, y1, newWidth, newHeight);
}

void MyGraphicsRectItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    if(myMode == MOVE) {
        return QGraphicsRectItem::mouseReleaseEvent(event);
    } else {
        QRectF rectDash = dashRect->rect();
        qreal x1 = rectDash.x(),
                y1 = rectDash.y(),
                x2 = rectDash.bottomRight().x(),
                y2 = rectDash.bottomRight().y();
        qreal posX, posY;
        // 这里posX, posY计算出来的是一个相对图形的pos的坐标
        checkRectPointsAndPos(width, height, x1, y1, x2, y2, posX, posY);
        posX += pos().x();
        posY += pos().y();
        setPos(QPointF(posX, posY));
        setRect(x1, y1, width, height);
        delete dashRect;
    }
}

#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);
}

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值