Vous êtes sur la page 1sur 50

Cours IHM-1

JavaFX
6 - Architecture MVC
Gestion des vnements
Jacques BAPST
jacques.bapst@hefr.ch

Architecture MVC
Structure d'une application

IHM-1 FX06

Jacques BAPST

Architecture MVC [1]


Il existe diffrentes manires de structurer le code des applications
interactives (celles qui comportent une interface utilisateur).
Une des architectures, communment utilise, et qui comporte de
nombreuses variantes, est connue sous l'acronyme MVC qui signifie
Model - View - Controller.
Dans cette architecture on divise le code des applications en entits
distinctes (modles, vues et contrleurs) qui communiquent entreelles au moyen de divers mcanismes (invocation de mthodes,
gnration et rception d'vnements, etc.).
Cette architecture (ou modle de conception, design pattern) a t
introduite avec le langage Smalltalk-80 dans le but de simplifier le
dveloppement ainsi que la maintenance des applications, en
rpartissant et en dcouplant les activits dans diffrents soussystmes (plus ou moins) indpendants.
IHM-1 FX06

Jacques BAPST

Architecture MVC [2]


Le principe de base de l'architecture MVC est relativement simple,
on divise le code du systme interactif en trois parties distinctes :
Le ou les modles (Models) qui se chargent de la gestion des donnes
(accs, transformations, calculs, etc.). Le modle enregistre
(directement ou indirectement) l'tat du systme et le tient jour.
Les vues (Views) qui comprennent tout ce qui touche l'interface
utilisateur (composants, fentres, botes de dialogue) et qui a pour
tche de prsenter les informations (visualisation). Les vues participent
aussi la dtection de certaines actions de l'utilisateur (clic sur un
bouton, dplacement d'un curseur, geste swipe, saisie d'un texte, ).
Les contrleurs (Controllers) qui sont chargs de ragir aux actions
de l'utilisateur (clavier, souris, gestes) et d'autres vnements internes
(activits en tches de fond, timer) et externes (rseau, serveur).

Une application peut galement comporter du code qui n'est pas


directement affect l'une de ces trois parties (librairies gnrales,
classes utilitaires, etc.).
IHM-1 FX06

Jacques BAPST

Interactions MVC [1]


Un exemple de communication entre les lments (MVC synchrone).
La vue peut consulter
les donnes du modle.

Model

View

La vue dtermine quels


vnements sont passs
au contrleur.

Le contrleur peut
consulter et mettre
jour le modle en fonction
des vnements.

Controller

Le contrleur peut demander


la mise jour des lments
affichs.

Flux de donnes
vnements

IHM-1 FX06

Jacques BAPST

Interactions MVC [1]


Lorsqu'un utilisateur interagit avec une interface, les diffrents
lments de l'architecture MVC interviennent pour interprter et
traiter l'vnement.

Interface
utilisateur

Rsultats

Vue

Stockage
Traitements

Dcisions

Action
interprte

Utilisateur

IHM-1 FX06

Mise jour
de l'interface

Oprations
(fonctions)

Contrleur

Jacques BAPST

Modle

MVC Modle
Le modle (Model) est responsable de la gestion des donnes qui
caractrisent l'tat du systme et son volution.
Dans certaines situations (simples) le modle peut contenir luimme les donnes mais, la plupart du temps, il agit comme un
intermdiaire (proxy) et gre l'accs aux donnes qui sont stockes
dans une base de donnes, un serveur d'informations, le cloud,
Le modle est souvent dfini par une ou plusieurs interfaces Java qui
permettent de s'abstraire de la faon dont les donnes (les objets
mtier) sont rellement stockes (notion de DAO Data Access Object).
Il offre galement les mthodes et fonctions permettant de grer,
transformer et manipuler ces donnes.
Les informations gres par le modle doivent tre indpendantes
de la manire dont elles seront affiches. Le modle doit pouvoir
exister indpendamment de la reprsentation visuelle des donnes.
IHM-1 FX06

Jacques BAPST

MVC Vue
La vue (View) est charge de la reprsentation visuelle des
informations en faisant appel des crans, des fentres, des
composants, des conteneurs (layout), des botes de dialogue, etc.
Plusieurs vues diffrentes peuvent tre bases sur le mme modle
(plusieurs reprsentations possibles d'un mme jeu de donnes).
La vue intercepte certaines actions de l'utilisateur et les transmet au
contrleur pour qu'il les traite (souris, clavier, gestes, ).
La mise jour de la vue peut tre dclenche par un contrleur ou
par un vnement signalant un changement intervenu dans les
donnes du modle par exemple (mode asynchrone).
La reprsentation visuelle des informations affiches peut dpendre
du Look-and-Feel adopt (ou impos) et peut varier d'une
plateforme l'autre. L'utilisateur peut parfois modifier lui mme le
thme de prsentation des informations.
IHM-1 FX06

Jacques BAPST

MVC Contrleur
Le contrleur (Controller) est charg de ragir aux diffrentes
actions de l'utilisateur ou d'autres vnements qui peuvent
survenir.
Le contrleur dfinit le comportement de l'application et sa logique
(comment elle ragit aux sollicitations, business logic).
Dans les applications simples, le contrleur gre la synchronisation
entre la vue et le modle (rle de chef d'orchestre).
Le contrleur est inform des vnements qui doivent tre traits et
sait d'o ils proviennent.
La plupart des actions tant interceptes (ou en lien) avec la vue, il
existe un couplage assez fort entre la vue et le contrleur.
Le contrleur communique gnralement avec le modle et avec la
vue. C'est le sens des transferts et le mode de communication qui
caractrisent diffrentes variantes de l'architecture MVC.
IHM-1 FX06

Jacques BAPST

Structure d'une application [1]


Une application JavaFX qui respecte l'architecture MVC comprendra
gnralement diffrentes classes et ressources :
Le modle sera frquemment reprsent par une ou plusieurs classes
qui implmentent gnralement une interface permettant de s'abstraire
des techniques de stockage des donnes.
Les vues seront soit codes en Java ou dclares en FXML. Des feuilles
de styles CSS pourront galement tre dfinies pour dcrire le rendu.
Les contrleurs pourront prendre diffrentes formes :
Ils peuvent tre reprsents par des classes qui traitent chacune un
vnement particulier ou qui traitent plusieurs vnements en relation
(menu ou groupe de boutons par exemple)
Si le code est trs court, ils peuvent parfois tre inclus dans les vues, sous
forme de classes locales anonymes ou d'expressions lambda.
La classe principale (celle qui comprend la mthode main()) peut faire

l'objet d'une classe spare ou tre intgre la classe de la fentre


principale (vue principale).
D'autres classes utilitaires peuvent venir complter l'application.
IHM-1 FX06

Jacques BAPST

10

Il existe de nombreuses dclinaisons de l'architecture MVC ainsi que


des variantes dont les plus connues sont :

C o mp l me nt

MVP
: Model - View - Presenter
MVVM : Model - View - View-Model

Dans ces variantes le modle et la vue sont dfinis de manire quasi


identique. C'est le rle du contrleur et sa manire de communiquer
avec les autres parties qui distinguent ces variantes de l'architecture
MVC standard.

< < <

> > >

Variantes de l'architecture MVC [1]

Ces diffrentes variantes


d'architecture ne pourront
pas tre explores plus en
dtail dans le cadre de ce
cours.

Source: tomyrhymond.wordpress.com/2011/09/16/mvc-mvp-and-mvvm

IHM-1 FX06

Jacques BAPST

11

Gestion des vnements

IHM-1 FX06

Jacques BAPST

12

Programmation vnementielle [1]


La programmation des applications avec interfaces graphiques est
gnralement base sur un paradigme nomm programmation
vnementielle (Event Programming).
Dans la programmation imprative (squence d'instructions), c'est
le programme qui dirige les oprations (par exemple, il demande
l'utilisateur d'entrer des valeurs, calcule et affiche un rsultat, etc.).
Avec la programmation vnementielle, ce sont les vnements
(gnralement dclenchs par l'utilisateur, mais aussi par le systme) qui
pilotent l'application. Ce mode non directif convient bien la gestion
des interfaces graphiques o l'utilisateur une grande libert
d'action (l'interface est au service de l'utilisateur et non l'inverse).
La programmation vnementielle ncessite qu'un processus (en
tche de fond) surveille constamment les actions de l'utilisateur
susceptibles de dclencher des vnements qui pourront tre
ensuite traits (ou non) par l'application (contrleurs).
IHM-1 FX06

Jacques BAPST

13

Programmation vnementielle [2]


En programmation squentielle, une interface utilisateur (en lignes
de commandes) pourrait tre code selon le pseudo-code suivant qui
illustre le principe.
SequentialProg {
Initialize();
Loop {
cmd = readCommand();
Switch (cmd) {
Case: command1
process_cmd1();
Case: command2
process_cmd2();
Case: command3
process_cmd3();
Case: . . .
. . .
}
}
}

IHM-1 FX06

Une boucle sans fin rpte le cycle


suivant :
- Demande l'utilisateur de saisir
une commande (prompt)
- Attend que la prochaine
commande soit entre au
clavier (+dcodage)
- Selon la commande entre, du
code spcifique est excut.
Lorsque le traitement est termin,
le cycle recommence (on attend la
prochaine commande).

Jacques BAPST

14

Programmation vnementielle [3]


En programmation vnementielle, on prpare les actions (code)
excuter en les associant aux vnements que l'on souhaite traiter
(enregistrement des callback) et on attend que le processus de
surveillance nous avertisse en excutant le code prvu.
EventProg {
Initialize();
CreateGUI();
RegisterCallback();
StartEventLoop();

Aprs initialisation, on cre l'interface


graphique puis on associe du code
chaque vnement que l'on souhaite
traiter (RegisterCallback).

// Thread

Event Loop Thread

________________________________________________________________________________

Callback1()
code1;

// Button clicked

Callback2()
code2;

// Key pressed

Callback3()
code3

// Window resized

. . .
. . .

// Pinch gesture

Lorsque les vnements enregistrs


se produisent, le code associ est
automatiquement excut par le
processus de surveillance qui tourne
en tche de fond Event Thread.

}
IHM-1 FX06

Jacques BAPST

15

vnement [1]
Un vnement (event) constitue une notification qui signale que
quelque chose s'est pass (un fait, un acte digne d'intrt).
Un vnement peut tre provoqu par :
Une action de l'utilisateur
Un clic avec la souris
La pression sur une touche du clavier
Le dplacement d'une fentre
Un geste sur un cran tactile
...

Un changement provoqu par le systme


Une valeur a chang (proprit)
Un timer est arriv chance
Un processus a termin un calcul
Une information est arrive par le rseau
...

IHM-1 FX06

Jacques BAPST

16

vnement [2]
En JavaFX les vnements sont reprsents par des objets de la
classe Event ou, plus gnralement, d'une de ses sous-classes.
De nombreux vnements sont prdfinis (MouseEvent, KeyEvent,
DragEvent, ScrollEvent, ) mais il est galement possible de crer
ses propres vnements en crant des sous-classes de Event.
Chaque objet de type "vnement" comprend (au moins) les
informations suivantes :
Le type de l'vnement (EventType consultable avec getEventType())
Le type permet de classifier les vnements l'intrieur d'une mme classe
(par exemple, la classe KeyEvent englobe KEY_PRESSED, KEY_RELEASED, KEY_TYPED)
La source de l'vnement (Object consultable avec getSource())
Objet qui est l'origine de l'vnement selon la position dans la chane de
traitement des vnements (event dispatch chain).
La cible de l'vnement (EventTarget consultable avec getTarget())
Composant cible de l'vnement (indpendamment de la position dans la
chane de traitement des vnements (event dispatch chain)

IHM-1 FX06

Jacques BAPST

17

Types d'vnements
Chaque vnement est d'un certain type (objet de type EventType).
Chaque type d'vnement possde un nom (getName()) et un type
parent (getSuperType()).
Les types d'vnement forment donc une hirarchie.
Par exemple si on presse une touche le nom de l'vnement est
KEY_PRESSED et le type parent est KeyEvent.ANY.

A la racine, on a
Event.ANY
(= EventType.ROOT)

La figure reprsente une partie


seulement de la hirarchie des
types d'vnements.
IHM-1 FX06

Jacques BAPST

18

Gestion des vnement [1]


Le traitement des vnements implique les tapes suivantes :
La slection de la cible (Target) de l'vnement
vnement clavier le composant qui possde le focus
vnement souris le composant sur lequel se trouve le curseur
le composant au centre de la position initiale
Gestes continus

Si plusieurs composant se trouvent un emplacement donn c'est celui qui est


"au-dessus" qui est considr comme la cible.

La dtermination de la chane de traitement des vnements


(Event Dispatch Chain : chemin des vnements dans le graphe de scne)
Le chemin part de la racine (Stage) et va jusqu'au composant cible en
parcourant tous les nuds intermdiaires

Le traitement des filtres d'vnement (Event Filter)

Excute le code des filtres en suivant le chemin descendant, de la racine


(Stage) jusqu'au composant cible

Le traitement des gestionnaires d'vnement (Event Handler)

Excute le code des gestionnaires d'vnement en suivant le chemin


montant, du composant cible la racine (Stage)

IHM-1 FX06

Jacques BAPST

19

Gestion des vnement [2]


Un exemple d'application avec son graphe de scne.
Si l'utilisateur clique sur le bouton Insert, un vnement de type
Action va tre dclench et va se propager le long du chemin
correspondant la chane de traitement (Event Dispatch Chain).
Stage
Scene
BorderPane

Label

IHM-1 FX06

TextArea

HBox

Button

Button

Button

Insert

Delete

Quit

Jacques BAPST

20

Gestion des vnement [3]


L'vnement se propage d'abord vers le bas, depuis le nud racine
(Stage) jusqu' la cible (Target) - c'est--dire le bouton cliqu - et les
filtres (Event Filter) ventuellement enregistrs sont excuts (dans
l'ordre de passage).
Stage

Event Capturing Phase

Scene
BorderPane

Label

IHM-1 FX06

TextArea

HBox

Button

Button

Button

Insert

Delete

Quit

Jacques BAPST

21

Gestion des vnement [4]


L'vnement remonte ensuite depuis la cible jusqu' la racine et les
gestionnaires d'vnements (Event Listener) ventuellement
enregistrs sont excuts (dans l'ordre de passage).

Stage

Event Bubbling Phase

Scene
BorderPane

Label

IHM-1 FX06

TextArea

HBox

Button

Button

Button

Insert

Delete

Quit

Jacques BAPST

22

Gestion des vnements [5]


Pour grer un vnement (excuter des instructions), il faut crer un
rcepteur d'vnement (Event Listener), appel aussi couteur
d'vnement, et l'enregistrer sur les nuds du graphe de scne o
l'on souhaite intercepter l'vnement et effectuer un traitement.
Un rcepteur d'vnement peut tre enregistr comme filtre ou
comme gestionnaire d'vnement. La diffrence principale entre
les deux rside dans le moment o le code est excut :
Les filtres (filters) sont excuts dans la phase descendante de la chane
de traitement des vnements (avant les gestionnaires)
Les gestionnaires (handlers) sont excuts dans la phase montante de la
chane de traitement des vnements (aprs les filtres)

Les filtres, comme les gestionnaires d'vnements, sont des objets


qui doivent implmenter l'interface fonctionnelle (gnrique)
EventHandler<T extends Event> qui impose l'unique mthode
handle(T event) qui se charge de traiter l'vnement.
IHM-1 FX06

Jacques BAPST

23

Gestion des vnements [6]


Pour enregistrer un rcepteur d'vnement sur un nud du graphe
de scne, on peut :
Utiliser la mthode addEventFilter() que possdent tous les nuds
(les sous-classes de Node) et qui permet d'enregistrer un filtre
Utiliser la mthode addEventHandler() que possdent tous les nuds
(les sous-classes de Node) et qui permet d'enregistrer un gestionnaire
d'vnement
Utiliser une des mthodes utilitaires (convenience methods) dont
disposent certains composants et qui permettent d'enregistrer un
gestionnaire d'vnement en tant que proprit du composant.
La plupart des composants disposent de mthodes nommes selon le
schma setOnEventType(EventHandler), par exemple :

IHM-1 FX06

setOnAction(Handler)

setOnKeyTyped(Handler)
Jacques BAPST

24

Gestion des vnements [7]


Par dfaut, les vnements se propagent donc le long de la chane
de traitement (Event Dispatch Chain) en traversant le graphe de
scne de la racine jusqu'au composant cible et retour.
Sur chaque nud du graphe de scne peuvent tre enregistrs
un ou plusieurs filtres
un ou plusieurs gestionnaires d'vnements

qui se chargeront de traiter diffrents types d'vnements avant de


les propager au nud suivant en parcourant la chane de traitement.

Cependant, chaque rcepteur d'vnement (filtre ou gestionnaire) peut


interrompre la chane de traitement en consommant l'vnement,
cest--dire en invoquant la mthode consume().
Si un rcepteur d'vnement appelle la mthode consume(), la
propagation de l'vnement s'interrompt et les autres rcepteurs
(qui suivent dans la chane de traitement) ne seront plus activs.
IHM-1 FX06

Jacques BAPST

25

Gestion des vnements [8]


Si un nud du graphe de scne possde plusieurs rcepteurs
d'vnements enregistrs, l'ordre d'activation de ces rcepteurs
sera bas sur la hirarchie des types d'vnement :
Un rcepteur pour un type spcifique sera toujours excut avant un
rcepteur pour un type plus gnrique
Par exemple un filtre enregistr pour MouseEvent.MOUSE_PRESSED sera
excut avant un filtre pour MouseEvent.ANY qui sera excut avant un
filtre pour InputEvent.ANY
L'ordre d'excution des
rcepteurs pour des types
de mme niveau n'est
pas dfini
La consommation d'un
vnement n'interrompt
pas le traitement des autres
rcepteurs enregistrs sur le
mme nud
IHM-1 FX06

Jacques BAPST

26

Event Handling [1]


private
private
private
private
private
private
private

BorderPane
HBox
Label
TextArea
Button
Button
Button

root
btnPanel
lblTitle
txaMsg
btnInsert
btnDelete
btnQuit

=
=
=
=
=
=
=

new
new
new
new
new
new
new

BorderPane();
HBox(10);
Label("Event Handling");
TextArea();
Button("Insert");
Button("Delete");
Button("Quit");

primaryStage.setTitle("Event Handling");
root.setPadding(new Insets(10));
//--- Title
lblTitle.setFont(Font.font("System", FontWeight.BOLD, 20));
lblTitle.setTextFill(Color.DARKGREEN);
BorderPane.setAlignment(lblTitle, Pos.CENTER);
BorderPane.setMargin(lblTitle, new Insets(0, 0, 10, 0));
root.setTop(lblTitle);
//--- Text-Area
txaMsg.setWrapText(true);
txaMsg.setPrefColumnCount(15);
txaMsg.setPrefRowCount(10);
root.setCenter(txaMsg);
IHM-1 FX05

Jacques BAPST

27

Event Handling [2]


//--- Button Panel
btnPanel.getChildren().add(btnInsert);
btnPanel.getChildren().add(btnDelete);
btnPanel.getChildren().add(btnQuit);
btnPanel.setAlignment(Pos.CENTER_RIGHT);
btnPanel.setPadding(new Insets(10, 0, 0, 0));
root.setBottom(btnPanel);

Code de l'interface sans la


gestion des vnements.

Scene scene = new Scene(root);


primaryStage.setScene(scene);
primaryStage.show();

IHM-1 FX05

Jacques BAPST

28

Event Handling [3]


Pour traiter les vnements des boutons, on peut crer une classe
contrleur qui implmente EventHandler et effectue les oprations
souhaites dans la mthode handle().
public class InsertButtonController implements EventHandler<ActionEvent> {
private TextArea tArea;
//--- Constructeur --------------------------------public InsertButtonController(TextArea tArea) {
this.tArea = tArea;
}
//--- Code excut lorsque l'vnement survient ---@Override
public void handle(ActionEvent event) {
tArea.appendText("A");
}
}

IHM-1 FX06

Si on veut agir sur des


composants de la vue,
il faut transmettre les
rfrences ncessaires.

Si le code est plus complexe,


on invoquera de prfrence
une mthode de la vue.

Jacques BAPST

29

Event Handling [4]


Dans la vue, il faut ensuite crer une instance de ce contrleur et
l'enregistrer comme gestionnaire d'vnement (type ACTION) sur le
bouton concern en invoquant la mthode addEventHandler().
. . .
//--- Button Events Handling
InsertButtonController insertCtrl = new InsertButtonController(txaMsg);
btnInsert.addEventHandler(ActionEvent.ACTION, insertCtrl);
. . .

A chaque clic sur le bouton Insert, le


gestionnaire d'vnement sera excut
et un caractre 'A' sera ajout dans le
composant TextArea.

IHM-1 FX06

Jacques BAPST

30

Event Handling [5]


Une autre manire de faire consiste crer le contrleur sous la
forme d'une classe locale anonyme. Par exemple, pour le bouton
Delete :
. . .

//--- Button Events Handling


btnDelete.addEventHandler(ActionEvent.ACTION,
new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
txaMsg.deletePreviousChar();
}
});
. . .

A chaque clic sur le bouton Delete, le


gestionnaire d'vnement sera excut
et un caractre sera supprim dans le
composant TextArea.
IHM-1 FX06

Jacques BAPST

31

Event Handling [5]


Une troisime possibilit pour traiter les vnements des boutons,
est d'utiliser la mthode setOnAction() et passer en paramtre
une expression lambda implmentant la mthode handle() de
l'interface EventHandler.
Par exemple pour traiter les trois boutons de l'interface :
//--- Button Events Handling
btnInsert.setOnAction(event -> {
txaMsg.appendText("A");
});
btnDelete.setOnAction(event -> {
txaMsg.deletePreviousChar();
});
btnQuit.setOnAction(event ->

{
Platform.exit();
});

IHM-1 FX06

Jacques BAPST

32

Classe 'contrleur'
Dans la variante MVC synchrone, il est frquent que la classe du
contrleur reoive dans son constructeur les rfrences du modle
et de la vue.
public class ButtonController implements EventHandler<ActionEvent> {

private AppModel model;


private MainView view;

Rfrences du
modle et de la vue.

//--- Constructeur ------------------------------------------public ButtonController(AppModel model, MainView view) {


this.model = model;
this.view = view;
}
//--- Code excut lorsque l'vnement survient -------------@Override
public void handle(ActionEvent event) {
Le contrleur accde
int newVal = model.getInfo();
aux donnes du modle
view.updateInfo(newVal);
et met jour la vue.
}
}
IHM-1 FX06

Jacques BAPST

33

Mthodes setOn() [1]


Liste des principales actions associes des mthodes utilitaires qui
permettent d'enregistrer des gestionnaires d'vnements (il faut
rechercher les mthodes setOnEventType() dans la classe).
Action de l'utilisateur
Pression sur une touche du clavier
Dplacement de la souris ou pression sur une
de ses touches
Glisser-dposer avec la souris (Drag-and-Drop)
Glisser-dposer propre la plateforme (geste
par exemple)
Composant "scroll"
Geste de rotation
Geste de balayage/dfilement (swipe)
Un composant est touch
Geste de zoom
Activation du menu contextuel
IHM-1 FX06

vnement
KeyEvent

Dans classe
Node, Scene

MouseEvent

Node, Scene

MouseDragEvent

Node, Scene

DragEvent

Node, Scene

ScrollEvent

Node, Scene

RotateEvent

Node, Scene

SwipeEvent
TouchEvent
ZoomEvent
ContextMenuEvent

Node, Scene
Node, Scene
Node, Scene
Node, Scene

Jacques BAPST

34

Mthodes setOn() [2]


Action de l'utilisateur
vnement
Texte modifi (durant la saisie)
InputMethodEvent
Bouton cliqu
ComboBox ouverte ou ferme
Une des options d'un menu contextuel active ActionEvent
Option de menu active
Pression sur Enter dans un champ texte
lment (Item) d'une liste,

ListView.

d'une table ou

TableColumn.

d'un arbre a t dit

EditEvent
CellEditEvent
TreeView.

EditEvent
Erreur survenue dans le media-player
MediaErrorEvent
Menu est affich (droul) ou masqu (enroul) Event
Fentre popup masque
Event
Onglet slectionn ou ferm
Event
Fentre affiche, ferme, masque
WindowEvent
IHM-1 FX06

Jacques BAPST

Dans classe
Node, Scene
ButtonBase
ComboBoxBase
ContextMenu
MenuItem
TextField
ListView

TableColumn
TreeView
MediaView
Menu
PopupWindow
Tab
Window
35

Fantas'TIP [1]
Exemple d'application pour illustrer diffrentes manires de raliser
le dcoupage du code en exploitant plusieurs des techniques qui
sont disposition (classes EventHandler, expressions lambda, binding de
proprits, ), tout en respectant les principes de l'architecture MVC.
L'application Fantas'TIP est un utilitaire qui calcule le pourboire
prvoir et le montant par personne, en fonction du montant de la
note, du pourcentage octroy et du nombre de convives.

IHM-1 FX06

Jacques BAPST

36

Fantas'TIP [2]
Quatre variantes de cette application ont t cres :
Variante 1 : Avec un contrleur ralis sous forme de classe 'ordinaire'
Variante 2 : Avec un contrleur ralis sous forme d'expression lambda
Variante 3 : Avec un contrleur ralis sous forme de liaisons de hautniveau (high-level binding) entre les donnes d'entre
(saisies par l'utilisateur) et les donnes de sortie (calcules)
Variante 4 : Avec un contrleur ralis sous forme de liaisons de basniveau (low-level binding) entre les donnes d'entre
(saisies par l'utilisateur) et les donnes de sortie (calcules)

Quelques extraits de code (les lments importants) figurent dans les


pages qui suivent.
L'intgralit du code source des applications est disponible sur la page :
http://jacques.bapst.home.hefr.ch/ihm1/src/chap06_FantasTIP

Les applications (excutables) sont disponibles sur la page :


http://jacques.bapst.home.hefr.ch/ihm1/applic/fantastip
IHM-1 FX06

Jacques BAPST

37

Fantas'TIP [3]
Variante 1 : modle de l'application
Interface du modle
Classe implmentant cette interface
public interface IFantasTipModel {
void
setBill(double amount);
void
setTipPercent(int percent);
void
setNbPeople(int nbPeople);
double getTipPerPerson();
double getTotalPerPerson();
}
public class FantasTipModel implements IFantasTipModel {
private double
private int
private int

bill
tipPercent
nbPeople

= 0;
= 0;
= 1;

//---------------------------------------------------------------------------public FantasTipModel() {
}
//---------------------------------------------------------------------------@Override
public void setBill(double amount) {
if (amount < 0) throw new IllegalArgumentException("Amount < 0");
this.bill = amount;
}

IHM-1 FX06

Jacques BAPST

38

Fantas'TIP [4]
//---------------------------------------------------------------------------@Override
public void setTipPercent(int percent) {
if (percent < 0) throw new IllegalArgumentException("Percent < 0");
this.tipPercent = percent;
}
//---------------------------------------------------------------------------@Override
public void setNbPeople(int nbPeople) {
if (nbPeople <= 0) throw new IllegalArgumentException("Nb people <= 0");
this.nbPeople = nbPeople;
}
//---------------------------------------------------------------------------@Override
public double getTipPerPerson() {
return bill * tipPercent / 100.0 / nbPeople;
}
//---------------------------------------------------------------------------@Override
public double getTotalPerPerson() {
return bill/nbPeople + getTipPerPerson();
}
}

IHM-1 FX06

Jacques BAPST

39

Fantas'TIP [5]
Variante 1 : classe contrleur (du bouton 'Calculate')
public class FantasTipController implements EventHandler<ActionEvent> {
private IFantasTipModel model;
private FantasTipView
view;
//---------------------------------------------------------------------------// Constructor receives model and view references
//---------------------------------------------------------------------------public FantasTipController(IFantasTipModel model, FantasTipView
view) {
this.model = model;
this.view = view;
}
//---------------------------------------------------------------------------// Method executed when 'Calculate' button is pressed
//---------------------------------------------------------------------------@Override
public void handle(ActionEvent event) {
//--- Get values from view (check for errors) and update model data
try {
double bill
= view.getBillValue();
int
tipPercent = view.getTipPercentValue();
int
nbPeople
= view.getNbPeopleValue();
model.setBill(bill);
model.setTipPercent(tipPercent);
model.setNbPeople(nbPeople);
}
catch (IllegalStateException e) {
return;
}

// Errors in some input values

//--- Update view with values from model output data


view.updateTipPerPerson(model.getTipPerPerson());
view.updateTotalPerPerson(model.getTotalPerPerson());
}
}

IHM-1 FX06

Jacques BAPST

40

Fantas'TIP [6]
Variante 1 : cration d'une instance du contrleur et association au
bouton Calculate.
Ces activits sont effectues dans le code de la vue (la mthode prive
createController() est appele dans la mthode start()).
. . .
//---------------------------------------------------------------------------// Create button controller instance and associate it to button
//---------------------------------------------------------------------------private void createController() {
controller = new FantasTipController(model, this);
btnCalc.setOnAction(controller);
}
. . .

IHM-1 FX06

Jacques BAPST

41

Fantas'TIP [7]
Variante 2 : le contrleur est cr sous forme d'expression lambda.
L'expression lambda est crite dans le code de la vue (la mthode
createController() est appele dans la mthode start()).
. . .
//---------------------------------------------------------------------------// Create button controller with lambda expression
//---------------------------------------------------------------------------private void createController() {
btnCalc.setOnAction(event -> {
try {
double bill
= getBillValue();
int
tipPercent = getTipPercentValue();
int
nbPeople
= getNbPeopleValue();
model.setBill(bill);
model.setTipPercent(tipPercent);
model.setNbPeople(nbPeople);
}
catch (IllegalStateException e) {
return;
}

// Errors in some input values

Mme si le code du
contrleur est crit
dans le fichier source
de la vue, l'expression
lambda constitue bien
un contrleur qui sera
reprsent par une
instance d'une classe
anonyme qui
implmente l'interface
EventHandler.

//--- Update view with values from model output data


updateTipPerPerson(model.getTipPerPerson());
updateTotalPerPerson(model.getTotalPerPerson());
});
}

IHM-1 FX06

Jacques BAPST

42

Fantas'TIP [8]
Dans les variantes 3 et 4, les vues possdent des liaisons (par
binding) entre les donnes d'entres et les donnes de sortie.
L'interface n'a donc plus besoin de bouton pour dclencher le calcul.
Les donnes de sortie sont automatiquement mises jour lorsqu'on
change les donnes d'entre (durant la saisie des champs texte).

IHM-1 FX06

Jacques BAPST

43

Fantas'TIP [9]
Variante 3 : le contrleur est constitu par les liaisons (bindings)
cres entre les proprits des composants d'entre et ceux de
sortie.
Dans cette variante, on cre des liaisons dites de haut-niveau (highlevel bindings) car les oprations sont effectues par des invocations
de mthodes en cascade. Cet enchanement d'appels est rendu
possible par l'utilisation d'un modle de conception appel fluent
interface pattern ou fluent API.
Les oprations disponibles sont limites mais suffisantes pour les calculs
ncessaires dans l'application propose.
Ex : result = a.multiply(b).add(c.multiply(d));

Des mthodes statiques de la classe Bindings peuvent galement


tre utilises pour effectuer des oprations de haut-niveau entre des
proprits.
Ex :
IHM-1 FX06

result = Bindings.add(Bindings.multiply(a, b),


Bindings.multiply(c, d));
Jacques BAPST

44

Fantas'TIP [10]
Variante 3 : mthode createViewModelBindings() appele dans la
mthode start() pour crer les liaisons entre les proprits.
. . .

private void createViewModelBindings() {


//--- Bind view text data with model number data (converter needed)
tfdBill.textProperty().bindBidirectional(model.getBillPty(),
dsConverter());
tfdTipPct.textProperty().bindBidirectional(model.getTipPercentPty(),
isConverter());
tfdNbPple.textProperty().bindBidirectional(model.getNbPeoplePty(),
isConverter());
//--- Bind 'output' model properties to calculated properties
model.getTipPerPersonPty().bind(model.getBillPty()
.multiply(model.getTipPercentPty())
.divide(100)
.divide(model.getNbPeoplePty()));
model.getTotalPerPersonPty().bind((model.getBillPty()
.divide(model.getNbPeoplePty())
.add(model.getTipPerPersonPty())));
//--- Bind TextField properties to model properties converted to String
tfdRTip.textProperty().bind(model.getTipPerPersonPty().asString());
tfdRTotal.textProperty().bind((model.getTotalPerPersonPty().asString()));

IHM-1 FX06

Jacques BAPST

45

Fantas'TIP [11]
Variante 3 : mthode dsConverter() utilise pour convertir une
proprit de type double en String et inversement.
. . .

//---------------------------------------------------------------------------// Number(Double) <--> String Converter


//---------------------------------------------------------------------------private NumberStringConverter dsConverter() {
return new NumberStringConverter() {
@Override
public Number fromString(String value) {
try {
return Double.parseDouble(value);
}
catch (NumberFormatException e) {
return Double.NaN;
}
}
@Override
public String toString() {
return super.toString();
}
};
}

IHM-1 FX06

Jacques BAPST

46

Fantas'TIP [12]
Variante 4 : le contrleur est constitu par les liaisons (bindings) de
bas-niveau (low-level bindings) entre les proprits des composants
d'entre et ceux de sortie.
Pour crer des liaisons de bas-niveau, on redfinit les mthodes
computeValue() de liaisons existantes (on cre des sous-classes de
IntegerBinding, DoubleBinding, StringBinding, etc.).
On dispose dans ce cas de tout le potentiel des instructions et des
librairies disposition pour effectuer les calculs qui lient les proprits
(aussi complexes soient-ils).
Ne pas oublier de dfinir toutes les proprits dont dpend la liaison en
invoquant la mthode parente super.bind(p1, p2, )

IHM-1 FX06

Jacques BAPST

47

Fantas'TIP [13]
Variante 4 : mthode dblTipPerPersonBinding() qui retourne une
spcialisation de DoubleBinding (qui calcule le pourboire par convive).
. . .

//---------------------------------------------------------------------------// Low-level binding (calculate TipPerPerson from 'input' properties)


//---------------------------------------------------------------------------private DoubleBinding dblTipPerPersonBinding() {
DoubleBinding dblBinding = new DoubleBinding() {
{
super.bind(model.getBillPty(),
model.getTipPercentPty(),
model.getNbPeoplePty());
}
@Override
protected double computeValue() {
double tipPP = model.getBillPty().get() *
model.getTipPercentPty().get()/100.0 /
model.getNbPeoplePty().get();
return tipPP;
}
};
return dblBinding;

IHM-1 FX06

Jacques BAPST

48

Fantas'TIP [14]
Variante 4 : mthode strTipPerPersonBinding() qui retourne une
spcialisation de StringBinding (qui convertit et formate la valeur).
. . .

//---------------------------------------------------------------------------// Low-level binding (convert TipPerPerson to formatted string)


//---------------------------------------------------------------------------private StringBinding strTipPerPersonBinding() {
StringBinding strBinding = new StringBinding() {
{
super.bind(model.getTipPerPersonPty());
}
@Override
protected String computeValue() {
double tipPP = model.getTipPerPersonPty().get();
if (tipPP < 0) return "n/a";
String fmtRes = String.format("%.2f", tipPP);
return fmtRes;
}
};
return strBinding;
}

IHM-1 FX06

Jacques BAPST

49

Fantas'TIP [15]
Variante 4 : mthode createViewModelBindings() qui effectue
l'ensemble des liaisons entre les diffrentes proprits.
. . .

//---------------------------------------------------------------------------// Create all bindings (view-model-view)


//---------------------------------------------------------------------------private void createViewModelBindings() {
//--- Bind view text data with model number data (converter needed)
tfdBill.textProperty().bindBidirectional(model.getBillPty(),
dsConverter());
tfdTipPct.textProperty().bindBidirectional(model.getTipPercentPty(),
isConverter());
tfdNbPple.textProperty().bindBidirectional(model.getNbPeoplePty(),
isConverter());
//--- Bind 'output' model properties to low-level calculated properties
model.getTipPerPersonPty().bind(dblTipPerPersonBinding());
model.getTotalPerPersonPty().bind(dblTotalPerPersonBinding());
//--- Bind TextField properties to model properties converted to String
tfdRTip.textProperty().bind(strTipPerPersonBinding());
tfdRTotal.textProperty().bind(strTotalPerPersonBinding());
}

IHM-1 FX06

Jacques BAPST

50