Finalement, voici un troisième article sur l'implémentation Windows. Deux raisons (donc deux parties dans cet article) :
Habituellement, pour utiliser les événements (clavier ou souris par exemple), vous devez surcharger les fonctions virtuelles correspondantes dans les classes dérivées de QWidget ou de QWindow (le plus souvent keyPressEvent et keyReleaseEvent pour le clavier, mouseMoveEvent, mousePressEvent et mouseReleaseEvent pour la souris). Comment sont appelées en interne ces fonctions par le gestionnaire d'événements de Qt ?
La boucle d'événements est une simple boucle qui tourne en permanence durant toute la durée de vie de l'application. Elle gère une liste des événements à traiter, cette liste étant remplie par les différents objets Qt (comme nous l'avons fait dans le dernier article, avec la fonction postEvent). À chaque fois que la boucle traite un événement, elle appelle en fait la fonction event en passant l'événement en paramètre. La fonction virtuelle event vérifie alors le type d'événement puis appelle la fonction correspondante.
Pour prendre en compte les événements du gamepad dans une classe, il faut dériver cette classe et surcharger la fonction event. Si c'est un événement provenant d'un gamepad, on appelle des fonctions spécialisées pour les gérer (gamepadConnectEvent, gamepadDiconnectEvent et gamepadUpdateEvent). Sinon, on appelle la fonction event de la classe parent pour qu'elle puisse traiter les autres types d'événement.
Par exemple, pour gérer les événements dans une vue Qt Quick :
class GamepadView : public QQuickView { protected: bool event(QEvent* event) override; virtual bool gamepadConnectEvent(GamepadEvent* event); virtual bool gamepadDiconnectEvent(GamepadEvent* event); virtual bool gamepadUpdateEvent(GamepadEvent* event); }
Dans la fonction event, on teste les différents types possibles d'événement gamepad :
bool GamepadView::event(QEvent* event) { if (event->type() == static_cast<QEvent::Type>( GamepadEvent::GamepadConnection)) return gamepadConnectEvent( static_cast<GamepadEvent*>(event)); else if (event->type() == static_cast<QEvent::Type>( GamepadEvent::GamepadDisconnection)) return gamepadDiconnectEvent( static_cast<GamepadEvent*>(event)); else if (event->type() == static_cast<QEvent::Type>( GamepadEvent::GamepadUpdate)) return gamepadUpdateEvent( static_cast<GamepadEvent*>(event)); else return QQuickView::event(event); }
On peut alors ajouter les fonctions de traitement des événements (remarque : il faut au moins une implémentation par défaut de ces fonctions, qui acceptent les événements et retournent vraie).
bool GamepadView::gamepadUpdateEvent(GamepadEvent* event) { qDebug() << "gamepadUpdateEvent"; event->accept(); return true; } bool GamepadView::gamepadConnectEvent(GamepadEvent *event) { qDebug() << "gamepadConnectEvent"; event->accept(); return true; } bool GamepadView::gamepadDiconnectEvent(GamepadEvent *event) { qDebug() << "gamepadDiconnectEvent"; event->accept(); return true; }
Sauf oubli, on a fini de faire le tour de la création d'événements personnalisés avec Qt. On va pouvoir passer à la suite, c'est-à-dire soit l'utilisation de ces classes dans un exemple (clone de R-Type).
Lors de l'implémentation du code d'exemple, je me suis retrouvé confronté à la question de l'utilisation des signaux et slots. En effet, la communication entre le C++ et le QML passe par des signaux-slots, il faut
On a donc le fonctionnement suivant :
On peut donc se poser la question de l'utilité des événements dans ce cas et proposer une implémentation alternative connectant directement le timer au code QML.
Deux implémentations sont possibles : soit on crée une propriété pour chaque variable, soit on crée une seule propriété contenant une structure de données contenant toutes les variables. Il est possible que cela ait un impact sur les performances, il faudra donc implémenter les différentes méthodes puis faire des tests de performances.
Pour la première méthode (je ne présente que pour une variable, le code pour les autres variables est similaire), il faut donc créer une propriété en lecture seule, avec un signal changed. La fonction setGamepadX est privée et n'est pas dans la propriété, elle ne sera utilisée que par le timer.
class Gamepad : public QTimer { Q_OBJECT Q_PROPERTY(float x READ gamepadX NOTIFY gamepadXChanged) public: float gamepadX() const; signals: void gamepadXChanged(); private: void setGamepadX(float value); };
La fonction setGamepadX teste si la nouvelle valeur est différente de la valeur actuelle et émet un signal si c'est le cas.
void Gamepad::setGamepadX(float value) { if (m_gamepadX != value) { m_gamepadX = value; emit gamepadXChanged(m_gamepadX); } }
Il faut également modifier la fonction update de Gamepad. Au lieu de tester l'état courant par rapport au précédent, on peut attribuer directement la nouvelle valeur à la variable.
void Gamepad::update() { JOYINFOEX info; if (joyGetPosEx(d->id, &info) == JOYERR_NOERROR) { setGamepadX(normalize(info.dwXpos, d->capabilities.xMin, d->capabilities.xMax, -1.0f, 1.0f)); } }
Cette classe est alors directement utilisable dans le code QML après l'avoir enregistrée dans le moteur QML :
qmlRegisterType<Gamepad>("Gamepad", 1, 0, "Gamepad");
On peut alors créer ce nouvel élément pour gérer le gamepad en QML.
Gamepad { id: gamepad } Image {
id: player source: "player.png" x: gamepad.x y: gamepad.y
} </code>
Le code est similaire, mais au lieu d'exporter chaque variable, on crée une structure et on exporte celle-ci.
struct GamepadState { float x, y, z, r, u, v, pov; unsigned buttons; }; Q_PROPERTY(GamepadState* state READ state NOTIFY stateChanged)
Dans le code QML, les variables sont accessibles via cette propriété state.
x: gamepad.state.x
Dans le prochain article, nous verrons le code d'exemple permettant de créer un clone minimaliste de R-Type gérant le gamepad.