【问题标题】:Swap widgets in QGraphicsView failsQGraphicsView 中的交换小部件失败
【发布时间】:2017-02-21 21:45:15
【问题描述】:

我想创建一个时间线小部件。 我有一个Row 类,我使用QGraphicsSceneQGraphicsView 插入到Timeline 小部件中。

如果我按顺序插入三行,我可以在小部件中显示它们。

然后我想拖动它们以重新排列行。例如,如果我有

+----------------------+
|  Row 1               |
+----------------------+
|  Row 2               |
+----------------------+
|  Row 3               |
+----------------------+

如果我在第 2 行和第 3 行之间拖动第 1 行,我将获得

+----------------------+
|  Row 2               |
+----------------------+
|  Row 1               |
+----------------------+
|  Row 3               |
+----------------------+

等等。

当我开始拖动重新排序作品时,但经过一些拖动(我总是拖动其他两个之间的第一行)拖动停止。我不能再拖第一行了。然后我开始拖动另一行,然后它又可以工作了。

这些是我用过的类(我不能只使用 hpp 文件,因为 moc 文件):

行类:

#ifndef ROW_HPP_
#define ROW_HPP_

#include <QGraphicsView>
#include <QGraphicsItem>
#include <QBrush>
#include <QObject>

const qreal TopZValue{ std::numeric_limits<qreal>::max() };

class Row : public QObject, public QGraphicsItem {

  Q_OBJECT

public:

  Row();
  virtual ~Row() = default;
  void setBrush(const QBrush& b);
  void setOrigin(int x, int y);
  void setHeight(int height);
  int getHeight() const;
  const QPoint& getOrigin() const;

public:

  virtual QRectF boundingRect() const override;
  virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;

signals:

  void originUpdated(const QPoint& origin);

protected:

  virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
  virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
  virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;

private:

  void drawBackground(QPainter* painter);

private:

  QBrush m_background;
  int m_height = 0;
  int m_width = 0;
  QPoint m_origin;
  qreal m_zValueWhenDragged = 0.0;
};

#endif // !ROW_HPP_

// CPP file
#include "Row.hpp"

Row::Row() :
  QGraphicsItem(nullptr) {
  setFlag(QGraphicsItem::ItemIsSelectable);
  setFlag(QGraphicsItem::ItemIsMovable);
  setFlag(QGraphicsItem::ItemSendsGeometryChanges);
}

void Row::setBrush(const QBrush& b) {
  m_background = b;
}

void Row::setOrigin(int x, int y) {
  m_origin.rx() = x;
  m_origin.ry() = y;
  setPos(0, 0);
}

void Row::setHeight(int height) {
  m_height = height;
}

int Row::getHeight() const {
  return m_height;
}

const QPoint& Row::getOrigin() const {
  return m_origin;
}

QRectF Row::boundingRect() const {
  return QRectF(m_origin.x(), m_origin.y(), m_width, m_height);
}

void Row::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
  Q_UNUSED(option)
  Q_UNUSED(widget)
  drawBackground(painter);
  QGraphicsView *view = scene()->views().first();
  m_width = view->width();
  painter->drawRect(m_origin.x(), m_origin.y(), m_width, m_height);
}

void Row::mousePressEvent(QGraphicsSceneMouseEvent *event) {
  m_zValueWhenDragged = zValue();
  QGraphicsItem::mousePressEvent(event);
}

void Row::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
  setZValue(TopZValue);
  QGraphicsItem::mouseMoveEvent(event);
}

void Row::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
  setZValue(m_zValueWhenDragged);
  QPoint newOrigin(0, m_origin.y() + scenePos().toPoint().y());
  m_origin = newOrigin;
  emit originUpdated(newOrigin);
  QGraphicsItem::mouseReleaseEvent(event);
}

void Row::drawBackground(QPainter* painter) {
    auto brush = painter->brush();
    auto width = painter->viewport().width();
    painter->setBrush(m_background);
    painter->drawRect(m_origin.x(), m_origin.y(), width, m_height);
    painter->setBrush(brush);
}

时间线类:

#ifndef TIMELINE_HPP_
#define TIMELINE_HPP_

#include "Row.hpp"
#include <QHBoxLayout>
#include <QMainWindow>
#include <QWidget>

class Timeline : public QWidget {

  Q_OBJECT

public:

  Timeline(QWidget* parent = nullptr);
  virtual ~Timeline() = default;
  size_t addRow(Row* row);
  size_t getNumberOfRows() const;

private slots:

  void setRowOrigin(const QPoint& origin);

private:

  void orderRowsOriginsByTheirPosition();

private:

  QGraphicsView* m_view;
  QGraphicsScene* m_scene;
  QHBoxLayout* m_layout;
  std::vector<Row*> m_rows;
};

#endif //!TIMELINE_HPP_

// CPP file
#include "Timeline.hpp"

Timeline::Timeline(QWidget* parent) :
  QWidget(parent) {
  m_view = new QGraphicsView(this);
  m_scene = new QGraphicsScene(this);
  m_layout = new QHBoxLayout(this);
  m_layout->addWidget(m_view);
  m_view->setScene(m_scene);
  m_view->setAlignment(Qt::AlignTop | Qt::AlignLeft);
}

size_t Timeline::addRow(Row* row) {
  m_rows.push_back(row);
  m_scene->addItem(row);
  orderRowsOriginsByTheirPosition();
  connect(row, &Row::originUpdated, this, &Timeline::setRowOrigin);
  return getNumberOfRows();
}

size_t Timeline::getNumberOfRows() const {
  return m_rows.size();
}

void Timeline::setRowOrigin(const QPoint& origin) {
  Q_UNUSED(origin)
  orderRowsOriginsByTheirPosition();
}

void Timeline::orderRowsOriginsByTheirPosition() {
  int offsetY = 0;
  std::sort(m_rows.begin(), m_rows.end(), [] (Row* left, Row* right) { return left->getOrigin().y() < right->getOrigin().y();});
  for (auto& it : m_rows) {
    it->setOrigin(0, offsetY);
    offsetY += it->getHeight();
  }
  m_scene->update();
  m_view->update();
}

MainWindow 类:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include "Timeline.hpp"

class MainWindow : public QMainWindow
{
  Q_OBJECT

public:
  explicit MainWindow(QWidget *parent = 0);
  ~MainWindow() = default;

private:

  Timeline* m_timeline;
};

#endif // MAINWINDOW_H

// CPP file
#include "MainWindow.hpp"

MainWindow::MainWindow(QWidget *parent) :
  QMainWindow(parent),
  m_timeline(new Timeline(this)) {
  setCentralWidget(m_timeline);
  setMinimumSize(300, 200);

  auto row1 = new Row();
  row1->setHeight(40);
  m_timeline->addRow(row1);
  row1->setBrush(Qt::red);
  auto row2 = new Row();
  row2->setHeight(30);
  m_timeline->addRow(row2);
  row2->setBrush(Qt::blue);
  auto row3 = new Row();
  row3->setHeight(50);
  m_timeline->addRow(row3);
  row3->setBrush(Qt::green);
}

main.cpp

#include "Row.hpp"
#include "MainWindow.hpp"
#include <QCoreApplication>
#include <QApplication>

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

当我启动我获得的程序时:

然后我在绿色和蓝色之间拖动红色行:

现在我不能拖动绿色行,但是如果我将另一行拖动到另一个位置,我可以再次拖动绿色行。

我做错了什么?

【问题讨论】:

    标签: c++ qt drag-and-drop qgraphicsview qgraphicsscene


    【解决方案1】:

    当您更改Row::m_origin 时,您会更改Row::boundingRect() 返回的值,而无需调用QGraphicsItem::prepareGeometryChange()

    但 Qt 文档指出:

    如果你想改变项目的边界矩形,你必须先 调用 prepareGeometryChange()。这通知现场即将发生 更改,以便它可以更新其项目几何索引;否则, 场景将不知道项目的新几何体,结果是 未定义(通常,渲染工件留在视图中)。


    你也可以用更简单的代码来实现。

    • 删除m_origin 并使用QGraphicsItem::pos()QGraphicsItem::setPos()
    • Row::boundingRect() 现在可以简单地返回 QRectF(0.0, 0.0, m_width, m_height) 并且您仅在更改 m_widthm_height 时才调用 QGraphicsItem::prepareGeometryChange()
    • Row::paint() 也是如此

    通过这种方式,您可以利用 QGraphicsScene 定位系统,而无需处理几何形状的变化。


    除了class Row : public QObject, public QGraphicsItem,你也可以写class Row : public QGraphicsObject

    【讨论】:

    • 谢谢 我不太了解prepareGeometryChange() 方法。我是 Qt 的这一部分的新手......也感谢其他宝贵的建议。
    猜你喜欢
    • 1970-01-01
    • 2011-07-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-23
    • 2011-01-12
    相关资源
    最近更新 更多