2017-02-21 3 views
0

Je souhaite créer un widget de montage. J'ai une classe que j'insère dans un widget Timeline, en utilisant QGraphicsScene et QGraphicsView.L'échange de widgets dans QGraphicsView échoue

Si j'insère trois lignes dans l'ordre, je peux les montrer dans le widget. Puis je veux les faire glisser afin de réorganiser les rangées. Par exemple, si je

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

Si je fais glisser la ligne 1 entre Row2 et Row3 j'obtenir

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

Et ainsi de suite. Lorsque je commence à faire glisser les travaux de réorganisation, mais après quelques glisser (je fais toujours glisser la première rangée entre les deux autres) glisser s'arrête. Je ne peux plus faire glisser la première rangée. Ensuite, je commence à faire glisser une autre ligne, puis cela fonctionne à nouveau.

Ce sont les classes que j'ai utilisé (je ne peux pas utiliser uniquement les fichiers HPP en raison de fichiers moc):

classe Row:

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

classe Timeline:

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

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

Quand je commence le programme que j'obtenir:

enter image description here

Puis je fais glisser la ligne rouge entre le vert et le bleu:

enter image description here

Maintenant, je ne peux pas faire glisser la ligne verte, mais si j'en traîne une autre dans une autre position, je peux encore faire glisser la ligne verte.

Qu'est-ce que je fais mal?

Répondre

1

Lorsque vous modifiez Row::m_origin, vous modifiez la valeur renvoyée par Row::boundingRect() sans appeler QGraphicsItem::prepareGeometryChange().

Mais la documentation Qt déclare:

Si vous souhaitez modifier le rectangle de délimitation de l'élément, vous devez d'abord appeler prepareGeometryChange(). Cela avertit la scène du changement imminent , afin qu'il puisse mettre à jour son index de géométrie d'article; sinon, la scène ne sera pas consciente de la nouvelle géométrie de l'élément et les résultats sont non définis (généralement, les artefacts de rendu sont conservés dans la vue).


vous pouvez aussi le faire avec un code plus simple.

  • Retirer m_origin et utiliser QGraphicsItem::pos() et QGraphicsItem::setPos().
  • Row::boundingRect() peut maintenant simplement retourner QRectF(0.0, 0.0, m_width, m_height) et vous appelez QGraphicsItem::prepareGeometryChange() uniquement lorsque vous modifiez m_width ou m_height.
  • va de même pour Row::paint()

De cette façon, vous pouvez tirer parti du système de positionnement QGraphicsScene, sans avoir à gérer les changements dans la géométrie.


D'un côté non au lieu de class Row : public QObject, public QGraphicsItem vous pourriez écrire class Row : public QGraphicsObject.

+0

Merci de ne pas avoir bien compris la méthode 'prepareGeometryChange()'. Je suis nouveau à cette partie de Qt ... Et aussi merci pour d'autres suggestions précieuses. – Jepessen