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.
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.
-
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:
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 intoFirstTab
. When creating aBox
instance,connect()
its mouse-up-clicked signal toFirstTab::onBoxClicked
slot. -
Define a new signal,
FirstTab::boxClicked(Box *box)
. HaveFirstTab::onBoxClicked
slot goemit 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 theconnect(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 callsecondTab->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.
-
-
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.
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.
Hi,
I've never used QGraphicsView,
but looking for some signals out there, in QGraphicsScene I've found:void changed(const QList<QRectF> ®ion)
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.
-
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.
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 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) orfocusItemChanged()
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 yourQGraphicsItem
s. 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 amouseDoubleClickEvent()
, but nomouseClickEvent()
. You can create your own signal for this:- Define a
mouseClicked()
method in thesignals:
section of the class definition in the.h
file. override
themouseReleaseEvent()
inBox
.- There go
emit mouseClicked()
to emit the signal. - Attach your desired slot when you create each
Box
, e.g. inMainWindow
, 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.] - Define a
-
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:
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_); }
-
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:
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 intoFirstTab
. When creating aBox
instance,connect()
its mouse-up-clicked signal toFirstTab::onBoxClicked
slot. -
Define a new signal,
FirstTab::boxClicked(Box *box)
. HaveFirstTab::onBoxClicked
slot goemit 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 theconnect(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 callsecondTab->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.