Qual é a maneira correta de exibir widgets sem chamar QApplication :: exec ()?

Nov 20 2020

Para fins de teste, gostaria de criar e exibir um widget. Por enquanto, só preciso que o widget seja renderizado corretamente, mas no futuro posso querer estender isso para simular vários eventos para ver como o widget se comporta.

De várias fontes, parece que o seguinte deve funcionar:

QApplication app;

QPushButton button("Hello");
button.show();

// Might also be necessary:
QApplication::processEvents();

Mas, para mim, o widget não é renderizado corretamente. Uma janela é criada para exibir o widget, porém é totalmente preta.

Posso fazer com que o widget seja renderizado corretamente adicionando as seguintes linhas:

std::this_thread::sleep_for(std::chrono::milliseconds(10));
QApplication::processEvents();

Com 10 milissegundos sendo o menor tempo necessário para fazer o widget renderizar corretamente.

Alguém sabe como fazer isso funcionar sem demora, ou sabe por que o atraso é necessário?

Respostas

4 pptaszni Nov 20 2020 at 17:13

Para testar o aplicativo Qt GUI, você precisa de pelo menos QApplicationum loop de instância e evento sendo processado. A maneira mais rápida é usar QTEST_MAINmacro, esta resposta explica de uma maneira agradável o que ela faz exatamente. No entanto, para ter mais flexibilidade (por exemplo, para usar GTest, GMock), você também pode simplesmente criar uma QAplicationinstância em seus testes principais (não há necessidade de chamar exec).

Então, para que os eventos sejam processados, você deve invocar QTest :: qWait . Isso processará seus eventos pelo período de tempo especificado. É uma boa prática usar o qWaitForque aceita um predicado - dessa forma você evita condições de corrida em seus testes.

No cenário específico, quando você espera que algum sinal seja emitido, você também pode usar uma funcionalidade semelhante de QSignalSpy :: wait .

Pequeno exemplo quando queremos esperar até que alguns parâmetros sejam passados ​​de um item para outro:

QSignalSpy spy(&widget1, &Widget1::settingsChanged);
widget2->applySettings();
ASSERT_TRUE(spy.wait(5000));
// do some further tests based on the content of passed settings
1 VincentFourmond Nov 20 2020 at 16:39

Por que você não quer que o aplicativo execute exec? O processo de exibição de um widget não é "estático". Você não "desenha" o widget na tela, mas sim um aplicativo que escuta vários eventos e recebe eventos de desenho do gerenciador de janelas. O aplicativo só pode desenhar o widget quando o gerenciador de janelas solicitar.

O motivo pelo qual seu segundo código funciona é que você espera o suficiente para que o gerenciador de janelas envie a solicitação de "sorteio" em suas condições . Isso não garante que sempre funcionará.

Se você quiser garantir a exibição do widget, você precisa iniciar um loop e esperar até que tenha recebido pelo menos um evento de sorteio, mas mesmo isso não é infalível.

ypnos Nov 20 2020 at 16:58

Conforme descrito habilmente por Vincent Fourmond, os widgets não são um negócio único. A GUI não é bloqueadora e, para isso, precisa ser executada em um loop de eventos .

O exec()método inicia este loop de eventos que você imitou por votação. Embora seja possível combinar o loop de eventos do Qt com outros loops de eventos, eu recomendo a você uma solução mais simples:

Continue seu programa dentro do loop de eventos chamando um método quando ele é iniciado. Encontre aqui uma resposta excelente sobre como fazer isso:https://stackoverflow.com/a/8877968/21974

Como você mencionou os testes de unidade, há também um sinal de que você pode usar para fazer verificações no final do ciclo de vida (antes widgets são destruídos): QApplication::aboutToQuit. Isso acontecerá quando a última janela for fechada (programaticamente ou pelo usuário).