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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章