Communicating between Widgets without parent casting
-
Hello guys,
I was trying a very simple approach to communicate between widgets in Qt. The method isn't working, while it makes good sense using C point of view. I'd like to discuss it with you.
So I have an OpenGL widget (glwidget), and a control widget (controlwidget), both are objects in a MainWindow object.
My idea is the following: Can I create a class object in MainWindow, that has the variable I wanna pass between the two widgets, pass its reference to both glwidget and controlwidget, and expect the change of value in one of them to change it in the other. The idea is logically feasible since they all control the same variable under the same pointer.The test:
I created a class called SettingsPasser, and did the following in MainWindow:
@
class MainWindow : public QMainWindow
{
Q_OBJECTpublic:
MainWindow(QWidget *parent = 0);
~MainWindow();
ControlWidget *controlWidget;
GLWidget *glWidget;
SettingsPasser *simulationSettings;
...
}
@For GLWidget I did the following:
@
class OpenGLClass : public QGLWidget
{
Q_OBJECTQPoint lastPos; void normaliseVectors(); double maxVectorLength; double minVectorLength; double maxPosition; double minPosition;
public:
explicit OpenGLClass(SettingsPasser &settings, QWidget *parent = 0);
...
@For ControlWidget I did pretty much the same. Now in MainWindow, in the constructor, I created both widgets as follows:
@
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
simulationSettings = new SettingsPasser;
glWidget = new GLWidget (*simulationSettings, this);
controlWidget = new ControlWidget(*simulationSettings, this);
...
@Now with this construction, I should expect a variable change in any of these objects should change it in all the others!!!
The result:
The result is that it happens, and you can see it when debugging, but you can't see its consequences in the program!!!! In the settings class I have a boolean value, which is controlled by a QCheckBox, which is connected to a slot function to change the boolean value in the class-object simulationSettings. A condition in the OpenGL simulation cannot see the change of this variable through an "if" command, while I can see the change happening when I debug the program in the watch list.
Why am I doing all this? because I don't want to have 10 copies of each variable in each widget, and to have stable initial values that are connected with the initial values of the controls in the controlWidget. Does this make sense?
Thank you for any efforts :-)
-
You're looking for the Observer pattern, I guess?
-
I'm not pretty sure what Observer pattern means. I check it in Wikipedia, and it looks like that it's what I'm looking for. Because I want all these hierarchically dependent objects to be informed when a change is applied upon a variable (or a class for this purpose) from any of them.
-
Well, and Qt has signals & slots, of course, which work really well as well for purposes like this.
-
Yes of course.
It's just so puzzling that I don't understand why it happens. If I connect the CheckBox to the variable through the pointer in glWidget (through a slot in glWidget), then glWidget sees it with no problem; whereas if I connect the CheckBox to the pointer that is in the controlWidget (through a slot in controlWidget), glWidget doesn't see it.
It doesn't make sense to me because both widgets have the same pointer value passed from the same parent!!! I mean why can't glWidget see it when it's really changed!! I thought C++ talks directly to the processor and memory, so that it checks what the value on memory is. While the value in memory is changed correctly according to the debugger.
Do you see the missing link in my story?
-
Many. I see a basic lack of how computer programs operate. Sorry, but you will really need to move back to square 1. Get back to studing the basics of C++ and programming in general. Also, get a book on design patterns.
-
Thank you for your replies.
But could you please tell me why it doesn't work in a nutshell? I'm not an engineer but a PhD physicist. I make programs for simulations and programs to serve the very specific purpose of our experiments.
Also would be very nice of you if you could recommend some book for design patterns and maybe a C++ book (or programming book in general) that could explain why my idea didn't work.
-
Think of the problem in terms of program flow. A change on a memory address (a changing variable) does not trigger a code path to start running. You have to make sure that the right code is triggered. There are many, many ways to do that, but in the end, somebody, somewhere will have to call a method on a relevant piece of code that takes action on the new value of your variable.
Signals & slots are a mechanism to trigger code to run because something happened; the observer pattern is another.
-
Hello again,
Today I learned about the keyword "volatile", and I remembered this question, and I would like to ask the following question:
Does using the keyword "volatile" solve the problem I posted in this topic without using signals and slots?
In other words; if my settings object is volatile, would that get all the other widgets to receive the changes when done by another widget?
-
No, I think you did not understand whatever you read about volatile. For the work you do with C++, you don't need it either.
The volatile keyword has a very specific use for the optimization that compilers do on your code in the context of multi threading or working with IO registers. It basically tells the compiler that the memory address the variable is pointing at may get changed by something outside of the current process (or thread), and that the compiler therefore cannot make assumptions about it keeping a certain value.
For instance:
@
int someValue(2);
volatile int someVolatileValue(42);//some code that does not use someValue or someVolatileValue
if (1 == someValue) {
// any code here (and the if branch itself) will be optimized out by any half-decent compiler
}
if (1 == someVolatileValue) {
// this code will not be optimized out!
}
@ -
Thank you for your reply.
That's exactly what I had understood about volatile variables. Let me expain why I think volatile could make this work, and please correct me if I'm wrong:
What I saw in my program, is that a change in an object cannot be seen by other objects (but only by the debugger), unless some call like signals and slots is done.
So, when I use volatile, I expect the object to be always taken/modified in the memory, and not in the CPU registers; which means (since memory management in C++ is low-level) that if I use a widget to change some value in the memory, which is under a volatile variable (and which I can see that it's changed in the debugger), I will be able to read this memory address with its new changed value through the other widgets, since the volatile keyword blocked the possibility for it to lie in the CPU registers.Does this argument make any sense?
-
-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.
-
I see you've just created a follow-up post on the volatile issue:
Volatile is only interesting when the variable value in memory can change externally. Externally means here, by another process or thread, not just by some other object in the same thread, as probably in your case.
Whether volatile might have anything to do with your problem can be answered easily: Do you change the settings object in a different thread than read from the object?I, too, don't think volatile plays a role here, especially if the phenomenon of "not changing setting variable" happens consistently across changes in the code and recompiles. If it was volatile, I'd expect the settings change to have effect most of the time, only sometimes not (in the seldom events where settings writing and reading happen simultaneously).
-
[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?