QWidget Drag And Drop inside Layout. Is this a good way?
-
Hello,
so i tried to get drag and drop inside a qlayout working with my custom qwidgets. I'll provide a simple example down below. Is this a good way to handle it if i want to have lets say 100 custom complex widgets or is there a more efficient way? Listview with delegates was no option as the widgets should be editable instantly and are very different from each other in the real case (not in the example below).
Can anyone tell me how to get some kind of drop indicator? So instead of dragging the real widget just display an indicator at the position the widget would land on?
simple widget.cpp:
#include "simplewidget.h" #include <QWidget> #include <QVBoxLayout> #include <QMouseEvent> #include <QStyleOption> #include <QApplication> #include <QPainter> #include <QTime> #include <QRandomGenerator> #include <QPalette> #include <QLineEdit> simplewidget::simplewidget(QWidget *parent) : QWidget{parent} { QLineEdit *edit = new QLineEdit(this); edit->setText("Test"); // Generieren Sie zufällige Werte für die RGB-Komponenten int r = QRandomGenerator::global()->bounded(256); int g = QRandomGenerator::global()->bounded(256); int b = QRandomGenerator::global()->bounded(256); // Setzen Sie die Hintergrundfarbe des Widgets QPalette palette = this->palette(); palette.setColor(QPalette::Window, QColor(r, g, b)); this->setPalette(palette); setAutoFillBackground(true); setFixedHeight(50); } void simplewidget::mouseMoveEvent(QMouseEvent *event) { if (!(event->buttons() & Qt::LeftButton)) return; if (!IsMinimumDistanceRiched(event)) { return; } int y = event->globalY() - mouseClickY + oldY; int BottomBorder = parentWidget()->geometry().height() - this->geometry().height(); if(y < 0) y = 0; else if(y > BottomBorder) y = BottomBorder; move(oldX, y); this->raise(); /* if (!(event->buttons() & Qt::LeftButton)) return; if (!IsMinimumDistanceRiched(event)) { return; } int y = event->globalY() - mouseClickY + oldY; int BottomBorder = parentWidget()->geometry().height() - this->geometry().height(); if(y < 0) y = 0; else if(y > BottomBorder) y = BottomBorder; move(oldX, y); // Bestimmen Sie die Bewegungsrichtung basierend auf der alten und neuen y-Position MoveDirection direct = (oldY > y) ? MoveUp : MoveDown; // Verschieben Sie das Widget im Layout moveInLayout(this, direct); this->raise(); */ /* int ny = geometry().y(); MoveDirection direct; int offset; if(oldY > ny) { offset = oldY - ny; direct = MoveUp; } else if(oldY < ny) { offset = ny - oldY; direct = MoveDown; } int count = offset/height(); for(int i = 0; i < count; i++) { moveInLayout(this, direct); } update(); QVBoxLayout* myLayout = qobject_cast<QVBoxLayout*>(this->parentWidget()->layout()); myLayout->update(); this->saveGeometry();*/ } void simplewidget::mousePressEvent(QMouseEvent *event) { if (event->buttons() & Qt::LeftButton) dragStartPosition = event->pos(); oldX = this->geometry().x(); oldY = this->geometry().y(); mouseClickX = event->globalX(); mouseClickY = event->globalY(); } bool simplewidget::IsMinimumDistanceRiched(QMouseEvent *event) { return (event->pos() - dragStartPosition).manhattanLength() >= QApplication::startDragDistance(); } bool simplewidget::moveInLayout(QWidget *widget, MoveDirection direction) { QVBoxLayout* myLayout = qobject_cast<QVBoxLayout*>(widget->parentWidget()->layout()); const int index = myLayout->indexOf(widget); if (direction == MoveUp && index == 0) { return false; } if (direction == MoveDown && index == myLayout->count()-2 ) { return false; } const int newIndex = direction == MoveUp ? index - 1 : index + 1; myLayout->removeWidget(widget); myLayout->insertWidget(newIndex , widget); return true; } void simplewidget::paintEvent(QPaintEvent *) { QStyleOption o; o.initFrom(this); QPainter p(this); style()->drawPrimitive(QStyle::PE_Widget, &o, &p, this); } void simplewidget::mouseReleaseEvent(QMouseEvent *) { int y = geometry().y(); MoveDirection direct; int offset; if(oldY > y) { offset = oldY - y; direct = MoveUp; } else if(oldY < y) { offset = y - oldY; direct = MoveDown; } int count = offset/height(); for(int i = 0; i < count; i++) { moveInLayout(this, direct); } update(); QVBoxLayout* myLayout = qobject_cast<QVBoxLayout*>(this->parentWidget()->layout()); myLayout->update(); this->saveGeometry(); }
simplewidget.h
#ifndef SIMPLEWIDGET_H #define SIMPLEWIDGET_H #include <QWidget> #include <QVBoxLayout> #include <QMouseEvent> class simplewidget : public QWidget { Q_OBJECT public: explicit simplewidget(QWidget *parent = nullptr); protected: void mouseMoveEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void paintEvent(QPaintEvent *event) override; private: QPoint dragStartPosition; int oldX; int oldY; int mouseClickX; int mouseClickY; enum MoveDirection { MoveUp, MoveDown }; bool IsMinimumDistanceRiched(QMouseEvent *event); bool moveInLayout(QWidget *widget, MoveDirection direction); signals: }; #endif // SIMPLEWIDGET_H
main.cpp
#include "mainwindow.h" #include <QVBoxLayout> #include <QWidget> #include "simplewidget.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { QWidget *central = new QWidget(); setCentralWidget(central); QVBoxLayout *layout = new QVBoxLayout(); central->setLayout(layout); for(int i = 0; i < 7; ++i) { simplewidget *widget = new simplewidget(); layout->addWidget(widget); } layout->addStretch(1); } MainWindow::~MainWindow() {}
Credits to this concept go to murzagurskiy: https://stackoverflow.com/a/36152762
-
@StudentScripter said in QWidget Drag And Drop inside Layout. Is this a good way?:
Can anyone tell me how to get some kind of drop indicator? So instead of dragging the real widget just display an indicator at the position the widget would land on?
You can set a pixmap on the
QDrag
object for that purpose.
It could also be the return value ofQWidget::grab()
, to get a pixmap of the widget you want to drag.
See here for more information and a tutorial. -
@Axel-Spoerl Great hint, but with a drop indicator i mean something like a blueish line above/below the widget in the layout it would land on.
-
like a blueish line above/below the widget in the layout
I have troubles to imagine what you mean by that. Colors aside, a widget is typically a rectangle. If I want to indicate a potential landing spot, I'd rather use a rectangle, than a line.
As a crative approach:
- represent the dragged widget by its
grab()
pixmap, as mentioned above. - override the receiving widget's
dragEnterEvent()
- when the dragged object enters receiving widget, take the rectangle of the drag's pixmap (equal to the dragged widget's geometry).
- override the receiving widget's
dragMoveEvent()
- when the dragged object moves over a suitable position in the layout, add a
QRubberBand
with the rectangle's size at the layout position - delete and recreate the
QRubberBand
each time, the layout position changes during the move - finally delete the rubber band and add the dragged widget instead.
I must admit, that I love crafting beautifully animated drag & drop implementations. They can be art, and work wonders in the user-acceptance of an application :-)
- represent the dragged widget by its
-
@Axel-Spoerl said in QWidget Drag And Drop inside Layout. Is this a good way?:
I have troubles to imagine what you mean by that. Colors aside, a widget is typically a rectangle. If I want to indicate a potential landing spot, I'd rather use a rectangle, than a line.
I think @StudentScripter means the same behavior you see when adding widgets to a layout in QtDesigner.
A "line" can be a thin rectangle :)
You also see a rectangle when dragging
QDockWidgets
around showing the validDockAreas
.Maybe check the source code of QtCreator's Designer or
QMainWindows
dockarea.