可调整鼠标大小的可拖动小部件

艾克桑·哈克维迪利(Ayxan Haqverdili)

我正在尝试允许用户向一种设计区域添加新的“小部件”(图像,文本,也许还有其他自定义数据。图像目前已经足够好了)。然后,我希望他们能够方便地调整/移动它们的大小。移动部分的最佳方法似乎是使用QGraphicsView可以用4行代码很好地完成:

auto const scene = new QGraphicsScene{this};
auto const item = scene->addPixmap(QPixmap{":/img/example.png"});
item->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
ui->graphicsView->setScene(scene);

结果是这样的:

在此处输入图片说明

很好,但是不能用鼠标调整大小。我已经(在此站点上)看到了多种方法来实现这一目标,并且可以通过鼠标对其进行调整大小,但是它们都具有一定的缺陷。

在Qt网站上看到了这个示例,示例采用了一种不同的方法来为可移动和可调整大小的容器创建自定义容器。似乎可以稍作调整使它工作,但这又不是一个很好的解决方案。该小部件实际上看起来好像不可调整大小。选中时,边界没有很好的线索,这是一个动态放置/大小的东西。

拥有像ms-paint这样的可移动小部件必须是一个常见的用例,因此我认为必须有一种很好的方法来实现这一目标。是这样吗 QGraphicsScene老实说,似乎是个不错的候选人。也许我缺少什么?

小艾德里尔

好的,所以我遇到了这个问题,我的解决方案是将处理程序的创建与项目的选择联系起来:

主窗口

#pragma once

#include <QMainWindow>
#include <QGraphicsItem>
#include <QPainter>

class Handler: public QGraphicsItem
{
public:
    enum Mode
    {
        Top         = 0x1,
        Bottom      = 0x2,
        Left        = 0x4,
        TopLeft     = Top | Left,
        BottomLeft  = Bottom | Left,
        Right       = 0x8,
        TopRight    = Top | Right,
        BottomRight = Bottom | Right,
        Rotate      = 0x10
    };

    Handler(QGraphicsItem *parent, Mode mode);
    ~Handler(){}
    void updatePosition();

    QRectF boundingRect() const override;
protected:
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
    QPointF iniPos;
    void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
    void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
    void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
private:
    Mode mode;
    bool isMoving = false;
};

class ObjectResizerGrip: public QGraphicsItem
{
public:
    ObjectResizerGrip(QGraphicsItem *parent): QGraphicsItem(parent)
    {
        setFlag(QGraphicsItem::ItemHasNoContents, true);
        setFlag(QGraphicsItem::ItemIsSelectable, false);
        setFlag(QGraphicsItem::ItemIsFocusable, false);
    }
    void updateHandlerPositions();
    virtual QRectF boundingRect() const override;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override{Q_UNUSED(painter) Q_UNUSED(option) Q_UNUSED(widget)}

protected:
    QList<Handler*> handlers;
};

class Object4SidesResizerGrip: public ObjectResizerGrip
{
public:
    Object4SidesResizerGrip(QGraphicsItem *parent);
};

class Item:public QGraphicsItem
{
public:
    Item(QGraphicsItem *parent=nullptr): QGraphicsItem(parent)
    {
        setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
        setAcceptHoverEvents(true);
    }
    QRectF boundingRect() const override
    {
        return boundingBox;
    }
    void setWidth(qreal value)
    {
        auto width = boundingBox.width();
        if(width == value) return;
        width = qMax(value, 100.0);
        setDimensions(width, boundingBox.height());
    }

    void setHeight(qreal value)
    {
        auto height = boundingBox.height();
        if(height == value) return;
        height = qMax(value, 100.0);
        setDimensions(boundingBox.width(), height);
    }

    void setDimensions(qreal w, qreal h)
    {
        prepareGeometryChange();
        boundingBox = QRectF(-w/2.0, -h/2.0, w, h);
        if(resizerGrip) resizerGrip->updateHandlerPositions();
        update();
    }

private:
    ObjectResizerGrip* resizerGrip = nullptr;

    QVariant itemChange(GraphicsItemChange change, const QVariant &value) override
    {
        if(change == ItemSelectedHasChanged && scene())
        {
            if(value.toBool())
            {
                if(!resizerGrip)
                    resizerGrip = newSelectionGrip();
            }
            else
            {
                if(resizerGrip)
                {
                    delete resizerGrip;
                    resizerGrip = nullptr;
                }
            }
        }

        return QGraphicsItem::itemChange(change, value);
    }
    QRectF boundingBox;
    virtual ObjectResizerGrip *newSelectionGrip() =0;
};

class CrossItem:public Item
{
public:
    CrossItem(QGraphicsItem *parent=nullptr): Item(parent){};

private:
    virtual ObjectResizerGrip *newSelectionGrip() override
    {
        return new Object4SidesResizerGrip(this);
    }

    virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override
    {
        painter->drawLine(boundingRect().topLeft(), boundingRect().bottomRight());
        painter->drawLine(boundingRect().topRight(), boundingRect().bottomLeft());
    }
};


class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
};

主窗口

#include "mainwindow.h"
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QApplication>
#include <QGraphicsSceneMouseEvent>
#include <QHBoxLayout>
// Return nearest point along the line to a given point
// http://stackoverflow.com/questions/1459368/snap-point-to-a-line
QPointF getClosestPoint(const QPointF &vertexA, const QPointF &vertexB, const QPointF &point, const bool segmentClamp)
{
    QPointF AP = point - vertexA;
    QPointF AB = vertexB - vertexA;
    qreal ab2 = AB.x()*AB.x() + AB.y()*AB.y();
    if(ab2 == 0) // Line lenth == 0
        return vertexA;
    qreal ap_ab = AP.x()*AB.x() + AP.y()*AB.y();
    qreal t = ap_ab / ab2;
    if (segmentClamp)
    {
         if (t < 0.0f) t = 0.0f;
         else if (t > 1.0f) t = 1.0f;
    }
    return vertexA + AB * t;
}

Object4SidesResizerGrip::Object4SidesResizerGrip(QGraphicsItem* parent) : ObjectResizerGrip(parent)
{
    handlers.append(new Handler(this, Handler::Left));
    handlers.append(new Handler(this, Handler::BottomLeft));
    handlers.append(new Handler(this, Handler::Bottom));
    handlers.append(new Handler(this, Handler::BottomRight));
    handlers.append(new Handler(this, Handler::Right));
    handlers.append(new Handler(this, Handler::TopRight));
    handlers.append(new Handler(this, Handler::Top));
    handlers.append(new Handler(this, Handler::TopLeft));
    handlers.append(new Handler(this, Handler::Rotate));
    updateHandlerPositions();
}

QRectF ObjectResizerGrip::boundingRect() const
{
    return QRectF();
}

void ObjectResizerGrip::updateHandlerPositions()
{
    foreach (Handler* item, handlers)
        item->updatePosition();
}

Handler::Handler(QGraphicsItem *parent, Mode mode): QGraphicsItem(parent), mode(mode)
{
    QPen pen(Qt::white);
    pen.setWidth(0);
    setFlag(QGraphicsItem::ItemIsMovable, true);
    setFlag(QGraphicsItem::ItemIsSelectable, false);

    setAcceptHoverEvents(true);
    setZValue(100);
    setCursor(Qt::UpArrowCursor);
    updatePosition();
}

void Handler::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    QPen pen(isMoving ? QColor(250,214,36) : QColor(100,100,100));
    pen.setWidth(0);
    pen.setBrush(pen.color());
    painter->setPen(pen);
    painter->setBrush(QColor(100,100,100,150));
    if(mode & Rotate)
    {
        auto rect_ = ((Item*) parentItem()->parentItem())->boundingRect();
        auto topPos = QPointF(rect_.left() + rect_.width() / 2 - 1, rect_.top());
        painter->drawLine(mapFromParent(topPos), mapFromParent(topPos - QPointF(0, 175)));
        painter->drawEllipse(boundingRect());
    }
    else
        painter->drawRect(boundingRect());
}

QRectF Handler::boundingRect() const
{
    return QRectF(-25, -25, 50, 50);
}

void Handler::updatePosition()
{
    auto rect_ = ((Item*) parentItem()->parentItem())->boundingRect();
    switch (mode)
    {
        case TopLeft:
            setPos(rect_.topLeft());
            break;
        case Top:
            setPos(rect_.left() + rect_.width() / 2 - 1,rect_.top());
            break;
        case TopRight:
            setPos(rect_.topRight());
            break;
        case Right:
            setPos(rect_.right(),rect_.top() + rect_.height() / 2 - 1);
            break;
        case BottomRight:
            setPos(rect_.bottomRight());
            break;
        case Bottom:
            setPos(rect_.left() + rect_.width() / 2 - 1,rect_.bottom());
            break;
        case BottomLeft:
            setPos(rect_.bottomLeft());
            break;
        case Left:
            setPos(rect_.left(), rect_.top() + rect_.height() / 2 - 1);
            break;
        case Rotate:
            setPos(0, rect_.top() - 200);
            break;
    }
}

void Handler::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    if(mode & Rotate)
    {
        Item* item = (Item*) parentItem()->parentItem();
        auto angle =  QLineF(item->mapToScene(QPoint()), event->scenePos()).angle();
        if(!(QApplication::keyboardModifiers() & Qt::AltModifier))  // snap to 45deg
        {
            auto modAngle = fmod(angle+180, 45);
            if(modAngle < 10 || modAngle > 35)
                angle = round(angle/45)*45;
        }
        item->setRotation(0);
        angle = QLineF(item->mapFromScene(QPoint()), item->mapFromScene(QLineF::fromPolar(10, angle).p2())).angle();
        item->setRotation(90 - angle);
        item->update();
    }
    else
    {
        Item* item = (Item*) parentItem()->parentItem();
        auto diff = mapToItem(item, event->pos()) - mapToItem(item, event->lastPos());
        auto bRect = item->boundingRect();
        if(mode == TopLeft || mode == BottomRight)
            diff = getClosestPoint(bRect.topLeft(), QPoint(0,0), diff, false);
        else if(mode == TopRight || mode == BottomLeft)
            diff = getClosestPoint(bRect.bottomLeft(), QPoint(0,0), diff, false);

        if(mode & Left || mode & Right)
        {
            item->setPos(item->mapToScene(QPointF(diff.x()/2.0, 0)));
            if(mode & Left)
                item->setWidth(item->boundingRect().width() - diff.x());
            else
                item->setWidth(item->boundingRect().width() + diff.x());
        }
        if(mode & Top || mode & Bottom)
        {
            item->setPos(item->mapToScene(QPointF(0, diff.y()/2.0)));
            if(mode & Top)
                item->setHeight(item->boundingRect().height() - diff.y());
            else
                item->setHeight(item->boundingRect().height() + diff.y());
        }
        item->update();
    }
    ((ObjectResizerGrip*) parentItem())->updateHandlerPositions();
}

void Handler::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    Q_UNUSED(event);
    isMoving = true;
}

void Handler::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    Q_UNUSED(event);
    isMoving = false;
}

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{    
    auto const graphicsView = new QGraphicsView(this);
    graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
    auto const scene = new QGraphicsScene(this);
    auto const item = new CrossItem();
    item->setWidth(100);
    item->setHeight(100);
    scene->addItem(item);
    item->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
    graphicsView->setScene(scene);
    graphicsView-> fitInView(scene->sceneRect(), Qt::KeepAspectRatio);

    setCentralWidget(graphicsView);
}

MainWindow::~MainWindow()
{
}

main.cpp

#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

该解决方案远非完美,但它可以工作,并且可以作为改进的良好开端。已知的问题:

  • 旋转夹点需要FullViewportUpdate,因为我太懒了,无法在单独的子项中实现它,并且它不在边界框内绘制。
  • 可能会有更好的架构,例如使用代理或信号/事件。

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

KendoUI可调整大小的小部件?

来自分类Dev

具有可调整大小的Qt小部件的可滚动背景

来自分类Dev

具有可调整大小的Qt小部件的可滚动背景

来自分类Dev

如何使Qt小部件可调整大小?

来自分类Dev

jQuery UI可拖动+可调整大小的包含问题

来自分类Dev

拖放后可拖动和可调整大小

来自分类Dev

jQuery UI可调整大小和可拖动的问题

来自分类Dev

ExtJS 4.2可调整大小的可拖动组件

来自分类Dev

div中可调整大小和可拖动的图像

来自分类Dev

隐藏小部件后如何仍可调整窗口大小,如何调整窗口大小?

来自分类Dev

如何获得可拖动和可调整大小的图像以及仅可调整div的图像?

来自分类Dev

PyQt-可调整大小的GridLayout中的居中小部件

来自分类Dev

如何在QGraphicsScene上创建可调整大小的cusom小部件

来自分类Dev

如何在QGraphicsScene上创建可调整大小的cusom小部件

来自分类Dev

可调整大小的可移动矩形

来自分类Dev

jQuery可调整大小的可拖动闪烁,用于CSS3转换

来自分类Dev

具有可调整大小/可拖动元素的响应式jQuery UI

来自分类Dev

在Java中绘制形状对象(可拖动,可调整大小并可以旋转)

来自分类Dev

使用内部滚动条创建可拖动的,可调整大小的div容器

来自分类Dev

在相同元素上可拖动和可调整大小的jQuery

来自分类Dev

jQuery UI同时使用可调整大小和可拖动

来自分类Dev

Angular 8中可拖动和可调整大小的垫对话框

来自分类Dev

具有角度的可拖动和可调整大小的div

来自分类Dev

可调整大小的容器中可拖动元素的收容问题

来自分类Dev

可拖动和可调整大小的DIV不起作用

来自分类Dev

Javascript Grid UI-可拖动和可调整大小

来自分类Dev

jQuery可调整大小和可拖动无法同时工作

来自分类Dev

具有z-index的可拖动,可调整大小的div

来自分类Dev

jQuery / Javascript可拖动和可调整大小不适用于div克隆

Related 相关文章

  1. 1

    KendoUI可调整大小的小部件?

  2. 2

    具有可调整大小的Qt小部件的可滚动背景

  3. 3

    具有可调整大小的Qt小部件的可滚动背景

  4. 4

    如何使Qt小部件可调整大小?

  5. 5

    jQuery UI可拖动+可调整大小的包含问题

  6. 6

    拖放后可拖动和可调整大小

  7. 7

    jQuery UI可调整大小和可拖动的问题

  8. 8

    ExtJS 4.2可调整大小的可拖动组件

  9. 9

    div中可调整大小和可拖动的图像

  10. 10

    隐藏小部件后如何仍可调整窗口大小,如何调整窗口大小?

  11. 11

    如何获得可拖动和可调整大小的图像以及仅可调整div的图像?

  12. 12

    PyQt-可调整大小的GridLayout中的居中小部件

  13. 13

    如何在QGraphicsScene上创建可调整大小的cusom小部件

  14. 14

    如何在QGraphicsScene上创建可调整大小的cusom小部件

  15. 15

    可调整大小的可移动矩形

  16. 16

    jQuery可调整大小的可拖动闪烁,用于CSS3转换

  17. 17

    具有可调整大小/可拖动元素的响应式jQuery UI

  18. 18

    在Java中绘制形状对象(可拖动,可调整大小并可以旋转)

  19. 19

    使用内部滚动条创建可拖动的,可调整大小的div容器

  20. 20

    在相同元素上可拖动和可调整大小的jQuery

  21. 21

    jQuery UI同时使用可调整大小和可拖动

  22. 22

    Angular 8中可拖动和可调整大小的垫对话框

  23. 23

    具有角度的可拖动和可调整大小的div

  24. 24

    可调整大小的容器中可拖动元素的收容问题

  25. 25

    可拖动和可调整大小的DIV不起作用

  26. 26

    Javascript Grid UI-可拖动和可调整大小

  27. 27

    jQuery可调整大小和可拖动无法同时工作

  28. 28

    具有z-index的可拖动,可调整大小的div

  29. 29

    jQuery / Javascript可拖动和可调整大小不适用于div克隆

热门标签

归档