Bonsoir,
Il faut voir Qt comme étant strictement événementiel. C'est-à-dire que, une fois que le app.exec() est lancé (bloquant), la seule manière de reprendre la main dessus, via son thread, est de passer par des événements. Par exemple, si on connecte un bouton d'une fenêtre à un slot (une méthode) onButtonClicked(), seule une interaction de l'utilisateur (un clic) permettra au slot onButtonClicked() d'être appelé. C'est-à-dire que le app.exec() va parcourir les événements, va constater un événement de clic de l'utilisateur. Il va alors le dispatcher dans l'arborescence des widgets jusqu'à ce qu'un widget dise "OK, c'est pour moi" (dans le cas actuel, le bouton dira cela). Ce bouton étant connecté à la méthode, la méthode sera appelée. Exemple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| int main(int argc, char **argv)
{
QApplication app(argc, argv);
Fenetre1 fen1;
fen1.show();
return app.exec();
}
Fenetre1::Fenetre1()
{
QPushButton *btn = new QPushButton("Click me!", this);
connect(btn, SIGNAL(clicked()), this, SLOT(onButtonClicked()));
}
void Fenetre1::onButtonClicked()
{
Fenetre2 *fen2 = new Fenetre2;
fen2->show();
} |
Le schéma grossier de l'exécution est le suivant :
Avec "Clic utilisateur", l'événement qui est traité par app.exec() lors d'un clic utilisateur, "CloseEvent", l'événement lié à un clic de l'utilisateur sur la croix de la fenêtre 1 et "CloseEvent 2", l'événement lié à un clic de l'utilisateur sur la croix de la fenêtre 2. Comme vous pouvez le constater le main est bloqué par l'exécution du QApplication, qui gère et redirige l'intégralité des événements vers les fenêtres (c'est la raison pour laquelle on ne peut pas créer un widget depuis un autre thread que le main thread : le QApplication sur le main thread se charge de la boucle d'événements commune de toutes les fenêtres).
Prenons maintenant votre exemple :
Imaginons que dans exe Qt classique, j'ai besoin de lancer plusieurs threads en parallèle pour effectuer des tâches de fond. Puis-je créer plusieurs fenêtres indépendantes qui affiche chacune les données en provenance des threads qui leur ont été préalablement assigné ? Comment puis-je lié les objet instancier dans ces threads avec leur fenêtre respective ?
On va dire que dans cet exécutable, il y aura une fenêtre travaillant avec son thread pour par exemple recevoir un flux vidéo : à chaque fois que le thread aura fini de récupérer une image, il voudra notifier la fenêtre pour que celle-ci puisse l'afficher. Le cas où il y aurait deux fenêtres avec deux threads est strictement identique, la procédure est toujours la même, à dupliquer. On a donc ceci, en terme de code :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| Thread1::run()
{
while (1)
{
// Récupération d'une partie de l'image
// ...
if (imageOk) // Booléen quelconque indiquant qu'une image est prête
{
mutex.lock(); // Mutex pour écrire l'image dans une variable interne _image
_image = imageManaged; // Écriture de l'image dans la variable interne
mutex.unlock(); // Libération du mutex
emit imageAvailable(); // Émission d'un signal indiquant qu'il a une image prête (voir le connect() ci-dessous)
}
}
}
Fenetre1::Fenetre1()
{
// Le Qt::QueuedEvent est important, il signifie que le signal imageAvailable() sera géré en tant qu'événement et mis en attente dans la queue des events du QApplication :
// cela signifie que le slot showImage() ne sera appelé que lors du tour suivant de traitement des événements de app.exec() : il sera donc appelé dans le main thread et non dans le thread 1
connect(thread1, SIGNAL(imageAvailable()), this, SLOT(showImage()), Qt::QueuedEvent);
}
void Fenetre1::showImage()
{
thread1->getMutex()->lock(); // Lock du mutex au cas où le thread serait en train de fournir une nouvelle image
QPixmap image = thread1->getImage(); // Récupération de l'image
thread1->getMutex()->unlock(); // Unlock du mutex
widgetAfficheur->setPixmap(image); // Affichage de l'image
} |
Voici le schéma d'un cas de fin de traitement de la part du thread :
Avec en rouge, ce qui est traité par le thread 1 et en vert, ce qui est traité par le main thread. Pour rajouter une fenêtre et un autre thread à cela, on duplique juste les opérations de thread 1 avec comme couleur le rouge remplacé par du orange, par exemple (c'est un autre thread), et on duplique Fenêtre 1 en Fenêtre 2, avec toujours du vert : les fenêtres sont toutes gérées par le main thread.
Dans la partie "Tour de boucle de app.exec()", j'entends ce que j'avais donné dans mon message précédent :
Tant qu'il y a des fenêtres affichées
S'il y a des événements en attente // Dans le cas présent, oui, il y a le signal qui a été appelé en mode queued
Gestion des événements (appel à processEvents()) // Mène à appeler le slot de
Fenetre1 et au tour de boucle suivant un PaintEvent qui gère l'affichage de Fenetre1,
vu que son affichage a changé
Temporisation
Maintenant, on peut répondre à la question de la modalité : comment une fenêtre modale peut-être en bloquer une autre ? QApplication va tout simplement cesser de délivrer les événements à la fenêtre bloquée tant qu'une autre fenêtre la bloquera (ce n'est cependant qu'une hypothèse, mais ça me paraît tout à fait plausible).
Après, de ce que je comprends de votre problème, il faudrait que le main() puisse reprendre la main malgré le fait que le app.exec() tourne en boucle infinie (premier schéma), ce qui me parait difficile, à moins que votre plateforme C ne tourne sur un thread autre que le main thread, mais je n'y crois pas trop. Une solution pourrait être de lancer une application Qt séparée via votre dll, tout simplement (QProcess::startDetached()), plutôt que de garder le même processus ?
Désolé pour les diagrammes de séquence un peu étranges (notamment sur les durées des traitements), je n'ai jamais pris suffisamment de temps pour apprendre à en faire de vraiment corrects.
Bonne soirée,
Louis
Partager