使用鼠标移动QGraphicsRectItem

3

我想在将QGraphicsRectItem添加到场景后移动它。它可以移动,但是离鼠标指针有一定的偏移量。我认为这只是将鼠标指针位置加到其原始位置上。我不知道如何解决这个问题。

以下是我的代码:

class ucFilter : public QGraphicsItem {
    
    std::shared_ptr<QGraphicsRectItem> m_rect;
    std::shared_ptr<QGraphicsTextItem> m_text;
    std::shared_ptr<QString> m_name;
    std::shared_ptr<QPointF> m_pos;
    QGraphicsItem* selectedItem;
    
    bool m_mouseGrabbed;
public:
    static const int default_x = 80, default_y=40;
    ucFilter::ucFilter(QString &name, QPointF &pos){
        m_name  = shared_ptr<QString>(new QString(name));
        m_pos   = shared_ptr<QPointF>(new QPointF(pos));
        m_rect  = shared_ptr<QGraphicsRectItem>( new QGraphicsRectItem(pos.x()-default_x, pos.y()-default_y, 2*default_x, 2*default_y ));

        
        m_text  = shared_ptr<QGraphicsTextItem>( new QGraphicsTextItem(name));
        
        m_text->setPos(pos.x() - m_text->boundingRect().width()/2, pos.y()- 30);
        selectedItem = NULL;
        m_mouseGrabbed = false;
    }

    QGraphicsRectItem*  getRect()   { return m_rect.get();  }
    QGraphicsTextItem*  getText()   { return m_text.get();  }
    QString*            getName()   { return m_name.get();  }
    QPointF*            getPos()    { return m_pos.get();   }
    
    void                setPos(QPointF newPos)  { m_pos->setX(newPos.x()); m_pos->setY(newPos.y()); }

    QRectF ucFilter::boundingRect() const
    {
        return m_rect->boundingRect();
    }

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
    {
        Q_UNUSED(widget);
        //QBrush brush(
        
        if (!m_mouseGrabbed){ grabMouse(); m_mouseGrabbed = true;   }
        
    }
    
    void mousePressEvent(QGraphicsSceneMouseEvent *event){
        selectedItem = this;
        QGraphicsItem::mousePressEvent(event);
    }

    void mouseReleaseEvent(QGraphicsSceneMouseEvent *event){
        selectedItem = NULL;
        QGraphicsItem::mouseReleaseEvent(event);
    }

    void mouseMoveEvent(QGraphicsSceneMouseEvent *event)
    {
        if(NULL != selectedItem){
            m_text->setPos(event->pos());
            m_rect->setPos(event->pos());
        }
        QGraphicsItem::mouseMoveEvent(event);
    }

};

ucFilter对象在场景dropEvent中创建:

void cGraphicsScene::dropEvent(QGraphicsSceneDragDropEvent * event){
    
    QTreeView* source = static_cast<QTreeView*>(event->source());
    
    string name = event->mimeData()->text().toUtf8().constData();
    if(0 == name.length()){
        event->acceptProposedAction();
        return ; // nothing to do anymore 
    }
    QPointF pos = event->scenePos ();

    shared_ptr<ucFilter> newFilter = shared_ptr<ucFilter>(new ucFilter(event->mimeData()->text(),event->scenePos ()));
    m_filters.push_back(newFilter);
    this->addItem(newFilter->getRect());
    this->addItem(newFilter->getText());

    this->addItem(newFilter.get()); // also add the item to grab mouse events

    event->acceptProposedAction();
}

问题可能出在哪里? 这是我实际看到的屏幕截图:enter image description here

我希望矩形能够在鼠标所在位置绘制。


可能这两个坐标(鼠标和物品)有不同的起点,如果我没记错的话,物品的坐标是相对于场景的,而鼠标的坐标是基于屏幕的。 - Marco
准确地说!我该如何更改这个? - Mihai Galos
1
喔耶,std::shared_ptr 疯狂了。您不需要将任何成员存储为指针。您的项目拥有其子项和其他任何参数。按值存储它们。你所做的是我见过的最令人发指的模仿编程行为。有人让你相信共享指针是正确的选择,但你不明白为什么以及何时使用它们,却滥用它们。这真的非常糟糕。您的初始化列表在哪里?通过const引用传递?呃。。。 - Kuba hasn't forgotten Monica
哦,我明白了。如果我将ucFilter类导出以便在不使用shared_ptr实例化的情况下使用,那么我需要想办法智能指向我创建的新对象。 - Mihai Galos
最后一个问题:为什么您有一个没有任何子项的QGraphicsItem?难道“rect”和“text”不应该相对于“ucFilter”定位吗? - Kuba hasn't forgotten Monica
显示剩余3条评论
1个回答

4
你有几个问题:
  1. 使用共享指针来持有所有内容是完全不必要的。场景就像 QObject 一样作为项目容器。

  2. ucFilter 没有子项。它持有指向其他项目的指针,但这是不必要的。基本项本身可以是矩形,并且可以将文本作为子项。这样,您无需以特殊方式处理定位。

  3. ucFilter 可以移动。不要重新实现该功能。

  4. 当传递引用时,除非您打算将修改后的值传递出去,否则请将它们作为常量引用传递。如果您希望在函数体内更改值,则可以通过值传递它。

  5. 当您拖动项目时,鼠标已经被抓取。

让我们从ucFilter项目开始。它非常简单,做了所有需要的事情。注意,m_text持有的是值,并且作为矩形父项的子项。
// https://github.com/KubaO/stackoverflown/tree/master/questions/graphics-item-drop-32574576
#include <QtWidgets>

class ucFilter : public QGraphicsRectItem {
   QGraphicsTextItem m_text;
public:
   ucFilter(const QString &name, const QPointF &pos, QGraphicsItem * parent = 0) :
      QGraphicsRectItem(parent),
      m_text(this)
   {
      static const QRect defaultRect(0, 0, 160, 80);
      setPos(pos);
      setRect(QRect(-defaultRect.topLeft()/2, defaultRect.size()));
      setFlags(QGraphicsItem::ItemIsMovable);
      setName(name);
   }
   void setName(const QString & text) {
      m_text.setPlainText(text);
      m_text.setPos(-m_text.boundingRect().width()/2, -30);
   }
   QString name() const {
      return m_text.toPlainText();
   }
};

由于我们要从一个方便的小部件(QListWidget)中删除,因此需要解码来自application/x-qabstractitemmodeldatalist MIME类型的文本:

const char * kMimeType = "application/x-qabstractitemmodeldatalist";

QVariant decode(const QMimeData* data, Qt::ItemDataRole role = Qt::DisplayRole) {
   auto buf = data->data(kMimeType);
   QDataStream stream(&buf, QIODevice::ReadOnly);
   while (!stream.atEnd()) {
      int row, col;
      QMap<int, QVariant> map;
      stream >> row >> col >> map;
      if (map.contains(role)) return map[role];
   }
   return QVariant();
}

当拖动物品进入场景时,该场景立即创建一个项目并在拖动期间移动该项目。
这些项目的所有权仍然属于场景:除非我们想要明确将它们从场景中删除,否则不需要删除任何项目。m_dragItem 用于引用当前拖动的项目,以便简单地移动它并在放置完成后将其添加到 m_filters 中。如果拖动离开场景(或被中止),项目将从场景中简单地删除。
class cGraphicsScene : public QGraphicsScene {
   QList<ucFilter*> m_filters;
   ucFilter* m_dragItem;
public:
   cGraphicsScene(QObject * parent = 0) : QGraphicsScene(parent), m_dragItem(nullptr) {}
   void dragEnterEvent(QGraphicsSceneDragDropEvent * event) Q_DECL_OVERRIDE {
      if (!event->mimeData()->hasFormat(kMimeType)) return;
      auto name = decode(event->mimeData()).toString();
      if (name.isEmpty()) return;
      QScopedPointer<ucFilter> filter(new ucFilter(name, event->scenePos()));
      addItem(m_dragItem = filter.take());
      event->acceptProposedAction();
   }
   void dragMoveEvent(QGraphicsSceneDragDropEvent * event) Q_DECL_OVERRIDE {
      if (!m_dragItem) return;
      m_dragItem->setPos(event->scenePos());
      event->acceptProposedAction();
   }
   void dropEvent(QGraphicsSceneDragDropEvent * event) Q_DECL_OVERRIDE {
      if (!m_dragItem) return;
      m_dragItem->setPos(event->scenePos());
      m_filters << m_dragItem;
      event->acceptProposedAction();
   }
   void dragLeaveEvent(QGraphicsSceneDragDropEvent * event) Q_DECL_OVERRIDE {
      delete m_dragItem;
      m_dragItem = nullptr;
      event->acceptProposedAction();
   }
};

测试工具非常简单:我们的场景、显示它的视图和一个包含两个项目的列表,您可以将它们拖到场景中。
int main(int argc, char ** argv) {
   QApplication app{argc, argv};
   QWidget w;
   cGraphicsScene scene;
   QGraphicsView view(&scene);
   QListWidget list;
   QHBoxLayout l(&w);
   l.addWidget(&view);
   l.addWidget(&list);

   list.setFixedWidth(120);
   list.addItem("Item1");
   list.addItem("Item2");
   list.setDragDropMode(QAbstractItemView::DragOnly);
   view.setAcceptDrops(true);

   w.resize(500, 300);
   w.show();
   return app.exec();
}

您可以将右侧列表中的项目拖动到左侧场景中。您还可以在场景内移动项目,以重新定位它们。

我由衷地感谢你,不仅解决了我的问题,还帮助我提高了编程水平。你的回答是传奇。 - Mihai Galos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接