Traitement des évènements d'une QScxmlStateMachine
-
Bonjour,
J'utilise avec bonheur QScxmlStateMachine depuis deux ou trois semaine.
Une première machine d'états suit l'état d'une base de données, et tout fonctionne bien.
Une seconde machine d'états fonctionne moins bien et j'aimerais vos lumières.Lorsque je déclenche un changement d'états avec submitEvent, je constate que le changement effectif d'état n'a pas lieu de suite, mais juste avant que l'application redonne la main à l'utilisateur. Or entre le submitEvent et l'attente d'une action utilisateur je réalise des opérations qui dépendent de l'état actif.
J'ai l'impression que la boucle des évènements ne traite ceux de la machine d'états qu'en dernier.
Alors j'ai cherché en vainc dans l'API de QScxmlStateMachine comment "purger" la file des évènements juste après le submitEvent.Quelqu'un sait ce qu'il faut faire ?
Sylvain -
Bonjour,
Solution trouvée : appeler
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
juste après un
submitEvent();Cette solution n'est pas préconisée par la doc Qt. J'ai donc essayé de récupérer un handle vers la QEventLoop de l'application mais je ne trouve pas comment faire...
-
qApp->processEvents(QEventLoop::ExcludeUserInputEvents)
C'est une macro qui return l'object Q(Core,Gui)Application.
processEvents
est de toute façon une méthode static donc c'est la bonne event loop qui est appelée. Ce qui n'est pas recommandé est l'usage de cette méthode. -
This post is deleted!
-
Depuis le temps j'ai pensé à une autre solution potentielle :
- créer une nouvelle boucle d'évènements dans celle de l'IHM ;
- "raccrocher" les évènements des QScxmlStateMachine à cette nouvelle boucle d'évènements.
Si le premier point est assez simple, je ne sais pas encore comment assurer le second. Ce serait possible ?
-
Serait-il possible d'avoir un démonstrateur minimal qui permette de voir le soucis de cette state machine ?
-
Bonjour,
Je m'y essaye !
Voici la machine d'états définie dans Qt Creator.
Voici le .h et le .cpp d'un widget formulaire intégré à un MainWindow. Ce widget compte plusieurs champs et cases à cocher. J'ai réduit au maximum.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// form.h //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #include "SM_EtatsFormulaire.h" #include "donneurs.h" namespace Ui { class Form; } class Form : public QWidget { Q_OBJECT public: explicit Form(QWidget *parent = nullptr); ~Form(); void lancer_machine_etats(); void renseigne_formulaire(Donneur *d); signals: void formChanged(); private slots: void formulaireModifie(); void on_pushButton_RAZ_clicked(); private: void formulaireValide(bool); void makeupLabelId(); void sm_emit_Vider_formulaire(); void sm_emit_Valider_formulaire(); void sm_emit_Selectionner_donneur_en_base(); void doItNow(); Ui::Form * ui; SM_EtatsFormulaire sm_etat_formulaire; }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// form.cpp //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #include "form.h" #include "ui_form.h" Form::Form(QWidget *parent) : QWidget(parent), ui(new Ui::Form) { ui->setupUi(this); lancer_machine_etats(); } Form::~Form(){ delete ui; } void Form::lancer_machine_etats(){ sm_etat_formulaire.connectToState("Formulaire_Non_Valide", [this](bool active) { if (active) this->formulaireModifie(); } ); sm_etat_formulaire.connectToState("Formulaire_Valide", [this](bool active) { this->formulaireValide(active); } ); sm_etat_formulaire.start(); } void Form::formulaireModifie(){ emit formChanged(); if (donneurs->filtre.isValid()) sm_emit_Valider_formulaire(); } void Form::formulaireValide(bool b){ ui->pushButton_select_donneurs->setEnabled(b); } void Form::on_pushButton_RAZ_clicked(){ vider_formulaire(); formulaireModifie(); sm_emit_Vider_formulaire(); makeupLabelId(); } /// Méthode appelée par MainWindow après sélection d'un donneur dans un QTableView. void Form::renseigne_formulaire(element *d){ [...] sm_emit_Selectionner_donneur_en_base(); formulaireModifie(); makeupLabelId(); } /////// Le soucis est là. // Si on ne prend pas la peine de vider la boucle des événements juste après le changement d'état, ... // ... le nouvel état ne sera pas détecté ici. // La transition vers le nouvel état aura lieu juste avant que MainWindow redonne la main à l'utilisateur. void Form::makeupLabelId(){ [...] if (sm_etat_formulaire.isActive("Formulaire_Vide")) badIdx = false; [...] } void Form::sm_emit_Vider_formulaire(){ sm_etat_formulaire.submitEvent("Vider_formulaire"); doItNow(); } void Form::sm_emit_Valider_formulaire(){ sm_etat_formulaire.submitEvent("Valider_formulaire"); doItNow(); } void Form::sm_emit_Selectionner_donneur_en_base(){ sm_etat_formulaire.submitEvent("Selectionner_donneur_en_base"); doItNow(); } void Form::doItNow(){ QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); }
-
Est-ce qu'à un quelconque moment, la machine d'état arrive dans un état final ?
Avant d'appeler processEvent, est-ce que la machine est arrêtée ? -
Non. La machine d'états (ME par la suite) passe d'un état à l'autre, c'est tout. Pour autant j'utilise dans ce même programme deux autres ME qui ne posent pas de soucis, tout fonctionne comme prévu (et d'ailleurs cela m'économise du temps, du code et me permet de formaliser graphiquement la dynamique système).
Après quelques tests je suis arrivé à la conclusion que les évènements de la ME sont traités en dernier juste avant de rendre la main à l'utilisateur de l'IHM, ou alors c'est la boucle d'évènements qui est traitée après le code.
Ainsi les changements d'états ne sont pas réalisés de suite après un submitEvent(), ce qui pose problème quand on interroge l'état actif de la ME juste après le submitEvent() et avant que l'appli ne redonne la main à l'utilisateur. D'où la solution envisagée, déconseillée par la doc Qt, mais qui fonctionne.Tu proposes d'arrêter la ME après chaque submitEvent() ? Auquel cas il faudrait la redémarrer avant de faire un nouveau changement d'états ?
J'ai imaginé une autre solution sans savoir si ça pourrait marcher :
- au sein de l'application lancer une autre boucle d'évènements ;
- raccrocher les machines d'états à cette nouvelle boucle d'évènements qui ne serait pas "poluée" par les évènements IHM.
Dans l'hypothèse ou le code de la boucle d'évènements n'est exécuté qu'après le code de l'application, cela ne résoudrait rien, sauf si cette nouvelle boucle d'évènement pour la ME est lancée dans un autre thread. Mais là pour moi, c'est encore un territoire inconnu...