PDA

View Full Version : Any alternatives to QThread and QProcess for blocking operations in main GUI thread?



mass85
22nd December 2011, 16:59
I am looking for an alternative to QThread and QProcess to perform blocking operations in main GUI thread in such a way that whenever user presses key during processing, corresponding QKeyEvent is discarded. I paste a snippet of code that presents what I want to achieve. But it does not work as I expect, I explain later why.


void MyWindow::keyPressEvent(QKeyEvent* keyEvent)
{
if (keyEvent->key() == Qt::Key_F1)
{
PleaseWait waitDlg; // a window covering whole screen and doing nothing
waitDlg.SetDetails("Please wait while processing...");
waitDlg.setWindowModality(Qt::ApplicationModal);
waitDlg.show();
QCoreApplication::processEvents();

int status = processData1();
QCoreApplication::processEvents();

if (status)
{
if ( message_box::ask("Question") ) // it opens modal dialog and calls exec()
processData2();
else
processData3();

QCoreApplication::processEvents();
}

processPostActions();
}
}

Unfortunately when user presses keys during processData1() execution, QKeyEvent objects are not generated, that's why calling QCoreApplication::processEvents() does not have any effect except execution of paint events. QKeyPress events are generated, probably in exec() of message_box. This results in QKeyEvent being handled in message_box dialog - it is unexpected behaviour.

I run my application on Linux on ARM which is pretty slow in a comparison to PC. That's why similar problem occurs whenever I open new dialogs. You can press key which triggers opening of new dialog and before it is actually displayed (it can take up to 1s) press another key, which will be handled in the new dialog - it is again unexpected and not acceptable behaviour.

Is there a way to affect somehow QT keyboard driver (or however is called the thing which creates QKeyEvents) and erase all queued input keys that come from Linux keyboard driver?

amleto
22nd December 2011, 19:00
you are going about it wrong. of course after you have shown waitDlg, that will have the focus and keypress events should be passed there, not MyWindow anyway!

Your problem is that your are doing a workflow inside a keypress handler!

your keyPressEvent should be more like


void MyWindow::keyPressEvent(QKeyEvent* keyEvent)
{
if (keyEvent->key() == Qt::Key_F1)
{
emit SomeSignal();
}
}

and you connect the signal to some slot(s) that control workflow for your dialogs.

In your slot you could set some member variable to guard against multiple entry whilst one workflow is still running. Or use a state machine to get things more tightly controlled.

mass85
22nd December 2011, 22:34
Your problem is that your are doing a workflow inside a keypress handler!

It is not my problem, it is my intention.

I know that I can have this things done by usage of signals and QThread or QProcess. But then my code gets more complicated (it would be nice to implement some kind of coroutines for code simplification - are there coroutines in QT?). Everything I need is just to block all input, discard all key events while I'm performing task.

What do you think about the second half of my question - concerning long lasting opening of dialogs and key events that are not welcomed in new window? To be clear I don't perform any long operations in constructors of dialogs, I just setup UI. Later I call exec() on constructed dialog and that's it, I have to wait up to 1second to see new dialog on the screen... Within this time I can press keys and unfortunately corresponding events will be handled in the new dialog, although IMHO they should be discarded. I guess this problem can not be solved by QThread.

amleto
23rd December 2011, 00:55
It is not my problem, it is my intention.


More fool you.

You have a bad case of 'fix the symptoms, not the problem'.

I guess you can take a horse to water, but you cannot make it drink. Good day.

Spitfire
23rd December 2011, 11:16
I agree with amleto.

You're doing it wrong.

You're using QCoreApplication::processEvents() to keep your UI responsive while doing some lenghty tasks.
Move that tasks to separate thread and connect thread's finished() signal with dialog hide() or some other signal to remove dialog when thread has finished.
Also crate dialog using new() and keep a pointer to it in private variable. Then you're key handler looks something like that:

if ( !this->dialog_pointer && keyEvent->key() == Qt::Key_F1)
{
//do something
}This way even if the event is delivered it will be discarded as long as dialog is there.

There's several better ways than the one you're taking.

mass85
23rd December 2011, 12:52
You're doing it wrong.

You're using QCoreApplication::processEvents() to keep your UI responsive while doing some lenghty tasks.

This pattern is described in QT documentation, that's why IMHO we should not assume it is wrong.


This way even if the event is delivered it will be discarded as long as dialog is there.

The problem is that it is not delivered there, neither to waitDlg. It is created much later after actual key press happened and it is delivered to message_box.

Unfortunately both of you don't seem to notice problem I described. I need a way of discarding keys that were pressed while application was switching dialogs.

amleto
23rd December 2011, 13:02
it's simple. When doing a long process and user inputs should be ignored, put the process in a thread and show a modal progress dialog - that dialog will eat all the events. Unfortunately you don't seem to recognise help when it is given.

Spitfire
23rd December 2011, 13:05
That pattern may be desribed in QT docummentation and I'm not saying it's wrong, but its application has to be correct, IMHO it's not in this case.

You're overcomplicating what you want to do or describing it wrong.

As to the discarding, if you'd read my example code you'd see that it would do exacly that.
Simple flag put in correct place can solve your issue, noting more is requried.

mass85
23rd December 2011, 14:19
Then what is the right design pattern for thing as simple as opening of new window? Consider this code:

void MainWindow::keyPressEvent(QKeyEvent* keyEvent)
{
if (keyEvent->key() == Qt::Key_F1)
{
HelpDialog help(this);
help.exec();
}
}

If in MainWindow you press quickly F1 three times, the first QKeyEvent will cause to exec HelpDialog, but the other two events will be handled in HelpDialog, which is totally unacceptable, because it may cause some random actions that user is not aware of. Please don't say that it is pointless to press one button three times and that noone will do it, because you can do it just by accident. But there is a better reason for doing such a thing than accident. When process of displaying new window lasts long (let's say 1s) user doubts if the keyboard/application detected if key had been pressed and then he presses it again.

amleto
23rd December 2011, 16:07
statemachine. Like I already said, back in post 2!

for this simple example you would have three states.

1) waiting
2) starting
3) running

You can only go from state to state in order: 1->2->3->1 etc.

Default state is waiting. As soon as f1 is pressed, state is changed to 2). In state 2), keypress does nothing.

State 2 does the making of new form and exec(). As soon as exec is called, state is changed to running.

As soon as exec finishes, state changes back to 1.

mass85
23rd December 2011, 18:47
If I understand your idea, you want to run exec in QThread. As far as I know QThread is just a worker thread and you cannot use any widgets in it.

amleto
23rd December 2011, 19:26
No, it has nothing at all to do with threads. Where did I mention threads? I mentioned state machine, didn't I?

and you can 'use' any widget you want in a thread, but you can only make widgets in the main thread.

mass85
23rd December 2011, 20:02
Then what is this state machine for? If you do this in main Gui thread you don't need to remember current state, everything happens consecutively like in my last example of code.

amleto
23rd December 2011, 22:01
your code has a problem when user keep pressing buttons. using a statemachine can resolve this problem. Is that the third time I have said it now?

mass85
24th December 2011, 11:17
You are still not explaining how state machine could solve this problem.

I think you are missing the fact that generation and delivery of QKeyEvent does not happen in background. There is no concurrency between construction of window and delivery of QKeyEvents. Here is the flow:

1. QKeyEvent is dispatched to MainWindow.
--- in the background user presses another two keys ---
2. keyPressedEvent constructs HelpDialog.
3. New event loop is started by calling exec() on modal dialog.
4. In the new loop Linux keyboard driver's output buffer is checked and two new keys are found. (actually I'm not sur how it happens, that's one of the things I'm trying to figure out).
5. Two QKeyEvent objects are generated for both of new keys.
6. The first is dispatched to HelpDialog which has focus now.
7. Later the second one will be dispatched to widget with focus (who knows which).

This is how it works (more or less) in my system.

amleto
24th December 2011, 13:07
Sorry, for some reason I have been thinking that you had a problem with too many events sent to the main window.

Anyway, your problem is purely down the event handler (or something else on the main thread) taking up too much time. It is blocking the main thread from handling events quickly, and that is why a key press on mainwindow is getting delayed and sent to another window.

Move heavy work out of the gui thread - it should not be there anyway.