Communicating between Widgets without parent casting
-
[quote author="DerManu" date="1336483777"]I strongly disagree with Andre.
Signals/Slots is not appropriate for connecting your controlWidget with your rendering mechanism as a replacement for your settings object, for many reasons. First of all, it lacks state, which might become necessary some day (think of queuing settings-changes or saving/loading different presets). It is very cumbersome and error-prone creating all the signals/slots and managing the connections, especially when your simulation grows to more objects that need to see or even interact with the settings. You'd basically have all your setting variables scattered all over the place, connected wildly with signals/slots. A central settings object is the correct way to go. Since I work on numerical physics myself, I can say that the observer pattern is well suited for the task. It's especially easy to extend the simulation to multiple cores (which you'll surely want), even in non-shared memory architectures, by extending the capabilities of the settings object.About your problem: Let's make sure we're talking about the same thing here. Andre suspects you expect the change of the pointed-to variable to automatically invoke a method in the rendering class. As he already pointed out, this is not the case.
I'm not sure you're actually saying that. Your rendering class surely does something continously, i.e. render a frame via a QTimer timeout-slot. In this slot, you check the current settings state and render accordingly. In that setup, you indeed should see the effects of the variable change, even if it was made somewhere outside the rendering class, provided you actually transfer the settings pointer and didn't copy the settings object by value accidentally.
From this point on we can only speculate, because we don't see more code of yours. You could start by turning "SettingsPasser &settings" in the rendering class ctor to "SettingsPasser *settings" in order to prevent flipping back and forth between pointer and reference semantics - maybe you accidentally invoke a copyconstructor/assignment operator there.
To rule this out completely, you may temporarily add this to the private section of your settings object:
Q_DISABLE_COPY(SettingsPasser)
This will cause a compile time error when a SettingsPasser object is copied (makes copyctor and assignment private).If you indeed want notifications to be sent to the objects dependant on the settings, this is the place where signals/slots are appropriate, but between the settings object and the simulation/rendering objects only. Possibly even reduced to one signal "settingsChanged" and one slot "updateSettings". So when your controller changes the settings, it may call updateSettings which in turn emits settingsChanged. All objects interested in settingschanges can listen to this signal and do whatever they do with new settings. Of course you can inflate this all the way to setter-slots for every single setting and signals for every single setting. Whether this is necessary depends on your needs and the complexity of the final application.
Note: If you're aiming for HPC, i.e. heavy multithreading, try to avoid signals/slots during the simulation at all costs. In the setup phase where settings are loaded/evaluated/forked, signals/slots are fine.[/quote]
Thank you for your reply.
Your last suggestion is exactly what I do right now. I have an update function for each widget, and when I pass a SettingsPasser object to it, it updates its controls according to the data in that object. But unfortunately the update function copies the new settings to the new widget, where every widget has a copy of the object. Whenever some setting changes in one Widget, a signal is sent to update the (relevant) settings in the other widgets.
Actually, in my first try for the observer approach, I was seeing the variables getting changed by the debugger, but the program couldn't see it; that's why I was looking for an answer to this problem, and till now I can't understand why the debugger can see the change and not my widget. I'm thinking now of volatile variables, and I don't know if this is the right way to go. I tried converting everything to pointers where I used the new operator only once, to make sure that there are no other copies... and yet it didn't work.
-
[quote author="TheDestroyer" date="1336485177"]But unfortunately the update function copies the new settings to the new widget, where every widget has a copy of the object. Whenever some setting changes in one Widget, a signal is sent to update the (relevant) objects in the other widgets.[/quote]That's not ideal as it recreates the decentralized signal/slot network I was warning about earlier.
[quote author="TheDestroyer" date="1336485177"]I was seeing the variables getting changed by the debugger, but the program couldn't see it[/quote]Well there's clearly a bug in your program (haha). What I mean is this is not expected behaviour when sharing pointers between different objects. Your approach of injecting a pointer to the one-and-only central settings object is sensible and should in my opinion be favored over the "every widget has its own settings copy" approach.
About the bug, we can only speculate without code. If you like you could post the code that creates/stores the one-and-only settings object, the code that injects the settings object pointer to the other objects/widgets, and the code inside the widget that supposedly doesn't see the new settings values when it accesses its pointer to the one-and-only settings object. -
[quote author="DerManu" date="1336483777"]I strongly disagree with Andre.
Signals/Slots is not appropriate for connecting your controlWidget with your rendering mechanism as a replacement for your settings object, for many reasons. [/quote]
I guess you missed that my very first suggestion was to look into using the Observer pattern... -
Thanks for your reply.
The project is closed now and the program is done. With the wrong approach, yeah; but I would like to get the right answer for future reference.
Well, let me assure you, in my single copy pointer-injection approach, there was no mistakes at all. Trust me on that. I had only a single copy of the pointer and all widgets were reading from it, and yet it didn't work. I use this approach all the time in other applications. I use very complicated polymorphic classes and I always register pointers for automated update; it's not my first time. But it only made problems in Qt widgets. I tried every way to debug it, but no use. Something was preventing the variable to get updated in the widget.
-
[quote author="Andre" date="1336487338"]I guess you missed that my very first suggestion was to look into using the Observer pattern...[/quote]Oh, I interpreted your first and "second":https://qt-project.org/forums/viewreply/74740/ post as saying signal/slots were more appropriate than observer pattern for this situation, sorry. (I strongly agree with you, now ;)
-
[quote author="TheDestroyer" date="1336487441"]Well, let me assure you, in my single copy pointer-injection approach, there was no mistakes at all. Trust me on that.[/quote]Okay. I guess you've witnessed a miracle then...um...hallelujah? ;)
-
Well, if this can never happen unless there's a bug in my code, how do you explain Andre's response? I think he would've suggested there's a problem in my code if there really would be, don't you think?
-
[quote author="TheDestroyer" date="1336489463"]Well, if this can never happen unless there's a bug in my code, how do you explain Andre's response? I think he would've suggested there's a problem in my code if there really would be, don't you think?[/quote]
I think "this post of his":http://qt-project.org/forums/viewreply/74751/ implies that, but I guess he may explain what he thinks the source of the problem is in follow-up posts.For me this is a question of probabilities. Either there's a tremendous bug with catastrophic effects in GCC (think about it, this problem doesn't occur in one of a million runs but consistently!) and nobody else has noticed it yet. Or there's a glitch in your code that you've just overlooked.
When I understand you correctly, you have used the pointer injected observer pattern in projects before, and it worked, right? -
The thing is that the way those GUI's work is different. That's why I could expect a different behaviour.
Yes, this approach works when I use it outside Qt. But with GUIs of Qt it won't work. No one knows how those meta files of the GUIs are managed..., and how they take information from other objects.
-
[quote author="TheDestroyer" date="1336490799"]The thing is that the way those GUI's work is different. That's why I could expect a different behaviour.[/quote]Not in such a fundamental way. A QWidget subclass still is a normal C++ class after the moc run, and when it has a pointer member you can do with it whatever you can also do in a non GUI class.
[quote author="TheDestroyer" date="1336490799"]No one knows how those meta files of the GUIs are managed..., and how they take information from other objects.[/quote]That's not right, what the meta object compiler does is relatively transparent when you look at the outputted moc_ files. No magic going on, it just adds some homebrew RTTI class and boilerplate code to every QObject class to make sigs/slots work. Specifically, it does not touch your settings pointer member.I've tried it. Here are three files: main.pro, main.h and main.cpp:
main.pro:
@QT += core gui
TEMPLATE = app
HEADERS += main.h
SOURCES += main.cpp@
main.h:
@#ifndef HEADER_H
#define HEADER_H
#include <QtGui>class Settings
{
public:
Settings():intValue(0), boolValue(false) {};
~Settings() {}
int intValue;
bool boolValue;
};class QTestWidget : public QWidget
{
Q_OBJECT
public:
explicit QTestWidget(QWidget *parent, Settings *settings);
protected:
virtual void paintEvent(QPaintEvent *event);
private:
Settings *mSettings;
QTimer mRenderTimer;
};class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow() { delete mSettings; }
private slots:
void changeBoolSetting();
void changeIntSetting();
private:
Settings *mSettings;
};#endif // HEADER_H@
and main.cpp:
@#include "main.h"int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}// === Implementation of MainWindow, QTestWidget and Settings ===
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
mSettings(new Settings)
{
setGeometry(50, 50, 100, 110);
QTestWidget *tw = new QTestWidget(this, mSettings);
QPushButton *b1 = new QPushButton(this);
QPushButton *b2 = new QPushButton(this);
tw->setGeometry(5, 5, 90, 70);
b1->setGeometry(25, 75, 25, 25);
b2->setGeometry(50, 75, 25, 25);
connect(b1, SIGNAL(clicked()), SLOT(changeBoolSetting()));
connect(b2, SIGNAL(clicked()), SLOT(changeIntSetting()));
}void MainWindow::changeBoolSetting()
{
mSettings->boolValue = !mSettings->boolValue;
}void MainWindow::changeIntSetting()
{
mSettings->intValue += 1;
}QTestWidget::QTestWidget(QWidget *parent, Settings *settings) :
QWidget(parent),
mSettings(settings)
{
connect(&mRenderTimer, SIGNAL(timeout()), this, SLOT(update()));
mRenderTimer.start(500);
}void QTestWidget::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event)
QPainter painter(this);
painter.drawRect(rect().adjusted(0, 0, -1, -1));
painter.drawText(rect(),
Qt::AlignCenter,
QString::number(mSettings->intValue)+"\n"+
QString(mSettings->boolValue ? "true" : "false"));
painter.drawText(0, 13, QDateTime::currentDateTime().toString("hh:mm:ss:zzz"));
}@run
qmake; make; ./main
and you'll see that it works as expected. Click the two buttons and see the QTestWidget update its display (in 500ms render-intervals), depending on it's injected Settings pointer.