Quelle est la manière correcte d'afficher les widgets sans appeler QApplication :: exec ()?
À des fins de test, j'aimerais créer et afficher un widget. Pour l'instant, je n'ai besoin que du widget pour un rendu correct, mais à l'avenir, je pourrais vouloir l'étendre afin de simuler divers événements pour voir comment le widget se comporte.
À partir de diverses sources, il semblerait que ce qui suit devrait fonctionner:
QApplication app;
QPushButton button("Hello");
button.show();
// Might also be necessary:
QApplication::processEvents();
Mais pour moi, le widget ne s'affiche pas correctement. Une fenêtre est créée pour afficher le widget, mais elle est entièrement noire.
Je peux obtenir un rendu correct du widget en ajoutant les lignes suivantes:
std::this_thread::sleep_for(std::chrono::milliseconds(10));
QApplication::processEvents();
Avec 10 millisecondes étant à peu près le plus petit temps nécessaire pour que le widget s'affiche correctement.
Est-ce que quelqu'un sait comment faire fonctionner cela sans délai, ou pourquoi ce retard est-il nécessaire?
Réponses
Pour tester l'application Qt GUI, vous devez au moins QApplication
traiter une instance et une boucle d'événements. Le moyen le plus rapide est simplement d'utiliser une QTEST_MAIN
macro, cette réponse explique d'une manière agréable ce qu'elle fait exactement. Cependant, pour avoir plus de flexibilité (par exemple pour utiliser GTest, GMock) vous pouvez aussi simplement créer une QAplication
instance dans vos tests main (pas besoin d'appeler exec
).
Ensuite, pour que les événements soient traités, vous devez appeler QTest :: qWait . Cela traitera vos événements pendant la durée spécifiée. C'est une bonne pratique à utiliser qWaitFor
qui accepte un prédicat - de cette façon, vous évitez les conditions de course dans vos tests.
Dans le scénario particulier, lorsque vous vous attendez à ce qu'un signal soit émis, vous pouvez également utiliser des fonctionnalités similaires de QSignalSpy :: wait .
Petit exemple quand on veut attendre que certains paramètres soient passés d'un élément à un autre:
QSignalSpy spy(&widget1, &Widget1::settingsChanged);
widget2->applySettings();
ASSERT_TRUE(spy.wait(5000));
// do some further tests based on the content of passed settings
Pourquoi ne voulez-vous pas que l'application s'exécute? Le processus d'affichage d'un widget n'est pas "statique". Vous ne «dessinez» pas le widget à l'écran, mais vous avez plutôt une application qui écoute les divers événements et reçoit les événements de dessin du gestionnaire de fenêtrage. L'application ne peut dessiner le widget que lorsque le gestionnaire de fenêtrage le lui demande.
La raison pour laquelle votre deuxième code fonctionne est que vous attendez suffisamment longtemps pour que le gestionnaire de fenêtrage ait envoyé la demande de "tirage" dans vos conditions . Cela ne garantit pas que cela fonctionnera toujours.
Si vous souhaitez garantir l'affichage du widget, vous devez démarrer une boucle et attendre d'avoir reçu au moins un événement de tirage au sort, mais même cela n'est pas infaillible.
Comme l'a décrit de manière experte Vincent Fourmond, les widgets ne sont pas une affaire ponctuelle. L'interface graphique n'est pas bloquante et pour cela, elle doit s'exécuter dans une boucle d'événements .
La exec()
méthode démarre cette boucle d'événements que vous avez imitée en interrogeant. Bien qu'il soit possible de combiner la boucle d'événements de Qt avec d'autres boucles d'événements, je vous recommanderais une solution plus simple:
Poursuivez votre programme dans la boucle d'événements en appelant une méthode au démarrage. Trouvez ici une excellente réponse sur la façon de procéder:https://stackoverflow.com/a/8877968/21974
Comme vous l' avez mentionné les tests unitaires, il y a aussi un signal que vous pouvez utiliser pour effectuer des contrôles à la fin du cycle de vie (avant widgets sont détruits): QApplication::aboutToQuit
. Cela se produira lorsque la dernière fenêtre sera fermée (par programme ou par l'utilisateur).