Qt - Block user interface while computer is making a move

Qt - Block user interface while computer is making a move



To learn Qt I decided to build a TicTacToe program. The UI is simple enough but I've stumbled on a problem that I can't solve. I wanted the human user to be able to play against the computer, as such, I need the UI to be blocked while the cpu is making the calculations. I've tried working with setEnabled but it's not working the way I wanted and I went with just put a Qframe in the front and use the hide() and show() functions to block the interaction with the user. The problem is that it isn't working. When I click on the QPushButton the cell is updated and call the show function. Then I do whatever calculations I need and when it is finished I just call hide(). Below is an example code.


void TicTacToe_Gui::on_cell11_clicked()


ui->cell11->setText("wait...");
ui->cell11->repaint();
ui->frontPanel->show();

for(int i=0; i<=1000000000; i++)


ui->cell11->setText("done");
ui->frontPanel->hide();




where the cell is a QPushButton.



Well, when the code in the for loop is running and I click on the other cells, the animations of the other buttons (I mean, the push animation) are not presented and it seems the UI is blocked but as soon as the code stops the other buttons are updated as if clicked. Check images below
enter image description here



enter image description here



I know similar questions have been asked many times and I've read most of them but I can't seem to make it to work. Can somebody help me?






What exactly does the following mean: I've tried working with setEnabled but it's not working the way I wanted, What problem has caused you? What do you want?

– eyllanesc
Nov 15 '17 at 2:47







You could share your code to understand better through github, gist or similar.

– eyllanesc
Nov 15 '17 at 2:49




1 Answer
1



To not handle many signals and slot of several buttons it is advisable to use QButtonGroup and connect the buttonClicked signal to a single slot, this class can associate a button with an id, with that id we can obtain the row and column of the button:


group = new QButtonGroup(this);
for(int i=0; i < 3; i++)
for(int j=0; j < 3; j++)
board[i][j] = TicTacToe_State::N;
QToolButton *btn = new QToolButton(this);
btn->setFixedSize(100, 100);
btn->setStyleSheet("QToolButton::disabledcolor:black");
group->addButton(btn, /*id*/ 3*i+j);
lay->addWidget(btn, i , j);


connect(group, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), this, &TicTacToe_Gui::onClicked);



The problem is caused because the heavy task you perform blocks the main-loop of the GUI, for heavy tasks it is advisable to use a secondary thread and when some result is obtained, send it to the main thread. In Qt there are several alternatives but the simplest for this case is to use QtConcurrent::run with QFutureWatcher and a QEventLoop, for this example I will create a structure that defines the result of the heavy algorithm:


QtConcurrent::run


QFutureWatcher


QEventLoop


struct TicTacToe_Result

int row;
int col;
;
enum TicTacToe_StateN, X, O;
TicTacToe_State board[3][3];



The struct is the output of the algorithm, so if you want to add more fields to the result just add a new member. The enumeration describes the possible states of each button, and the array is the matrix that stores the information of the button board, this is a member of the class, so that the lambda function can access is passed &.


void TicTacToe_Gui::onClicked(int id)

QAbstractButton *btnO = group->button(id);
btnO->setText("O");
int row = id/3;
int col = id%3;
board[row][col] = TicTacToe_State::O;
QFutureWatcher<TicTacToe_Result> watcher;
QFuture<TicTacToe_Result> future = QtConcurrent::run([&]()
// here the heavy algorithm
for(int i=0; i<=1000000000; i++)

return TicTacToe_Resultr, c;
);

watcher.setFuture(future);
QEventLoop loop;
connect(&watcher, &QFutureWatcherBase::finished, &loop, &QEventLoop::quit);
loop.exec();
TicTacToe_Result r = watcher.result();
board[r.row][r.col] = TicTacToe_State::X;
QAbstractButton *btnX = group->button(3*r.row+r.col);
btnX->setText("X");



QtConcurrent returns the result through QFuture, this class can wait synchronously and asynchronously, to do it asynchronously we use QFutureWatcher, to be able to execute it sequentially we will use QEventLoop.


QtConcurrent


QFuture


QFutureWatcher


QEventLoop



I have implemented an example in the following link






Thank you so much for your time. Your answer and suggested solution is very pedagogical. I'll be spending the day studying your code. Thank you very

– PML
Nov 15 '17 at 11:51






@PML If my solution served you, do not forget to mark it as correct.

– eyllanesc
Nov 15 '17 at 11:54




Thanks for contributing an answer to Stack Overflow!



But avoid



To learn more, see our tips on writing great answers.



Required, but never shown



Required, but never shown




By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

Edmonton

Crossroads (UK TV series)