PDA

View Full Version : Need 2 widgets without a parent to comunicate



boblatino
23rd July 2010, 03:13
I have an appication that has multiple views (Qwidget). The app is not like a regular app that has a main window and all child windows / widgets. It has 2 main windows that are not related. So when you open the app none of them are shown. If an event occurrs (network event piped to a signal), it will show one of them or the other. Both Qwidgets are parentless as they are independant. The problem that I have is that even if the signal is emited, the hidden widget didnt receive it until I hide (via the X) the other widget. When I do so, the slot is called. Is there any way that both QWidgets are parented to some invisible widget? How should I manage this? Here is a simple graphic that depicts the situation attached. I can add some code if needed.


Thanks.
Ramiro

tbscope
23rd July 2010, 06:20
This doesn't make sense to me.
Signals are sent to objects (regardless of being a child item), not child items of a parent.

But what are you trying to do? Maybe there's a better way to do what you want.

ChrisW67
23rd July 2010, 08:44
This sort of thing? Two independent windows counting when they receive signals, and continuing even when you minimise or close either.


#include <QtGui>
#include <QDebug>

class Window: public QMainWindow
{
Q_OBJECT
public:
Window(QWidget *p = 0): QMainWindow(p) {
count = 0;
central = new QLabel(this);
setCentralWidget(central);
}
public slots:
void receive() {
central->setText(QString::number(count++));
}
private:
QLabel *central;
int count;
};

class Controller: public QObject {
Q_OBJECT
public:
Controller(QObject *parent = 0): QObject(parent) {
m_winA = new Window();
m_winA->setWindowTitle("Window A");
connect(this, SIGNAL(signalA()), m_winA, SLOT(receive()));

m_winB = new Window();
m_winB->setWindowTitle("Window B");
connect(this, SIGNAL(signalB()), m_winB, SLOT(receive()));

m_winA->show();
m_winB->show();

// some fake incoming events
m_count = 0;
connect(&m_timer, SIGNAL(timeout()), this, SLOT(incoming()));
m_timer.start(100);
}
~Controller() {
delete m_winA;
delete m_winB;
}
public slots:
void incoming() {
if (m_count++ % 3 == 0)
emit signalA();
else
emit signalB();
}
signals:
void signalA();
void signalB();
private:
Window *m_winA;
Window *m_winB;

int m_count;
QTimer m_timer;
};

int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Controller c;
return app.exec();
}

#include "main.moc"

boblatino
23rd July 2010, 15:32
Yes, Exactly like that. The problem is that when one of the mainWindows is shown, the timer (or any event in the controller) justs emits the signal correctly, but the other window did not receive it until the one that is currently shown is closed (via the X in the window). The controller is a QObject and the windows are QWidgets so the controller cant be the parent of both windows. In fact they are parentless. Is there any way to put both windows a parent? Is this the correct way of doing this? Can both windows be parentless without affecting Qt way to dispatch events?

Thanks
Ramiro

boblatino
23rd July 2010, 15:41
I have modified a little bit the example, but is almost the same. It will show / hide one or the other QWidget



#include <QtGui>
#include <QDebug>

class Window: public QMainWindow
{
Q_OBJECT
public:
Window(QWidget *p = 0): QMainWindow(p) {
central = new QLabel(this);
setCentralWidget(central);
}
public slots:
void show() {
this->show();
}
void hide() {
this->hide();
}
private:
QLabel *central;
};

class Controller: public QObject {
Q_OBJECT
public:
Controller(QObject *parent = 0): QObject(parent) {
m_winA = new Window();
m_winA->setWindowTitle("Window A");
connect(this, SIGNAL(signalA()), m_winA, SLOT(show()));
connect(this, SIGNAL(signalA2()), m_winA, SLOT(hide()));

m_winB = new Window();
m_winB->setWindowTitle("Window B");
connect(this, SIGNAL(signalB()), m_winB, SLOT(show()));
connect(this, SIGNAL(signalB2()), m_winB, SLOT(hide()));


// some fake incoming events
m_count = 0;
connect(&m_timer, SIGNAL(timeout()), this, SLOT(incoming()));
m_timer.start(100);
}
~Controller() {
delete m_winA;
delete m_winB;
}
public slots:
void incoming() { //Just show one and hide the other with signals.
//The signals are emited but it will not arrive to
//the slot until the other window is hidden.
if (m_count++ % 3 == 0)
{
emit signalA();
emit signalB2();
}
else
{
emit signalB();
emit signalA();
}
}
signals:
void signalA();
void signalB();
private:
Window *m_winA;
Window *m_winB;

int m_count;
QTimer m_timer;
};

int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Controller c;
return app.exec();
}

#include "main.moc"


Thanks
Ramiro

^NyAw^
23rd July 2010, 16:57
Hi,

Why are you using singals and slots? You have access to "m_winA" and "m_winB" pointers that can be used directly:


public slots:
void incoming()
{
if ((m_count++ % 3) == 0)
{
m_winA->show();
m_winB->hide();
}
else
{
m_winA->hide();
m_winB->show();
}
}

boblatino
23rd July 2010, 17:24
Yes, I know that. Its just to depict a simpler case. In my Controller class I have a separate thread that is calling the emit. So the reason to do it is for Qt to process the events in its graphical thread. If I call it directly, QT throws invalid thread exception.

Thanks
Ramiro

boblatino
23rd July 2010, 17:29
This doesn't make sense to me.
Signals are sent to objects (regardless of being a child item), not child items of a parent.

But what are you trying to do? Maybe there's a better way to do what you want.

Hi tbscope, I agree that signal / slot mechanism should be independant of child / parent, but why the Qt thread just doesnt process events for the non visible qwidget? Maybe Im doing something wrong. Is there any other way to do what Im trying to do? All the examples I have found, suppose that you have a main window and all the widgets are parented to it. No one is making 2 main windows parentless. Maybe there is another way to doit.

Thanks
Ramiro

ChrisW67
23rd July 2010, 23:58
I have modified a little bit the example, but is almost the same. It will show / hide one or the other QWidget



#include <QtGui>
#include <QDebug>

class Window: public QMainWindow
{
Q_OBJECT
...
public slots:
void show() { // <<< overrides QWidget::show()
this->show(); // <<< Calls this slot again, infinite loop (ends in segmentation fault on my machine)
}
void hide() { // <<< overrides QWidget::hide()
this->hide(); // <<< Calls this slot again, infinite loop (ends in segmentation fault on my machine)
}
...

The QMainWindow class already has a show() and hide() slot inherited from QWidget that you are overriding. Each of your new slots calls itself in an infinite loop, which is why things go awry here.

You should either

connect to the pre-existing slots by not defining a new show() and hide(),
give your show() and hide() slot different names (or one slot with a parameter), or
make sure your overrides also call QWidget::show() or hide().

public slots:
void show() {
qDebug() << windowTitle() << "show()";
QWidget::show();
}
void hide() {
qDebug() << windowTitle() << "hide()";
QWidget::hide();
}
This last option still was Not Quite Rightâ„¢ for me.

boblatino
24th July 2010, 03:11
Thanks ChrisW67, that names (show / hide) where just to ilustrate the 2 slots, but they have different names in the real application (sorry for my error, I did not take so much care with the names in the example). Im just seaking a valid way to do this (2 qwidgets parentless) or some other way to do it. I need an advice on this matter. Thanks a lot to all of the forum. Its realy a big help.

Thanks
Ramiro

ChrisW67
24th July 2010, 06:08
I don't know what the problem is then. The two top-level windows in the example below hide/show just fine here (if both are hidden simultaneously the example app closes) and their timers are clearly running while hidden.


#include <QtGui>
#include <QDebug>

class Window: public QMainWindow
{
Q_OBJECT
public:
Window(QWidget *p = 0): QMainWindow(p) {
m_count = 0;
central = new QLCDNumber(this);
setCentralWidget(central);

m_timer = new QTimer(this);
connect(m_timer, SIGNAL(timeout()), this, SLOT(tick()));
m_timer->start(100);
}
public slots:
void tick() { central->display(m_count++); }
private:
QLCDNumber *central;
QTimer *m_timer;
int m_count;
};

class Controller: public QObject {
Q_OBJECT
public:
Controller(QObject *parent = 0): QObject(parent) {
m_winA = new Window();
m_winA->setWindowTitle("Window A");
m_winA->move(0,0);

m_winB = new Window();
m_winB->setWindowTitle("Window B");
m_winB->move(200,200);

connect(this, SIGNAL(signalA()), m_winA, SLOT(show()));
connect(this, SIGNAL(signalA()), m_winB, SLOT(hide()));

connect(this, SIGNAL(signalB()), m_winA, SLOT(hide()));
connect(this, SIGNAL(signalB()), m_winB, SLOT(show()));

m_winA->show();
m_winB->show();

// some fake incoming events
m_count = 0;
m_timer = new QTimer(this);
connect(m_timer, SIGNAL(timeout()), this, SLOT(incoming()));
m_timer->start(5000);
}
~Controller() {
delete m_winA;
delete m_winB;
}
public slots:
void incoming() {
if (m_count++ % 2 == 0)
emit signalA();
else
emit signalB();
}
signals:
void signalA();
void signalB();
private:
Window *m_winA;
Window *m_winB;

int m_count;
QTimer *m_timer;
};

int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Controller c;
return app.exec();
}

#include "main.moc"

boblatino
24th July 2010, 20:33
So, could be a problem that a different thread is calling the emit function? Or it just post an event to the Qt GUI thread? In the example above, the timer uses the same thread as the graphical thread.

Thanks
Ramiro

ChrisW67
24th July 2010, 23:36
If the signals are cross-thread then they will be queued rather than the slot executing synchronously. Making the connections at line 37-41 Qt::QueuedConnection as they would be in the threaded scenario does not affect the performance. Perhaps you could produce a small example that more closely matches what you are doing.

boblatino
25th July 2010, 00:03
If the signals are cross-thread then they will be queued rather than the slot executing synchronously. Making the connections at line 37-41 Qt::QueuedConnection as they would be in the threaded scenario does not affect the performance. Perhaps you could produce a small example that more closely matches what you are doing.

Hi ChrisW67, Im using a plain connect so it should be detected and working as a queued connect as shows in the documentation. The next I will do is force a Qt::QueuedConnection instead of relaing in Qt::AutoConnection. On monday I will have a pc with visual studio and I will create an example. Its basicaly the same as the above message, but instead of a QTimer that emits the signals, is a plain OS thread. The thread just dequeues elements and calls emit. Does you see an issue to have 2 widgets parentless that interact? Is this the correct way to doit?


Thanks
Ramiro

wysota
25th July 2010, 02:52
Could you prepare a minimal compilable example reproducing the problem?

boblatino
25th July 2010, 02:57
Could you prepare a minimal compilable example reproducing the problem?

Yes, on monday I will post a compilable example. I now dont have visual studio in my laptop.

Thanks
Ramiro

wysota
25th July 2010, 03:04
I now dont have visual studio in my laptop.
You can use MinGW, we won't mind :)

boblatino
26th July 2010, 16:33
You can use MinGW, we won't mind :)

Here is an example:



class window : public QWidget
{
Q_OBJECT

public:
window(QWidget *parent = 0, Qt::WFlags flags = 0) : QWidget(parent, flags)
{

}

public slots:
void showMe()
{
show();
}

void hideMe()
{
hide();
}

};

class consumerThread : public QThread
{
Q_OBJECT

public:
consumerThread(fifo<int> & f):f(f)
{
start();
}

void run()
{
while(true)
{
int a;
f.pop(a);
if(a == 0)
{
emit showMe1();
emit hideMe1();
}
else
{
emit showMe2();
emit hideMe2();
}
}
}

signals:
void showMe1();
void showMe2();
void hideMe1();
void hideMe2();
private:

fifo<int> & f;

};

class producerThread : public QThread
{
public:
producerThread(fifo<int> & f):f(f)
{
start();
}

void run()
{
while(true)
{
f.push(0);
Sleep(1000);
f.push(1);
Sleep(1000);
}
}
private:

fifo<int> & f;

};

class dispatcher : public QObject
{
Q_OBJECT

public:
dispatcher()
{
consumerThread* consumer = new consumerThread(queue);
producerThread* producer = new producerThread(queue);
window1 = new window();
window2 = new window();

connect(consumer, SIGNAL(showMe1()), window1, SLOT(showMe()));
connect(consumer, SIGNAL(showMe2()), window2, SLOT(showMe()));
connect(consumer, SIGNAL(hideMe1()), window1, SLOT(hideMe()));
connect(consumer, SIGNAL(hideMe2()), window2, SLOT(hideMe()));
}
private:
fifo<int> queue;
window* window1;
window* window2;

};



Thanks
Ramiro

wysota
26th July 2010, 17:21
Starting a thread in its constructor is in general a bad idea. But back to the main issue... you are aware that slots of a QThread subclass are executed in the context of the main thread and not the thread controlled by the QThread object, right? Please also explain what is the point in emitting showMe1() and immediately after that emitting hideMe1().

It could be simpler to explain what effect you want to achieve and we will suggest a better approach.

boblatino
26th July 2010, 17:38
you are aware that slots of a QThread subclass are executed in the context of the main thread and not the thread controlled by the QThread object, right?

No, I didnt know that, but in the real code the thread is a OS thread (via CreateThread).


Please also explain what is the point in emitting showMe1() and immediately after that emitting hideMe1()

It has no point sorry. It should be



emit showMe1();
emit hideMe2();


The idea is an application that has more than 1 top level window and a dispatcher that controls which window is shown. Sometimes it could be no window shown and sometimes 2 at the same time. The issue that we are facing now is that when a widget is hidden and other is put in the front (show), the first time you touch the window (we are using touchscreen), the event is processed by the hidden widget. The second time, the shown widget captures the event normaly. I have called activateWindow also but with no luck. Is there a better approach on this? I dont want to create and close (destroy) the widgets all the time so I have created them once and show / hide accordly. If I create them and destroy every time I dont have this problem.

Thanks
Ramiro

wysota
26th July 2010, 18:06
I don't think we can help you if you show us code that does something different than the one you are actually using...

If the two windows are never shown together maybe you should use a QStackedWidget instead of two widgets?

Anyway... if I wanted to have a mechanism for switching windows, I'd do it more or less like this:

class Dispatcher : public QObject {
Q_OBJECT
public:
void addWindow(QWidget *win) {
m_windows << win;
if(m_current == -1) {
setWindow(0);
}
}
public slots:
void next() {
setWindow(m_current+1 % m_windows.count());
}
void prev() {
setWindow(m_current-1 % m_windows.count());
}
void setWindow(int which) {
if(m_current == which) return;
if(m_current!=-1) {
m_windows.at(m_current)->hide();
}
m_current = which;
m_windows.at(m_current)->show();
qApp->processEvents(); // optionally force event processing (don't unless sure it's required)
}
private:
QList<QWidget *> m_windows;
int current = -1;
};

Then it's just a matter of connecting a signal to one of the slots defined in the dispatcher.

boblatino
26th July 2010, 19:34
Thanks wysota, so that will be another way of show / hide the windows, but why the inactive window (hidden) takes the focus instead of the one that is shown the first time? After just a click the shown window it will regain focus and work. I have also called activateWindow but it doesnt take the control of the focus. Also I dont understand why a hidden window can take the user event of a press.


Thanks
Ramiro

GreenScape
27th July 2010, 01:34
when you have QueuedConnection emit post event. that event must be processed by event loop in concrete widget; because only one parentless widet wokrs with main event loop at same time(the signal is posted but nobody cares, because main event loop processing another widget's events) you should create event loops for both threads.

OR

you should inherit from QDialog and use exec(); instead show(); because exec creates own dialog's event loop.

boblatino
27th July 2010, 01:38
when you have QueuedConnection emit post event. that event must be processed by event loop in concrete widget; because only one parentless widet wokrs with main event loop at same time(the signal is posted but nobody cares, because main event loop processing another widget's events) you should create event loops for both threads.

So I must create 2 separate threads one per window? That will cause synchronization issues. Is possible to create multiple loops without a new thread?

Thanks
Ramiro

GreenScape
27th July 2010, 01:41
no, u just should to create event loop for each widget.

example:

create 2 threads, inside each thread create widget, and event loop.
this will look like 2 applications, each widget in its own thread, but in fact it will be one app

wysota
27th July 2010, 04:15
create 2 threads, inside each thread create widget, and event loop.
this will look like 2 applications, each widget in its own thread, but in fact it will be one app

If you do that your app will crash. Why? Search google (or the forum) for Qt+widget+thread. I'm a bit tired repeating this over and over...

boblatino
27th July 2010, 15:06
If you do that your app will crash.

I also have the same idea, only one thread should be the graphical thread. So how can I solve the problem:


inactive window (hidden) takes the focus instead of the one that is shown the first time? After just a click the shown window it will regain focus and work.

Thanks
Ramiro

wysota
27th July 2010, 15:11
So how can I solve the problem
First determine what exactly happens. It could be you are not letting the events be processed and something doesn't know it should release (or gain) focus, i.e. you can't have a focus on a widget which is hidden, so if you do:

widget->show();
widget->setFocus(Qt::OtherFocusReason);
it will probably not work but this might:

widget->show();
QCoreApplication::processEvents();
widget->setFocus(Qt::OtherFocusReason);

boblatino
2nd August 2010, 15:18
it will probably not work but this might:

widget->show();
QCoreApplication::processEvents();
widget->setFocus(Qt::OtherFocusReason);

Thanks wysota, it does work now perfectly. I dont know why processEvents is required to avoid sending events to the hidden window but it WORKS! Thanks a lot to all the forum members that have posted here.

Ramiro