Mis à jour le 05/10/2017
  • 4 heures
  • Facile

Ce cours est visible gratuitement en ligne.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Les événements souris et clavier

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Nous avons créé dans le chapitre précédent notre clavier avec ses huit touches. On a appris à créer nos propres nœuds graphiques et à appliquer des effets de designs comme les dégradés ou les reflets. Nous allons maintenant apprendre à capturer les événements utilisateur comme les événements souris ou les événements clavier, et nous allons déclencher des actions dans notre application à partir de ces événements.

En gros, nos allons rendre notre application interactive. A la fin de ce chapitre, notre Mélordi devrait permettre de jouer du piano sur son clavier :) .

Les événements souris

Il est possible de capturer tous les types d'événement souris existant :

  • Entrée de la souris dans une zone.

  • Sortie de la souris.

  • Clic.

  • Relâchement du clic.

  • Mouvement quelconque de la souris.

  • Mouvement de la roulette de la souris.

  • Glissement de la souris (mouvement pendant un clique).

Pour détecter chacun de ces événements, la méthode est toujours la même. Chaque objet graphique et chaque groupe d'objets graphiques possède un certain nombre de paramètres auxquels on peut affecter un objet de type EventHandler qui exécutera une fonction à chaque fois qu'un certain type d'événement se produit. Par exemple pour déclencher une action à chaque fois qu'on clique sur un objet, il suffit d'initialiser le paramètre onMouseClicked avec un EventHandler :

objet.setOnMouseClicked(new EventHandler<MouseEvent>(){
    public void handle(MouseEvent me){
        //instructions à exécuter lors de cet événement
    }
});

Le paramètre me de type MouseEvent de la fonction handle permet d'avoir toutes sortes d'informations sur l'événement. Cette méthode est très simple et rapide à utiliser, et comme je l'ai dit elle est valable pour tous les nœuds graphiques : les rectangles, les cercles, les textes, les groupes et bien sûr ceux que nous créons.

Pour notre Mélordi, nous allons commencer par rendre nos touches sensibles à deux types d'événements :

  • 1. Quand la souris survole une touche, celle-ci change de couleur et devient gris clair.

  • 2. Quand l'utilisateur clique sur une touche, celle-ci change de couleur et devient gris foncé, elle translate de deux pixels vers le bas pour donner l'impression qu'elle s'enfonce et cela déclenche la note de musique associée à la touche.

Survol de la souris

Commençons par le premier événement : la touche devient gris clair quand la souris la survole puis redevient blanche quand ce n'est plus le cas. Pour ça dirigeons nous vers notre feuille Touche.java qui définit le comportement des objets du type Touche. Nous allons faire varier la valeur de la propriété fill du rectangle de notre touche :

fond_touche.setFill(<couleur>);

Nous allons donc définir deux EventHandler pour les paramètres onMouseEntered et onMouseExited du groupe de notre objet de type Touche :

public Touche(String l, int posX, int posY, int n){

        //…

        this.setOnMouseEntered(new EventHandler<MouseEvent>(){
            public void handle(MouseEvent me){
                //instructions
            }
        });
        this.setOnMouseExited(new EventHandler<MouseEvent>(){
            public void handle(MouseEvent me){
                //instructions
            }
        });
    }

Vous devinez ce qu'il reste à faire n'est-ce pas... Dans la fonction de onMouseEntered on affecte à la propriété fill de la touche la valeur Color.LIGHTGREY, et dans la fonction de onMouseExited la valeur Color.WHITE :

this.setOnMouseEntered(new EventHandler<MouseEvent>(){
            public void handle(MouseEvent me){
                fond_touche.setFill(Color.LIGHTGREY);
            }
        });
        this.setOnMouseExited(new EventHandler<MouseEvent>(){
            public void handle(MouseEvent me){
                fond_touche.setFill(Color.WHITE);
            }
        });

Compilez et voyez le résultat :

Image utilisateur

Parfait, passons maintenant au clic souris.

Le clic souris

On a dit que quand l'utilisateur cliquait sur une touche ça déclencherait trois événements :

  • 1. La touche se décale de deux pixels vers le bas, donc la valeur du paramètre translateY du groupe augmenterait de 2.

  • 2. La couleur de la touche deviendrait gris foncé, donc la valeur de la propriété fill deviendrait Color.DARKGREY.

  • 3. La note de musique de la touche serait jouée.

Pour commencer, nous allons ajouter deux fonctions à notre classe Touche :

  • La fonction appuyer() qui serait appelée quand l'utilisateur clique sur la touche.

  • La fonction relacher() qui serait appelée quand l'utilisateur relâche son clic.

Voici la déclaration de ces deux fonctions :

public void appuyer(){
    //instructions
}
    
public void relacher(){
    //instructions
}

Dans ces fonctions, il faudra faire varier la couleur de la touche, sa translation verticale et déclencher ou arrêter la note de musique.

Commençons par faire varier la couleur et la translation verticale :

public void appuyer(){
    fond_touche.setFill(Color.DARKGREY);
    this.setTranslateY(positionY+2);
}
    
public void relacher(){
    fond_touche.setFill(Color.WHITE);
    this.setTranslateY(positionY);
}

On s'occupera de la note de musique juste après...
Tout ce qu'il nous reste à faire, c'est appeler la fonction appuyer() quand l'utilisateur appuie sur sa souris, et appeler la fonction relacher() quand il la relâche. Pour ça on procède exactement comme tout à l'heure mais avec les paramètres onMousePressed et onMouseReleased du groupe de notre objet Touche. On obtient le code suivant :

public class Touche extends Parent {
    
    public String lettre = new String("X");
    private int positionX = 0;
    private int positionY = 0;
    private int note = 0;    
    
    Rectangle fond_touche = new Rectangle(75,75,Color.WHITE);
    Text lettre_touche = new Text();
    
    public Touche(String l, int posX, int posY, int n){
        lettre = l;
        positionX = posX;
        positionY = posY;
        note = n;
        
        fond_touche.setArcHeight(10);
        fond_touche.setArcWidth(10);
        this.getChildren().add(fond_touche);
        
        lettre_touche.setContent(lettre);
        lettre_touche.setFont(new Font(25));
        lettre_touche.setFill(Color.GREY);
        lettre_touche.setX(25);
        lettre_touche.setY(45);
        this.getChildren().add(lettre_touche);
        
        this.setTranslateX(positionX);
        this.setTranslateY(positionY);
        this.setOnMouseEntered(new EventHandler<MouseEvent>(){
            public void handle(MouseEvent me){
                fond_touche.setFill(Color.LIGHTGREY);
            }
        });
        this.setOnMouseExited(new EventHandler<MouseEvent>(){
            public void handle(MouseEvent me){
                fond_touche.setFill(Color.WHITE);
            }
        });
        this.setOnMousePressed(new EventHandler<MouseEvent>(){
            public void handle(MouseEvent me){
                appuyer();
            }
        });
        this.setOnMouseReleased(new EventHandler<MouseEvent>(){
            public void handle(MouseEvent me){
                relacher();
            }
        });
    }
    public void appuyer(){
        fond_touche.setFill(Color.DARKGREY);
        this.setTranslateY(positionY+2);
    }
    
    public void relacher(){
        fond_touche.setFill(Color.WHITE);
        this.setTranslateY(positionY);
    }
}

Vous pouvez compiler et observer le résultat :

Image utilisateur

Super, on n'a plus qu'à ajouter le son et ce sera parfait ;) .

Pour ça, on voudrait accéder aux méthodes note_on() et note_off() de l'objet mon_instru que nous avons créé dans notre classe principale Melordi. Rappelons le diagramme UML de notre application :

Image utilisateur

Pour accéder à ces deux fonctions depuis la classe Touche, je propose de faire passer l'objet mon_instru de la classe Melordi en paramètre du constructeur de la classe Clavier puis en paramètre du constructeur de la classe Touche. De cette façon nous aurons accès à toutes les fonctions de l'objet mon_instru depuis l'objet mon_clavier et depuis chacune de ses touches.

Commençons par la classe Clavier, nous pouvons lui ajouter un objet Instru qui prendra la valeur de l'objet mon_instru que nous lui passerons en paramètre :

public class Clavier extends Parent{
    
    private Touche[] touches;
    private Instru instru;//on déclare un objet de type Instru
    
    public Clavier(Instru ins){
        instru = ins;//l'objet de type Instru prend la valeur de l'objet passé en paramètre
        
        Rectangle fond_clavier = new Rectangle();
        fond_clavier.setWidth(400);
        //...
        
    }
}

Ainsi, lors de la construction de l'objet mon_clavier dans la classe Melordi (feuille Melordi.java), nous pouvons passer l'objet mon_instru en paramètre :

Clavier mon_clavier = new Clavier(mon_instru);

Nous pouvons faire la même chose pour la classe Touche :

public class Touche extends Parent {
    
    public String lettre = new String("X");
    private int positionX = 0;
    private int positionY = 0;
    private int note = 0;    
    private Instru instru;//on déclare un objet de type Instru
    
    Rectangle fond_touche = new Rectangle(75,75,Color.WHITE);
    Text lettre_touche = new Text();
    
    public Touche(String l, int posX, int posY, int n, Instru ins){
        lettre = l;
        positionX = posX;
        positionY = posY;
        note = n;
        instru = ins;//l'objet de type instru prend la valeur de l'objet passé en paramètre
        
        //…
    }
}

Et dans la classe Clavier, à chaque fois qu'on construit un objet de type Touche, on passe l'objet de type Instru en paramètre :

touches = new Touche[]{
            new Touche("U",50,20,60,instru),
            new Touche("I",128,20,62,instru),
            new Touche("O",206,20,64,instru),
            new Touche("P",284,20,65,instru),
            new Touche("J",75,98,67,instru),
            new Touche("K",153,98,69,instru),
            new Touche("L",231,98,71,instru),
            new Touche("M",309,98,72,instru)
        };

Parfait, vous pouvez compiler pour vérifier qu'il n'y a pas d'erreur. Nous pouvons maintenant faire jouer des notes de musique à chacune des touches de notre Mélordi. On n'a plus qu'à appeler la fonction note_on() depuis la fonction appuyer() de la classe Touche, et la fonction note_off() depuis la fonction relacher() :

public void appuyer(){
        fond_touche.setFill(Color.DARKGREY);
        this.setTranslateY(positionY+2);
        instru.note_on(note);
    }
    
    public void relacher(){
        fond_touche.setFill(Color.WHITE);
        this.setTranslateY(positionY);
        instru.note_off(note);
    }

Maintenant ça devrait être bon, si vous compilez vous pouvez jouer de la musique avec votre souris :

Image utilisateur

Nous n'avons plus qu'à déclencher les mêmes actions quand l'utilisateur appuie sur les touches de son clavier.

Les événements clavier

Pour permettre à un objet graphique de capturer des événements clavier, la démarche est la même que pour les événements souris. Il suffit d'affecter aux paramètres onKeyPressed, onKeyReleased ou onKeyTyped, des objets de type EventHandler qui exécuteront une certaine fonction à chaque fois que l'un des événements correspondant sera réalisé :

  • l'utilisateur appuie sur une touche

  • l'utilisateur relâche une touche

La syntaxe est la même que pour les clics souris :

objet.onKeyPressed (new EventHandler<KeyEvent>(){
            public void handle(KeyEvent ke){
                //instructions à exécuter lors de cet événement
            }
        });
objet.onKeyReleased (new EventHandler<KeyEvent>(){
            public void handle(KeyEvent ke){
                //instructions à exécuter lors de cet événement
            }
        });

Le paramètre ke de type KeyEvent de la fonction handle permet d'avoir des informations sur l'événement, notamment la lettre de la touche grâce à la fonction ke.getText() dont nous nous servirons...

Il y a pourtant une différence importante entre les événements souris et les événements clavier. Tous les éléments graphiques situés dans notre fenêtre peuvent être sensibles aux événements souris en même temps. Alors qu'un seul objet graphique à la fois peut être sensible aux événements clavier. On dit que c'est l'objet qui a le focus qui pourra capter les événements clavier.

Pour qu'un objet ait le focus, il suffit de le lui attribuer grâce à la fonction :

mon_objet.requestFocus();

Après cela, tous les événements clavier seront captés par cet objet.

Dans notre programme, nous allons rendre l'objet mon_clavier sensible aux événements clavier :

  • Dès que l'utilisateur appuiera sur une touche, l'objet mon_clavier déclenchera la fonction appuyer() de la touche correspondante.

  • Dès que l'utilisateur relâchera une touche, l'objet mon_clavier déclenchera la fonction relacher() correspondante.

On va donc affecter des EventHandler aux paramètres onKeyPressed et onKeyReleased de notre classe Clavier :

public Clavier(Instru ins){
        instru = ins;
        
        //...
        
        this.setOnKeyPressed(new EventHandler<KeyEvent>(){
            public void handle(KeyEvent ke){
                for (Touche touche: touches){
                    //instructions quand une touche est enfoncée
                }
            }
        });
        this.setOnKeyReleased(new EventHandler<KeyEvent>(){
            public void handle(KeyEvent ke){
                for (Touche touche: touches){
                    //instructions quand une touche est relâchée
                }
            }
        });
    }

A l'intérieur de ces deux fonctions, la fonction ke.getText() nous permet de connaître la lettre de la touche que l'utilisateur a enfoncée ou relâchée. Pour déclencher l'événement sur la touche correspondante de l'objet mon_clavier, il faut :

  • 1. Parcourir l'ensemble des objets Touche contenus dans le tableau touches.

  • 2. Et si la lettre de la touche est la même que la variable e.text, alors on appelle la fonction appuyer() ou relacher() de cette touche.

Ainsi on peut remplir nos fonctions pour onKeyPressed et onKeyReleased :

this.setOnKeyPressed(new EventHandler<KeyEvent>(){
            public void handle(KeyEvent ke){
                for (Touche touche: touches){
                    if( touche.lettre.equals( ke.getText().toUpperCase() ) )
                        touche.appuyer();
                }
            }
        });
        this.setOnKeyReleased(new EventHandler<KeyEvent>(){
            public void handle(KeyEvent ke){
                for (Touche touche: touches){
                    if(touche.lettre.equals( ke.getText().toUpperCase() ) )
                        touche.relacher();
                }
            }
        });

Et pour finir, on donne le focus à notre objet mon_clavier en ajoutant tout à la fin de la fonction start() de la classe Melordi :

mon_clavier.requestFocus();

Vous devriez maintenant pouvoir pianoter de vraies symphonies sur votre Mélordi ;)

Image utilisateur

Ce chapitre était un peu long mais nous avons appris des choses utiles :

  • Capturer les événements déclenchés par l'utilisateur à travers la souris ou le clavier.

  • Rendre notre application interactive en la faisant réagir à ces événements.

Dans le chapitre suivant, nous allons permettre à notre Mélordi de changer d'instrument. Pour ça on va apprendre à créer des images et un layout.

Exemple de certificat de réussite
Exemple de certificat de réussite