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

Calling MainWindow code from QGraphicsItem objects



  • Hi all

    I'm quite new at Qt and having problems with using QGraphicsView, QGraphicsScene and QGraphicsRectItem classes.

    My application consists of a QMainWindow that has a couple of tabs in a QTabWidget. The first tab has a button and a QGraphicsView area. Clicking the button adds a new QGraphicsRectItem into the QGraphicsView area. I tried to sketch the general structure of the application in this picture.

    DSC_0524.JPG

    The user clicks on a Box item (that is sub-classed from QGraphicsRectItem) shown on the graphics view. Then information from that particular Box object needs to be shown at the other two tabs as well. Any hints how to do this kind of functionality?

    I tried implementing Box::mouseReleaseEvent() and it receives the clicks correctly, but as far as I know, I can't call any functions located at MainWindow class from Box::mouseReleaseEvent() function.

    If necessary, I can try to make a Minimum Viable Project as I'm not allowed to share the actual project code. Thanks in advance.



  • @art-rasa
    There are many ways to skin a cat, and similarly many ways to implement this :)

    I'm going to go for the following architecture. I'm going to use the main window as the "controller" or "central governor", essentially because it is the level which creates the various tabs and knows what they do. I'm going to have:

    • Mouse-up on graphics item box will emit a signal.

    • FirstTab will have a slot which it connects when it creates each box.

    • That slot will emit a new signal, defined in FirstTab, to say a box was clicked.

    • SecondTab will have a slot to be notified when a box is clicked. There it will do whatever with that information.

    • Main window creates (or knows about) both FirstTab & SecondTab. So it will connect the "box clicked" signal from FirstTab to the "on box clicked" slot in SecondTab.

    Can I leave this to you? In outline:

    • Put an onBoxClicked slot into FirstTab. When creating a Box instance, connect() its mouse-up-clicked signal to FirstTab::onBoxClicked slot.

    • Define a new signal, FirstTab::boxClicked(Box *box). Have FirstTab::onBoxClicked slot go emit this->boxClicked(box). This "forwards" the signal onward.

    • Define a SecondTab::onBoxClicked(Box *box) slot. Have that do whatever from the box on the second tab.

    • In MainWindow do the connect(firstTab, &FirstTab::boxClicked, secondTab, &SecondTab::onBoxClicked).

    If you find you need to now/later to also do something in MainWindow on box click, you can alter the last step to connect to a slot in main window, where you can do some extra work, and have that call secondTab->onBoxClicked(box).

    If you don't like so many slots, you can also chain signals directly: connect(someObject, &SomeObject::someObjectSignal, anotherObject, &AnotherObject::anotherObjectSignal).

    So you end up with signal/slot flow something like:

    Box click signal ->
    FirstTab on box clicked slot ->
    FirstTab box click signal ->
    SecondTab on box clicked slot
    

    As I say, there are many alternative ways of arranging this, but hopefully this one makes sense.



  • @art-rasa

    Hi,
    I've never used QGraphicsView,
    but looking for some signals out there, in QGraphicsScene I've found:

    void changed(const QList<QRectF> &region)
    void focusItemChanged(QGraphicsItem *newFocusItem, QGraphicsItem *oldFocusItem, Qt::FocusReason reason)
    void sceneRectChanged(const QRectF &rect)
    void selectionChanged()

    Connecting your main window to focusItemChanged() or changed(), should meet your need.



  • @art-rasa said in Calling MainWindow code from QGraphicsItem objects:

    I tried implementing Box::mouseReleaseEvent() and it receives the clicks correctly, but as far as I know, I can't call any functions located at MainWindow class from Box::mouseReleaseEvent() function.

    @mpergand is right that in Qt we do this via signals. However, only selectionChanged() (if you are using selections at all) or focusItemChanged() would be useful here. I haven't used focus: mostly it's to do with input methods, I don't know is those are appropriate for your QGraphicsItems. Both of these would only fire when you change what item is clicked on (if they work on clicks), which may or may not be what you want.

    From where you were with your QGraphicsItem::mouseReleaseEvent(). For whatever reason, QGraphicsItem has a mouseDoubleClickEvent(), but no mouseClickEvent(). You can create your own signal for this:

    • Define a mouseClicked() method in the signals: section of the class definition in the .h file.
    • override the mouseReleaseEvent() in Box.
    • There go emit mouseClicked() to emit the signal.
    • Attach your desired slot when you create each Box, e.g. in MainWindow, like:
    MainWindow::onBoxClicked(Box *box)
    {
        // call some method in tab2, passing `box` to it
        // or `emit` your own signal, passing `box` to it, for which you have connected a slot defined in tab2
        //   so as to keep looser coupling between the main window and the tabs
    }
    
    // somewhere in `MainWindow`
    box = new Box();
    connect(box, &Box::mouseClicked, this, [this, box]() { this->onBoxClicked(box); });
    

    [If you are not familiar with it, I am using a C++ lambda for the slot in the connect() so that I can pass an extra parameter, which is not in the signal, to the slot.]



  • Many thanks for your suggestions. I thought the signal & slot mechanism can only be used inside a single class, e.g. to connect a button to a slot.

    Currently the button widget, QGraphicsView, QGraphicsScene, layouts are constructed inside the first Tab widget's constructor.

    FirstTab::FirstTab(QWidget *parent) : QWidget(parent)
    {
        /* Unrelated widgets, pusbuttons, layouts etc. removed */
        pView_ = new QGraphicsView{};
        scene_ = new QGraphicsScene{};
        scene_->setSceneRect(0, 0, 550, 420);
        pView_->setScene(scene_);
        pView_->show();
    }
    

    And here is the slot that creates a new Box object and adds it to the QGraphicsView. It is connected to the released() signal of the pushbutton. Box() constructor takes an int value, which for the time being is just the total number of Boxes.

    void FirstTab::addButton_handler()
    {
        Box * pBox{ new Box(num_boxes_) };
        int x_pos{ 20 + (num_boxes_ % 4) * (BOX_WIDTH_  + 10) };
        int y_pos{ 20 + (num_boxes_ / 4) * (BOX_HEIGHT_ + 10) };
    
        pBox->setRect(x_pos, y_pos, BOX_WIDTH_, BOX_HEIGHT_);
        scene_->addItem(pBox);
        pView_ -> show();
    
        num_boxes_ = scene_->items().count();
    
        /* Can't access MainWindow::onBoxClicked() */
        //connect(pBox, &Box::mouseClicked, this, [this, pBox]() {this->onBoxClicked(pBox);});
    }
    

    But when trying to connect the new Box object's signal into a slot, I once again realized that I can't reach the main window's slot from there. Should I try to move the Box creation and connection part of the code upwards in the hierarchy, into the MainWindow class?

    Here's my project files:
    projectfiles.png

    Mainwindow's constructor:

    MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
    {
        tabWidget_ = new QTabWidget{};
        firstTab_ = new FirstTab{};
        secondTab_ = new SecondTab{};
        thirdTab_ = new ThirdTab{};
    
        tabWidget_ -> addTab(firstTab_, QString{"First tab"});
        tabWidget_ -> addTab(secondTab_, QString{"Second tab"});
        tabWidget_ -> addTab(thirdTab_, QString{"Third tab"});
    
        QVBoxLayout * mainLayout = new QVBoxLayout{};
    
        mainLayout->addWidget(tabWidget_);
    
        centralWidget_ = new QWidget{};
        centralWidget_->setLayout(mainLayout);
        setCentralWidget(centralWidget_);
    
        setWindowTitle(QString{"Main window"});
    
        this->setFixedSize(WINDOW_WIDTH_, WINDOW_HEIGHT_);
    }
    


  • @art-rasa
    There are many ways to skin a cat, and similarly many ways to implement this :)

    I'm going to go for the following architecture. I'm going to use the main window as the "controller" or "central governor", essentially because it is the level which creates the various tabs and knows what they do. I'm going to have:

    • Mouse-up on graphics item box will emit a signal.

    • FirstTab will have a slot which it connects when it creates each box.

    • That slot will emit a new signal, defined in FirstTab, to say a box was clicked.

    • SecondTab will have a slot to be notified when a box is clicked. There it will do whatever with that information.

    • Main window creates (or knows about) both FirstTab & SecondTab. So it will connect the "box clicked" signal from FirstTab to the "on box clicked" slot in SecondTab.

    Can I leave this to you? In outline:

    • Put an onBoxClicked slot into FirstTab. When creating a Box instance, connect() its mouse-up-clicked signal to FirstTab::onBoxClicked slot.

    • Define a new signal, FirstTab::boxClicked(Box *box). Have FirstTab::onBoxClicked slot go emit this->boxClicked(box). This "forwards" the signal onward.

    • Define a SecondTab::onBoxClicked(Box *box) slot. Have that do whatever from the box on the second tab.

    • In MainWindow do the connect(firstTab, &FirstTab::boxClicked, secondTab, &SecondTab::onBoxClicked).

    If you find you need to now/later to also do something in MainWindow on box click, you can alter the last step to connect to a slot in main window, where you can do some extra work, and have that call secondTab->onBoxClicked(box).

    If you don't like so many slots, you can also chain signals directly: connect(someObject, &SomeObject::someObjectSignal, anotherObject, &AnotherObject::anotherObjectSignal).

    So you end up with signal/slot flow something like:

    Box click signal ->
    FirstTab on box clicked slot ->
    FirstTab box click signal ->
    SecondTab on box clicked slot
    

    As I say, there are many alternative ways of arranging this, but hopefully this one makes sense.



  • Now it finally starts making sense. I was able to link the signals and slots together thanks to your explanation.

    I think the signal procedures are like radio transmitters that emit a message at a certain frequency. And a slot is the transmitter that must be tuned to the same frequency with connect() to be useful. But just like in radio, repeater receiver/transmitter pairs (chaining) are needed over long distances (across class boundaries).

    Please excuse my ramblings and many thanks for your helpful, clear explanations.


Log in to reply