I want to put a QWidget
into a QGraphicsView
and make the widget selectable and movable by using QGraphicsProxyWidget
.
(This works perfectly for QGraphicsRectItem
, QGraphicItem
etc.)
This is the code I'm using currently:
// Create new QGraphicsScene and assign to graphicsView
scene = new QGraphicsScene(this);
ui->graphicsView->setScene(scene);
// Create widget and add to scene
MyWidget *widget = new MyWidget;
QGraphicsProxyWidget *proxy = scene->addWidget(widget);
// Make selectable
proxy->setFlag(QGraphicsItem::ItemIsSelectable, true);
// Make movable
proxy->setFlag(QGraphicsItem::ItemIsMovable, true);
The widget is displayed correctly, but it is neither movable nor selectable!
Any help would be greatly appreciated ;)
I was asking myself the same question as you did here. Before I go on with the solution I came up with after several hours of struggling with this issue I have to mention that in general adding widgets to the scene is not a good idea if you want to have a large amount of those in it. You can read about the issue that come with this here.
Now back on track. Here are a couple of the things that arise as great issues when trying to implement the thing you want to:
mouseMove()
and mouseHover()
of your widget proxy you will screw up the way the widget's UI component behave.How do we do that? Well, directly this seems to be impossible. However I came up with a simple solution that gets the work done - combine a QGraphicsProxyWidget
with a QGraphicsItem
!
In order to preserve the UI components' functionality in your widget you need to add the proxy as a child to the graphics item. The graphics item (the parent of the proxy) on the other hand will cover the selection and moving part.
Here is a small demo (I do not exclude bugs since this is something that I'm currently researching myself and most of the time I use PyQt instead of C++ Qt):
scenewithmovableproxies.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SceneWithMovableProxies</class>
<widget class="QWidget" name="SceneWithMovableProxies">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>SceneWithMovableProxies</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QGraphicsView" name="graphicsView"/>
</item>
</layout>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>
main.cpp
#include "scenewithmovableproxies.hpp"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
SceneWithMovableProxies w;
w.show();
return a.exec();
}
scenewithmovableproxies.hpp
#ifndef SCENEWITHMOVABLEPROXIES_HPP
#define SCENEWITHMOVABLEPROXIES_HPP
#include <QWidget>
#include <QPoint>
namespace Ui {
class SceneWithMovableProxies;
}
class SceneWithMovableProxies : public QWidget
{
Q_OBJECT
public:
explicit SceneWithMovableProxies(QWidget *parent = 0);
~SceneWithMovableProxies();
private:
Ui::SceneWithMovableProxies *ui;
void addWidgetToScene(QPoint initPos);
};
#endif // SCENEWITHMOVABLEPROXIES_HPP
scenewithmovableproxies.cpp
#include "scenewithmovableproxies.hpp"
#include "ui_scenewithmovableproxies.h"
#include "scenewidgetitem.hpp"
#include <QGraphicsProxyWidget>
SceneWithMovableProxies::SceneWithMovableProxies(QWidget *parent) :
QWidget(parent),
ui(new Ui::SceneWithMovableProxies)
{
ui->setupUi(this);
ui->graphicsView->setRenderHint(QPainter::Antialiasing);
ui->graphicsView->setScene(new QGraphicsScene(this));
// Add widget proxies + their parenting graphics items to scene
addWidgetToScene(QPoint(10, 10));
addWidgetToScene(QPoint(300, 100));
addWidgetToScene(QPoint(200, 200));
}
SceneWithMovableProxies::~SceneWithMovableProxies()
{
delete ui;
}
void SceneWithMovableProxies::addWidgetToScene(QPoint initPos)
{
// Create a widget
SceneWidgetItem *widget = new SceneWidgetItem();
// Create the graphics item that will be used to move the widget around the screen as well as be selectable (for example in case we want to delete a widget that is in the scene)
// Depending on the position of the graphics item relative to its widget proxy you can adjust the size and location of both
QGraphicsRectItem *proxyControl = ui->graphicsView->scene()->addRect(initPos.x(), initPos.y(), widget->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
proxyControl->setFlag(QGraphicsItem::ItemIsMovable, true);
proxyControl->setFlag(QGraphicsItem::ItemIsSelectable, true);
// Create the proxy by adding the widget to the scene
QGraphicsProxyWidget * const proxy = ui->graphicsView->scene()->addWidget(widget);
// 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);
// proxyControl->setRotation(45); // Because the widget is a child of the graphics item if we do some sort of transformation to it, the change will be propagated to the widget too!
}
scenewidgetitem.hpp
#ifndef SCENEWIDGETITEM_HPP
#define SCENEWIDGETITEM_HPP
#include <QWidget>
#include <QVBoxLayout>
#include <QCheckBox>
#include <QComboBox>
#include <QLabel>
#include <QPushButton>
class SceneWidgetItem : public QWidget
{
Q_OBJECT
QVBoxLayout *layout;
QLabel *label;
QCheckBox *checkbox;
QComboBox *combobox;
QPushButton *resetButton;
public:
explicit SceneWidgetItem(QWidget *parent = 0);
~SceneWidgetItem();
signals:
public slots:
void reset();
};
#endif // SCENEWIDGETITEM_HPP
scenewidgetitem.cpp
#include "scenewidgetitem.hpp"
// Create a widget with whichever UI components you like
SceneWidgetItem::SceneWidgetItem(QWidget *parent) : QWidget(parent)
{
layout = new QVBoxLayout(this);
checkbox = new QCheckBox("Enable proxy", this);
checkbox->setChecked(true);
combobox = new QComboBox(this);
combobox->addItem("---");
combobox->addItem("Item 1");
combobox->addItem("Item 2");
combobox->addItem("Item 3");
label = new QLabel(this);
label->setText(combobox->itemText(0));
resetButton = new QPushButton("Reset", this);
// Maybe add some signals :P
connect(checkbox, SIGNAL(toggled(bool)), combobox, SLOT(setEnabled(bool)));
connect(checkbox, SIGNAL(toggled(bool)), resetButton, SLOT(setEnabled(bool)));
connect(resetButton, SIGNAL(clicked(bool)), this, SLOT(reset()));
connect(combobox, SIGNAL(currentIndexChanged(QString)), label, SLOT(setText(QString)));
layout->addWidget(checkbox);
layout->addWidget(label);
layout->addWidget(resetButton);
layout->addWidget(combobox);
// Resizing the widget to its layout's content is very important. If
// you don't do that the parenting graphics item will not visually fit
// to its child widget and you will get a mess
resize(layout->sizeHint());
setLayout(layout);
}
SceneWidgetItem::~SceneWidgetItem()
{
}
void SceneWidgetItem::reset()
{
combobox->setCurrentIndex(0);
label->setText("---");
}
Here are some screenshots:
Initial view
Two out of three selected
Moving
Rotating
Interacting with widget - using the QComboBox
Interacting with widget - using the QCheckBox
Now due to the nature of QGraphicsItem
(as well as QGraphicsProxyWidget
) there are some issue the most annoying of which is the overlapping
Overlapping
However overlapping can be relatively easily avoided by using collision detection and basically not allowing overlapping at all. Since QGraphicsProxyWidget
can also use the QGraphicsItem
function collisionWithItem(...)
you can implement a mechanism for handling this situation. Since I'm new to QGraphicsScene
and all that (started 2 days ago while answering a question here on SO :D) there might be some sort of integrated machanism in QGraphicsScene
itself to handle this.
Distortion
In the rotation screenshot you might have noticed that there is some visual weirdness going on with what were previously perfect looking straight lines. These are the so called jaggies. Currently I don't know how to get rid of those. I have tried using high quality antialiasing but the results is even worse than with just antialiasing (QPainter::Antialiasing
).
Futher research
I am actually currently working on a small project of mine where I want to create a composite-node-based UI for image processing. Looking this topic up online always returned solutions where people were using simple QGraphicsItem
s and the node configuration itself was partially outsourced to external to the QGraphicsViewer
widgets. You can use the design I have described above and add more to it depending on what you want to do later on. Multiple QGraphicItem
s can also be attached to the widget proxy. You can go crazy with this but remember that there is a performance impact (read the blog post I've linked at the beginning of my answer again and again).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With