Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Stop QGraphicsProxyWidget from forwarding events to the owned widget



  • Hi,
    I'm building a QGraphicsSceneembedding a few already built QWidgets through QGraphicsProxyWidget. The point is, when I receive a signal, those widgets should stop working like widgets and behave like movable items, so that the user can freely adjust their positions.
    I've tried to do something like this in an "experiment project", with limited success. Here's the code of the constructor of my main window:

    MainWindow::MainWindow(QWidget *parent)
    	: QMainWindow(parent)
    	  , ui(new Ui::MainWindow)
    {
    	ui->setupUi(this);
    
    	auto scene = new QGraphicsScene(this);
    	ui->graphicsView->setScene(scene);
    
    	auto widget = new QPushButton("Hello World");
    	connect(widget, &QPushButton::clicked, this, []() { qDebug() << "Button clicked"; });
    
    	// Create the rect to control the widget
    	QGraphicsRectItem * proxyControl = scene->addRect(0, 0, widget->width(), widget->height(), QPen(Qt::black), QBrush(Qt::darkGreen));
    	proxyControl->setFlag(QGraphicsItem::ItemIsMovable, true);
    	proxyControl->setFlag(QGraphicsItem::ItemIsSelectable, true);
    
    	// Insert the widget
    	QGraphicsProxyWidget * proxy = scene->addWidget(widget);
    	proxy->setParentItem(proxyControl);
    	proxy->setPos(widget->width() / 2, widget->height() / 2);
    }
    

    Aside from the weirdness around the widget height/width which needs to be fixed, this solution doesn't stop the button from working, so it's not feasible for me.

    So my idea was to interrupt the chain of event. If I'm not wrong, the chain of event is something like this: QGraphicsView gets the event from the window or from other Qt systems and translates the coordinates for QGraphicsScene -> QGraphicsScene determines which item needs to get the event and forwards it -> the item gets the event; if it's a QGraphicsProxyWidget, event is forwarded to the widget (after proper mapping of coordinates has been made).

    I tried to interrupt the last portion, writing a new type of proxy which forwards event to the widget only if an internal flag is set. In this way, it could behave as a widget or as a standard item. Something like this:
    Header:

    #include <QGraphicsProxyWidget>
    
    class SpecialGraphicsProxyWidget : public QGraphicsProxyWidget
    {
    public:
    	SpecialGraphicsProxyWidget(QGraphicsItem * parent = nullptr,
    							   Qt::WindowFlags wFlags = Qt::WindowFlags());
    
    	void setMovable(bool isMovable);
    
    protected:
    	void mousePressEvent(QGraphicsSceneMouseEvent * event) override;
    
    private:
    	bool m_isMovable;
    };
    

    Cpp:

    #include "SpecialGraphicsProxyWidget.h"
    #include <QDebug>
    SpecialGraphicsProxyWidget::SpecialGraphicsProxyWidget(QGraphicsItem * parent,
    													   Qt::WindowFlags wFlags)
    	: QGraphicsProxyWidget(parent, wFlags), m_isEditable(false)
    {}
    
    void SpecialGraphicsProxyWidget::setMovable(bool isMovable)
    {
    	m_isMovable = isMovable;
    	setFlag(QGraphicsItem::ItemIsMovable, isMovable);
    	setFlag(QGraphicsItem::ItemIsSelectable, isMovable);
    }
    
    void SpecialGraphicsProxyWidget::mousePressEvent(QGraphicsSceneMouseEvent * event)
    {
    	qDebug() << "mousePressEvent triggered";
    	if (m_isMovable) {
    		qDebug() << "handled by QGraphicsItem";
    		return QGraphicsItem::mousePressEvent(event);
    	} else {
    		qDebug() << "handled by QGraphicsProxyWidget";
    		return QGraphicsProxyWidget::mousePressEvent(event);
    	}
    }
    

    This seems to work to a certain extent: the "Button clicked" signal is not emitted. However, doing that I noticed that QGraphicsItem doesn't have a general event(QEvent *) function that dispatches an event to the proper handler (as you can see in the code above I redirected only the mouse press events), which confused me a lot. Digging through the source code, I found out that QGraphicsScene deals with events and dispatches them to the proper handler of the right item (thus cutting one "level"). But before doing that, it uses a virtual QGraphicsItemPrivate::isProxyWidget() (which returns false and is overridden in the proxy to return true) from the "private", inaccessible section of the item, to determine if it has to forward it. I feel that this is the point where I can intervene with the least effort, but in this case it would mean having to recompile Qt library, something I have no experience on.

    To avoid it, I would probably have to reimplement all event handlers - and I'm not even 100% sure it would work. Is there any simpler/alternative solution?


  • Lifetime Qt Champion

    Hi,

    One simpler alternative would be to have an "edit mode" were you would switch your widget proxy with a QGraphicsPixmapItem on which you could set a pixmap from your widget. That will make moving it way simpler and clearly show when your user can modify the content and when they can interact with it.



  • @SGaist said in Stop QGraphicsProxyWidget from forwarding events to the owned widget:

    you would switch your widget proxy with a QGraphicsPixmapItem

    Not sure what you are proposing as a solution. If you mean I have to set a static image (one for every type of widget), so that the image would not show the actual state of the widget, I have two observation:

    • In a normal usage case there will be dozens of widgets on screen, potentially even thousands. I think switching everyone out for a pixmap would require a complete and inefficient rebuild of the scene (and a repaint), unless I keep an "edit mode" scene cached, which is a waste of memory.

    • Basically every widget is displaying information from a model (or a section of it). In edit mode, the user will also be able to change the model referred by the selected widget using a toolbar. It would be very important that as soon as the new model is selected, the user can see the new data fetched, so that it can judge if it likes the overall effect.


  • Lifetime Qt Champion

    1. no, you would update the images when switching to edit mode and hence keep information in sync.

    2. thousands of widgets at the same time ? That sounds bad from a design point of view and even more when using QGraphicsProxyWidgetItem which is a helper but is not optimized for such cases.

    Can you explain what your application does that would require that many movable widgets ?



  • Thousands is a bit of an exageration, a few hundred was already a big number... on a second thought we agreed it wouldn't be a problem to limit the user to see something like 20-30 widget (of the most simple type) at the same time - while scene can still easily hold one/two hundred widgets.

    Anyway, if I understand it correctly, you suggest to call something like QWidget::render(myPixMap) on every widget I have and then keep them in sync with the "real" widget if anything happens. I'm gonna try and see if performance is an issue, especially during the switch to "edit mode".

    Regarding how the app works, imagine a static report with few interactive parts where the user can modify simple pieces of data (which can in turn modify other data displayed and so on, often creating a complex, but finite, chain of updates). A fundamental aspect is that the data displayed and how it is displayed should be completely customizable, since the type, the number of data and their relation/update rules are not known at compile time and have to "programmed" through a very simple interface by the user.

    Simple example: the user say all his data consists of a single integer. Then it goes to the "edit mode", add a proper widget(s) to display the int (let's say it has a choice between a QLCDNumber and a more complex widget made up of a line edit with a proper mask and a label with a description). It can add 10 widget of the same type displaying the same information and position them where he likes.


Log in to reply