I have a QListWidget
and a QGraphicsView
both subclassed to overwrite some of their members.
I prepared a minimal verifiable example showing the problem I have.
From the QListWidget
I can drag and drop specific field represented by a QTableWidget
and drop them into a QGraphicsView
and in order to do that I am using a QGraphicsProxyWidget
approach as shown below.
It is important to mention that the green QGraphicsRectItem
it is used to move around the QTableWidget
as well as adjusting its dimension.
The problem: dragging from the QListWidget
is not a problem. But dropping into QGraphicsView
is a problem because as you see the QGraphicsRectItem
is right on top of the QTableWidget
.
The minimal verifiable example can be found here and below for completeness:
However as soon as I touch the QSizeGrip
bottom right of the QGraphicsProxyWidget
the QGraphicsRectItem
adjust itself with the dimension of the QTableWidgetItem
:
Below the minimal verifiable example:
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QGraphicsView>
#include "scene.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
Scene *mScene;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
mScene = new Scene;
ui->graphicsView->setRenderHint(QPainter::Antialiasing);
ui->graphicsView->setScene(mScene);
ui->graphicsView->show();
}
MainWindow::~MainWindow()
{
delete ui;
}
optionlist.h
#ifndef OPTIONLIST_H
#define OPTIONLIST_H
#include <QListWidget>
class OptionList : public QListWidget {
public:
OptionList(QWidget *parent = nullptr);
protected:
void startDrag(Qt::DropActions supportedActions);
};
#endif // OPTIONLIST_H
optionlist.cpp
#include "optionlist.h"
#include <QDrag>
OptionList::OptionList(QWidget *parent) : QListWidget(parent) {
setDragEnabled(true);
setDropIndicatorShown(true);
setSelectionMode(QAbstractItemView::SingleSelection);
setDefaultDropAction(Qt::CopyAction);
setViewMode(QListView::ListMode);
for (const QString &workspaceTree : {"Images", "Path", "Connection"}) {
QListWidgetItem *img = new QListWidgetItem;
img->setText(workspaceTree);
img->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable |
Qt::ItemIsDragEnabled);
addItem(img);
}
}
void OptionList::startDrag(Qt::DropActions supportedActions) {
if (supportedActions & Qt::CopyAction) {
QList<QListWidgetItem *> m_items = selectedItems();
if (m_items.isEmpty())
return;
QMimeData *data = mimeData(m_items);
QDrag *drag = new QDrag(this);
QPixmap pixmap("/home/Icon_icon.png");
drag->setPixmap(pixmap);
drag->setMimeData(data);
drag->setHotSpot(pixmap.rect().center());
drag->exec(Qt::CopyAction);
} else
QListWidget::startDrag(supportedActions);
}
customtablewidget.h
#ifndef CUSTOMTABLEWIDGET_H
#define CUSTOMTABLEWIDGET_H
#include <QTableWidget>
#include <QResizeEvent>
class CustomTableWidget : public QTableWidget
{
Q_OBJECT
public:
CustomTableWidget(QWidget *parent = Q_NULLPTR) : QTableWidget(parent){}
void resizeEvent(QResizeEvent *event);
signals:
void sizeChanged();
};
#endif // CUSTOMTABLEWIDGET_H
customtablewidget.cpp
#include "customtablewidget.h"
void CustomTableWidget::resizeEvent(QResizeEvent *event)
{
QTableWidget::resizeEvent(event);
emit sizeChanged();
}
And finally where the problem is:
scene.h
#ifndef SCENE_H
#define SCENE_H
#include <QGraphicsScene>
class Scene : public QGraphicsScene
{
public:
Scene(QObject *parent = nullptr);
protected:
void dragEnterEvent(QGraphicsSceneDragDropEvent *event);
void dragMoveEvent(QGraphicsSceneDragDropEvent *event);
void dropEvent(QGraphicsSceneDragDropEvent *event);
};
#endif // SCENE_H
scene.cpp
#include "scene.h"
#include "customtablewidget.h"
#include <QGraphicsSceneDragDropEvent>
#include <QMimeData>
#include <QTableWidget>
#include <QGraphicsProxyWidget>
#include <QVBoxLayout>
#include <QMetaEnum>
#include <QEvent>
#include <QSizeGrip>
Scene::Scene(QObject *parent)
{
setBackgroundBrush(Qt::lightGray);
}
void Scene::dragEnterEvent(QGraphicsSceneDragDropEvent *event) {
if (event->mimeData()->hasFormat("application/x-qabstractitemmodeldatalist"))
event->setAccepted(true);
}
void Scene::dragMoveEvent(QGraphicsSceneDragDropEvent *event) {
if (event->mimeData()->hasFormat("application/x-qabstractitemmodeldatalist"))
event->setAccepted(true);
}
void Scene::dropEvent(QGraphicsSceneDragDropEvent *event) {
QByteArray encoded =
event->mimeData()->data("application/x-qabstractitemmodeldatalist");
QDataStream stream(&encoded, QIODevice::ReadOnly);
QStringList rosTables;
QString newString;
while (!stream.atEnd()) {
int row, col;
QMap<int, QVariant> roleDataMap;
stream >> row >> col >> roleDataMap;
rosTables << roleDataMap[Qt::DisplayRole].toString();
}
for (const QString &tableType : rosTables) {
if(tableType == "Images")
{
QPoint initPos(0,0);
CustomTableWidget *wgt = new CustomTableWidget;
QGraphicsRectItem *proxyControl = addRect(initPos.x(), initPos.y(), wgt->width(), 20, QPen(Qt::black), QBrush(Qt::darkGreen)); // widget->width() works properly here because of the resize(layout->sizeHint()) that we have used inside it
QSizeGrip * sizeGrip = new QSizeGrip(wgt);
QHBoxLayout *layout = new QHBoxLayout(wgt);
//QGraphicsRectItem *proxyControl = addRect(initPos.x(), initPos.y(), wgt->width(), 20, QPen(Qt::black), QBrush(Qt::darkGreen));
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(sizeGrip, 0, Qt::AlignRight | Qt::AlignBottom);
connect(wgt, &CustomTableWidget::sizeChanged, [wgt, proxyControl](){
proxyControl->setRect(wgt->geometry().adjusted(-10, -10, 10, 10));
});
proxyControl->setPos(initPos.x(), initPos.y());
proxyControl->setFlag(QGraphicsItem::ItemIsMovable, true);
proxyControl->setFlag(QGraphicsItem::ItemIsSelectable, true);
wgt->setColumnCount(2);
wgt->setRowCount(2);
for (int ridx = 0 ; ridx < wgt->rowCount() ; ridx++ )
{
for (int cidx = 0 ; cidx < wgt->columnCount() ; cidx++)
{
QTableWidgetItem* item = new QTableWidgetItem();
item->setText(QString("%1").arg(ridx));
wgt->setItem(ridx,cidx,item);
}
}
QGraphicsProxyWidget * const proxy = addWidget(wgt);
// In my case the rectangular graphics item is supposed to be above my widget so the position of the widget is shifted along the Y axis based on the height of the rectangle of that graphics item
proxy->setPos(initPos.x(), initPos.y()+proxyControl->rect().height());
proxy->setParentItem(proxyControl);
}
}
}
I did extensive research about this topic and came across this other post which was very useful because made me solve the resizing issue inside the QGraphicsView
but it didn't make me solve the drop problem of the widget inside the QGraphicsView
.
The post I mentioned proceed to subclass the widget desired and add an additional signals called sizeChanged();
and that is what I did.
I additionally moved the following statement after AND before the QSizeGrip
declaration:
connect(wgt, &CustomTableWidget::sizeChanged, [wgt, proxyControl](){
proxyControl->setRect(wgt->geometry().adjusted(-10, -10, 10, 10));
});
But it didn't cause any change and I still have the bug.
Thanks for pointing in the right direction for solving this problem.
Cause
You create
proxyControl
givingwgt->width()
as a width:However, at that time
wgt
does not have its final geometry.Solution
Setup
proxyControl
after you are done withwgt
.Example
Here is an example I have written for you to demonstrate how the proposed solution could be implemented. Change your
dropEvent
like this:Note: Use
QGraphicsSceneDragDropEvent::scenePos()
to properly position the dropped item.Result
The proposed change gives you the desired result right away: