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

Posted

技术标签:

【中文标题】鼠标可调整大小,可拖动的小部件【英文标题】:Mouse resizable, draggable widgets 【发布时间】:2020-10-22 15:24:57 【问题描述】:

我试图让用户将新的“小部件”(图像、文本,也许还有其他自定义数据。图像现在已经足够好了)到一种设计区域。然后我希望他们能够方便地调整/移动它们。移动部分的最佳方式似乎是使用QGraphicsView。 4行代码就可以搞定:

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

结果如下:

很好,但不能用鼠标调整大小。我已经(在这个网站上)看到了多种方法来制作这个,有点,用鼠标调整大小,但它们都是 hacky。

我在 Qt 网站上看到了 this 示例,该示例采用不同的方法为可移动和可调整大小的容器创建自定义容器。似乎可以通过更多的调整来使其工作,但这又不是一个好的解决方案。该小部件看起来并不像可调整大小。选中后,边框不会很好地表明它是动态放置/大小的东西。

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

【问题讨论】:

看看这个:github.com/scopchanov/SO_QProxy @scopchanov 好吧,这并没有什么帮助,一大堆没有解释的代码。如果你能在这里写下大纲作为答案,我会很高兴。 然后在这里查看我的答案:***.com/a/52026817/5366641 可能还有这个答案:***.com/a/64408630/5366641 最好的方法是处理事件。看看这个:doc.qt.io/qt-5/qtquick-input-mouseevents.html 【参考方案1】:

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

主窗口.h

#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) overrideQ_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:
;

主窗口.cpp

#include "mainwindow.h"
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QApplication>
#include <QGraphicsSceneMouseEvent>
#include <QHBoxLayout>
// Return nearest point along the line to a given point
// http://***.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,因为我懒得在单独的子项中实现它,而且它在边界框之外绘制。 可能有更好的架构,例如使用代理或信号/事件。

【讨论】:

感谢您的帖子。什么是 Item 类? @AyxanHaqverdili 它是任何带有 ObjectResizerGrip* resizerGrip = nullptr 的 QGraphicsItem/Object 派生类;属性。让你的所有物品都来源于它。 @AyxanHaqverdili 我决定做个好朋友,放一个完整的可编译示例。 看来我只能在 17 小时内奖励赏金。我会尽快处理的 @AyxanHaqverdili 我解决了漂移问题。设置维度时,我将 qreal 转换为 int。这段代码在我最初的实现中是不同的,我在创建这个例子时引入了这个错误。现在效果好多了。【参考方案2】:

在使用鼠标、键盘和捕获操作系统事件时,您必须依赖事件系统。基类是QEvent,在您的具体情况下,它允许您“QResizeEvent,QMouseEvent,QScrollEvent,...”以及更多有趣的事情。

【讨论】:

QWidgets 旨在用作 GUI 的构建块,而不是文档。您会建议某人使用 QWidget 来表示文本文档中的字母吗?我希望不是。它当然可以完成,但这将是一个非常重大的设计缺陷。那你为什么建议 OP 为图形文档使用小部件? 此外,从头开始做所有事情的基本想法远不合适。 OP 抱怨 QGraphics 框架缺乏一些功能,而你告诉他/她:啊,算了吧——拿 QWidget 自己做所有事情。这没有意义。 顺便说一句,如果我听起来很刺耳,请原谅。这不是我的本意。然而,很多人,包括我,可能还有你,都在使用 SO 作为参考。未来的读者应该清楚,为什么这不是一个好主意。所以,没有难过的感觉。 @AyxanHaqverdili,您可以熟悉一下图形视图框架here。但是,您已经正确地注意到,缺少一些基本的东西,如调整大小。您可以这样做并按照示例进行操作,包括我提供的示例。但是,随着 Qt 6 敲门并承诺改进 QML,我认为您最好直接朝那个方向前进。这是小部件和 QGraphicsView 的一些混合,是(来自 Qt)推荐的构建现代外观应用程序的方法。 @AyxanHaqverdili,你仍然需要自己实现很多东西,但这是现在的方式。

以上是关于鼠标可调整大小,可拖动的小部件的主要内容,如果未能解决你的问题,请参考以下文章

角度可拖动和调整大小

Java:制作可调整大小和可拖动的组件

PyQt 中的半可调整大小的小部件

当浏览器调整大小时,不断在视频上移动可调整大小/可拖动的图像

如何在使用 jQuery(可拖动和可调整大小)拖动光标时调整图像大小

jquery 中可调整大小、可拖动的对象。可能的?