I. Introduction - partie 2▲
Pour faire suite à la partie 1, nous nous attaquons ici à la communication d'événements entre objets graphiques ou métiers. Cette communication est un élément important dans le développement d’interfaces graphiques car elle permet de découpler le code et d’avoir des composants indépendants et réutilisables tout cela aidé par l’application de patterns type MVC, MVP, MVVM et plein d’autres.
Comme expliqué dans la première partie, nous nous appuyons sur le framework MVVMFX qui nous apporte différents modes de communication. Il permet, par exemple, d'avoir une liaison (« binding ») des propriétés entre la vue et le contrôleur, de publier des notifications entre le contrôleur et la vue et enfin de publier des événements entre certains contrôleurs ou tous.
Pour rappel, l’initialisation du framework MVVMFX se fait dans la classe « main » en surchargeant MvvmfxGuiceApplication et non pas Application comme pour une application JavaFX classique. Le « Guice » ici fait référence au système d'injection des dépendances de Google. Il est possible d'utiliser d'autres implémentations d'injection de dépendances tel que JBoss Weld ou bientôt Spring. Cela sera utile pour la communication entre la vue et le contrôleur (injection du contrôleur dans la vue) ou encore la communication entre contrôleurs.
II. Communication par liaison simple▲
Il y a plusieurs niveau de communication entre la vue et le contrôleur et leur usage va dépendre des cas d’utilisation mais l’idée générale est que la vue traite l’interaction avec l’utilisateur alors que le contrôleur s’occupe des échanges avec les machines (services, base de données…).
II-A. Communication entre la vue et le contrôleur▲
Il s'agit d'un lien entre une propriété (de type « Property ») de la vue et une autre du contrôleur. Quand une propriété change, l'autre est automatiquement notifiée et mise à jour (observer pattern). Il y a soit un « binding » unidirectionnel (donc une notification que dans un sens) ou bien bidirectionnel. Nous privilégions généralement ce deuxième usage afin d'initialiser la valeur dans le contrôleur et de récupérer les mises à jour par la vue.
Pour illustrer ce cas, nous créons une pop-up About appelée depuis notre menu principal.
Dans le package gui, ajoutez un sous répertoire about qui contient les 3 fichiers de base d'un composant.
Comme vu dans la première partie, le fichier XML contient le descriptif de l’interface graphique, complété par la classe AboutView. Et la classe AboutViewModel fait le lien entre la présentation et la logique ou les données métier.
Dans la fenêtre about, nous souhaitons afficher le numéro de version et le numéro de build. La vue s'occupe d'afficher un texte donné par le contrôleur par liaison simple.
1. Dans le contrôleur, nous créons un objet de type « Property » qui contient le texte à afficher et qui est initialisé ici. Les types simples ont des propriétés déjà disponible comme SimpleStringProperty ou SimpleIntegerProperty et pour des types plus complexes nous utilisons un objet genérique de type Property<T>.
public
class
AboutViewModel implements
ViewModel {
private
StringProperty versionNumber =
new
SimpleStringProperty
(
);
public
void
initialize
(
) {
versionNumber.setValue
(
"Version: "
+
App.APP_VERSION);
}
public
StringProperty versionNumberProperty
(
) {
return
versionNumber;
}
}
2. Dans le fichier XML, nous ajoutons le label avec un id, de type « fx:id » pour y accéder depuis le « code-behind », ie. la classe vue. Cet id a le même nom que la variable défini dans la classe vue (votre IDE peut vous générer la variable depuis le XML). Au passage, nous réduisons la taille de la fenêtre et ajoutons un bouton pour fermer la pop-up:
<AnchorPane
xmlns
=
"http://javafx.com/javafx"
xmlns
:
fx
=
"http://javafx.com/fxml"
fx
:
controller
=
"com.djay.gui.about.AboutView"
prefHeight
=
"200.0"
prefWidth
=
"450.0"
>
<Label
layoutX
=
"20"
layoutY
=
"40"
fx
:
id
=
"version"
/>
<Button
layoutX
=
"200"
layoutY
=
"160"
onAction
=
"#close"
text
=
"Fermer"
/>
</AnchorPane>
3. Enfin dans la vue, nous effectuons la liaison entre le « Label » affiché et la « Property » de notre contrôleur :
public
class
AboutView implements
FxmlView<
AboutViewModel>
{
public
Label version;
private
Stage aboutDialog;
@InjectViewModel
private
AboutViewModel viewModel;
public
void
initialize
(
) {
version.textProperty
(
).bindBidirectional
(
viewModel.versionNumberProperty
(
));
}
public
void
close
(
) {
aboutDialog.close
(
);
}
public
void
setStage
(
Stage dialogStage) {
this
.aboutDialog =
dialogStage;
}
}
Dans la méthode « initialize » de la vue, nous avons la liaison bidirectionnelle du contenu du label avec le contrôleur. Ainsi, les propriétés ont toujours la même valeur.
Enfin, pour faciliter l’appel du DialogAbout depuis le menu, nous ajoutons une classe « DialogHelper » dans un package « util » qui va créer une nouvelle scène et les éléments associés et lui appliquer la propriété « APPLICATION_MODAL » :
public
class
DialogHelper {
/**
*/
public
static
void
initDialog
(
BooleanProperty openProperty, final
Stage parentStage, Supplier<
Parent>
rootSupplier) {
final
Stage dialogStage =
new
Stage
(
StageStyle.UTILITY);
dialogStage.initOwner
(
parentStage);
dialogStage.initModality
(
Modality.APPLICATION_MODAL);
openProperty.addListener
((
obs, oldValue, newValue) ->
{
if
(
newValue) {
// when it is the first time the dialog is made visible (and therefore no scene exists) ...
if
(
dialogStage.getScene
(
) ==
null
) {
// ... we create a new scene and register it in the stage.
Scene dialogScene =
new
Scene
(
rootSupplier.get
(
));
dialogScene.getStylesheets
(
).add
(
"/contacts.css"
);
dialogStage.setScene
(
dialogScene);
}
else
{
// ... otherwise we simple bring the dialog to front.
dialogStage.toFront
(
);
}
dialogStage.sizeToScene
(
);
dialogStage.show
(
);
}
else
{
dialogStage.close
(
);
}
}
);
// when the user clicks on the close button of the dialog window
// we want to set the property to false
dialogStage.setOnCloseRequest
(
event ->
openProperty.set
(
false
));
}
public
static
Stage showDialog
(
Parent view, Stage parentStage, StageStyle stageStyle, String... sceneStyleSheets) {
final
Stage dialogStage =
new
Stage
(
StageStyle.UTILITY);
dialogStage.initOwner
(
parentStage);
dialogStage.initModality
(
Modality.APPLICATION_MODAL);
if
((
stageStyle !=
null
)) {
dialogStage.initStyle
(
stageStyle);
}
else
{
dialogStage.initStyle
(
StageStyle.DECORATED);
}
if
(
dialogStage.getScene
(
) ==
null
) {
// ... we create a new scene and register it in the stage.
Scene dialogScene =
new
Scene
(
view);
dialogScene.getStylesheets
(
).addAll
(
sceneStyleSheets);
dialogStage.setScene
(
dialogScene);
dialogStage.sizeToScene
(
);
dialogStage.show
(
);
return
dialogStage;
}
return
null
;
}
}
Du coup, dans la classe « MenuView » la méthode « about » appelée quand on click sur le menu, initialise les trois fichiers habituels du composant about (vue et controleur) et les passe au dialog helper précédent :
public
void
about
(
) {
ViewTuple<
AboutView, AboutViewModel>
aboutView =
FluentViewLoader.fxmlView
(
AboutView.class
).load
(
);
aboutView.getView
(
).getStylesheets
(
).add
(
"/css/dialog.css"
);
Stage dialogStage =
DialogHelper.showDialog
(
aboutView.getView
(
), primaryStage, StageStyle.TRANSPARENT);
aboutView.getCodeBehind
(
).setStage
(
dialogStage);
}
Afin de pouvoir fermer la boite de dialogue, nous passons le « stage » dans un setter :
aboutView.getCodeBehind
(
).setStage
(
dialogStage);
Pour rappel, une application JavaFX est comme une représentation d’un spectacle : on retrouve la scène (Stage) puis l’estrade (Scene) et enfin les éléments qui la compose (Pane et autres nœuds).
Et voilà, quand vous ouvrez le dialog About, la valeur affichée provient de votre contrôleur. Si celle-ci est modifiée, la valeur sera automatiquement changée dans la vue.
II-B. Note sur l’affichage du numéro de build et du numéro de version avec Maven▲
Dans cette exemple, nous affichons le numéro de version et de build qui nous sont donné par une méthode statique de notre classe main. Nous souhaitons ne pas dupliquer cette donnée et estimons que le changement doit se faire uniquement dans le pom.xml.
Le problème est alors de récupérer cette information dans un fichier projet depuis notre instance.
Une façon de résoudre ce problème est de créer un fichier version.txt dans vos ressources et qui contient :
version=${pom.version}
build.date=${timestamp}
Dans le fichier pom.xml, il faut ajouter la propriété « timestamp » utilisée qui se base sur l’instruction « maven.build » et nous ajoutons une instruction de formatage du build juste en dessous. Enfin, il ne faut pas oublier d’ajouter l’instruction de « filtering » à true pour le répertoire de reources afin que maven parse le fichier :
<properties>
<!-- autres propriétés ici -->
<timestamp>
${maven.build.timestamp}</timestamp>
<maven.build.timestamp.format>
yyyy-MM-dd HH:mm:ss</maven.build.timestamp.format>
</properties>
<build>
<!-- use resource files (fxml, css) from the java directory -->
<resources>
<resource>
<directory>
src/main/java</directory>
</resource>
<resource>
<directory>
src/main/resources</directory>
<filtering>
true</filtering>
</resource>
</resources>
<!-- Autres instructions de build à la suite →
</build>
A l’exécution du goal « compile » de Maven, ce dernier va générer le fichier version.txt avec les données du pom.xml
III. Communication par événements internes▲
Le framework MVVMFX nous apporte un système de notification entre la vue et le contrôleur. Cela est utilisé quand des traitements sont effectués depuis le contrôleur, comme des appels à des traitements asynchrones, et que nous souhaitons ensuite notifier la vue.
Nous venons de voir que pour mettre à jour des données nous pouvons avoir la liaison par propriété. Maintenant si nous souhaitons faire un traitement plus important dans la vue, comme une actualisation complète, nous pouvons notifier la vue par des messages depuis le contrôleur.
Tout d'abord, nous avons un lien entre la vue et le contrôleur qui nous est donné par l'annotation « @InjectViewModel ». Cette annotation permet d’avoir une référence au contrôleur depuis la vue.
Il est alors possible d'obtenir des données du contrôleur en appelant ses méthodes (exemple RefreshPositions ci-dessous). Mais dans ce cas, la vue est couplée aux méthodes du contrôleur car elle doit connaître ses méthodes.
L'autre cas d'usage est de notifier la vue des changements de données depuis le contrôleur (exemple RefreshInstruments ci-dessous). Dans ce cas, le contrôleur publie un message que la vue écoute via la methode « subscribe » pour ensuite lancer un traitement.
Pour illustrer ces deux cas, nous ajoutons une page centrale « Dashboard » avec les trois fichiers habituels (vue, fxml et contrôleur) et qui contient deux panneaux avec des statistiques sur les positions et instruments. Nous ajoutons deux boutons dans la vue pour simuler les deux types de communication.
Le code de la vue:
public
class
DashboardView implements
FxmlView<
DashboardViewModel>
{
public
StackPane statsPositionsPane;
public
StackPane statsInstrumentsPane;
public
Label nbActivePositionText;
public
Label nbActiveInstrumentText;
@InjectViewModel
DashboardViewModel viewModel;
public
void
initialize
(
) {
// Souscrit aux messages provenant du controleur
viewModel.subscribe
(
viewModel.REFRESH_DATA, (
k, payload) ->
{
Integer nbInstruments =
(
Integer) payload[0
];
if
(
nbInstruments!=
null
) {
refreshFromController
(
nbInstruments);
}
}
);
// Effet visuel sur les panneaux de stats pour les faire resortir par defaut et les changer quand la souris passe dessus
// pour illustrer un autre moyen de le faire qu'en css
JFXDepthManager.setDepth
(
statsPositionsPane, 1
);
JFXDepthManager.setDepth
(
statsInstrumentsPane, 1
);
statsPositionsPane.hoverProperty
(
).addListener
((
observable, oldValue, newValue) ->
{
if
(
newValue)
changeVisualEffectPane
(
statsPositionsPane, 0
);
else
changeVisualEffectPane
(
statsPositionsPane, 1
);
}
);
statsInstrumentsPane.hoverProperty
(
).addListener
((
observable, oldValue, newValue) ->
{
if
(
newValue)
changeVisualEffectPane
(
statsInstrumentsPane, 0
);
else
changeVisualEffectPane
(
statsInstrumentsPane, 1
);
}
);
}
private
void
refreshFromController
(
Integer nbInstruments) {
// Le controlleur vient de notifier la vue qu'un changement a eu lieu.
// Il est aussi possible de passer la donnée dans le contenu du message.
nbActiveInstrumentText.setText
(
nbInstruments +
" instruments utilisés"
);
}
public
void
refreshPostions
(
ActionEvent actionEvent) {
nbActivePositionText.setText
(
viewModel.getOpenPositions
(
));
}
public
void
refreshInstruments
(
ActionEvent actionEvent) {
// Appelle le controleur pour simuler des changements de données
viewModel.refreshInstrumentSimulation
(
);
}
private
void
changeVisualEffectPane
(
StackPane pane, int
level) {
JFXDepthManager.setDepth
(
pane, level);
}
}
Dans la partie graphique nous utilisons un « JFXMasonryPane » de la bibliothèque Jfoenix.
<JFXMasonryPane
fx
:
id
=
"masonryPane"
HSpacing
=
"20"
VSpacing
=
"20"
>
<StackPane
fx
:
id
=
"statsPositionsPane"
prefHeight
=
"220"
prefWidth
=
"220"
styleClass
=
"stat-pane"
>
<VBox>
<children>
<Button
fx
:
id
=
"refresh1"
onAction
=
"#refreshPostions"
text
=
"MàJ Positions"
>
<VBox.margin>
<Insets
top
=
"10.0"
/>
</VBox.margin>
</Button>
<Label
fx
:
id
=
"nbActivePositionText"
text
=
"0 position active"
>
<VBox.margin>
<Insets
top
=
"50.0"
/>
</VBox.margin>
</Label>
</children>
<padding>
<Insets
bottom
=
"20.0"
left
=
"20.0"
right
=
"20.0"
top
=
"20.0"
/>
</padding>
</VBox>
</StackPane>
<StackPane
fx
:
id
=
"statsInstrumentsPane"
prefHeight
=
"220"
prefWidth
=
"220"
styleClass
=
"stat-pane"
>
<VBox>
<children>
<Button
fx
:
id
=
"refresh2"
onAction
=
"#refreshInstruments"
text
=
"MàJ Instruments"
>
<VBox.margin>
<Insets
top
=
"10.0"
/>
</VBox.margin>
</Button>
<Label
fx
:
id
=
"nbActiveInstrumentText"
text
=
"0 instrument utilisé"
>
<VBox.margin>
<Insets
top
=
"50.0"
/>
</VBox.margin>
</Label>
</children>
<padding>
<Insets
bottom
=
"20.0"
left
=
"20.0"
right
=
"20.0"
top
=
"20.0"
/>
</padding>
</VBox>
</StackPane>
<padding>
<Insets
bottom
=
"20.0"
left
=
"20.0"
right
=
"20.0"
top
=
"20.0"
/>
</padding>
</JFXMasonryPane>
Et enfin le contrôleur qui simule des traitements :
public
class
DashboardViewModel implements
ViewModel {
public
static
String REFRESH_DATA =
"Refresh data message"
;
public
void
refreshInstrumentSimulation
(
) {
// Nous simulons par l'appel à cette methode un traitement long
// Une fois terminé, nous envoyons une notification à la vue
try
{
Thread.sleep
(
500
);
}
catch
(
InterruptedException e) {
e.printStackTrace
(
);
}
publish
(
REFRESH_DATA, new
Random
(
).nextInt
(
100
) +
10
);
}
public
String getOpenPositions
(
) {
Integer simulatedNb =
new
Random
(
).nextInt
(
20
);
return
simulatedNb +
" positions actives."
;
}
}
Pour afficher le dashboard par défaut dans le contenu principal de l’application, nous modifions la « MainView » pour inclure la vue du dashboard par programmation :
public
class
MainView implements
FxmlView<
MainViewModel>
{
public
JFXTabPane tabPane;
public
void
initialize
(
) {
final
ViewTuple dashboard =
FluentViewLoader.fxmlView
(
DashboardView.class
).load
(
);
tabPane.getTabs
(
).add
(
new
Tab
(
"Dashboard"
, dashboard.getView
(
)));
}
}
Ici on remarque l’apport du framework MVVMFX qui facilite le chargement d’une vue avec la classe « FluentViewLoader ».
A ce stade, vous devriez avoir ceci et en cliquant sur les boutons vous simulez des traitements et une communication entre la vue et le contrôleur :
IV. Communication par événements entre composant▲
Comme autre moyen fourni par MVVMFX pour la communication entre composant, nous avons les events par scope. L'objet Scope est utilisé uniquement au niveau des contrôleurs. Pour illustrer cet usage, le click sur le « hamburger » (l’icône avec les trois barres horizontales) va afficher la barre de navigation latérale. Le click et le résultat sont dans des composants différents.
IV-A. Préparation cosmétique de la barre de navigation latérale▲
Nous devons commencer par modifier le composant centrale pour avoir une barre de navigation latérale rétractable et un contenu centrale en onglets. L’icône hamburger qui ouvre ou ferme la barre de navigation se trouve dans le menu.
Nous modifions le menu pour y ajouter l’icône et encapsulons le menu existant dans une « vbox » :
<VBox AnchorPane.
bottomAnchor
=
"0.0"
AnchorPane.
leftAnchor
=
"0.0"
AnchorPane.
rightAnchor
=
"0.0"
AnchorPane.
topAnchor
=
"0.0"
fx
:
id
=
"menuPane"
>
<children>
<MenuBar AnchorPane.
bottomAnchor
=
"0.0"
AnchorPane.
leftAnchor
=
"0.0"
AnchorPane.
rightAnchor
=
"0.0"
AnchorPane.
topAnchor
=
"0.0"
>
<menus>
<Menu
mnemonicParsing
=
"false"
text
=
"%menu.file"
>
<items>
<MenuItem
mnemonicParsing
=
"false"
text
=
"%menu.quit"
onAction
=
"#quit"
/>
</items>
</Menu>
<Menu
mnemonicParsing
=
"false"
text
=
"%menu.help"
>
<items>
<MenuItem
mnemonicParsing
=
"false"
onAction
=
"#about"
text
=
"%menu.about"
/>
</items>
</Menu>
</menus>
</MenuBar>
<JFXToolbar>
<leftItems>
<JFXRippler
maskType
=
"CIRCLE"
style
=
"-fx-ripple-color:WHITE;"
>
<StackPane
fx
:
id
=
"titleBurgerContainer"
onMouseClicked
=
"#hamburgerClicked"
>
<JFXHamburger
fx
:
id
=
"titleBurger"
>
<HamburgerSlideCloseTransition/>
</JFXHamburger>
</StackPane>
</JFXRippler>
<Label
text
=
""
></Label>
</leftItems>
</JFXToolbar>
</children>
</VBox>
Nous ajoutons le triplet de fichier (Vue xml, la classe de vue et son contrôleur) dans un package content, avec les onglets du main actuel et nous modifions les 3 fichiers du main ainsi :
- nous remplaçons les onglets par un objet de type drawer qui va construire une barre latérale et un contenu centrale
<!-- Contenu principale -->
<center>
<JFXDrawer
fx
:
id
=
"drawer"
defaultDrawerSize
=
"180"
direction
=
"LEFT"
styleClass
=
"body"
>
</JFXDrawer>
</center>
- La vue initialise l’objet Drawer et va écouter le contrôleur pour ouvrir ou fermer la barre latérale :
public
class
MainView implements
FxmlView<
MainViewModel>
{
public
JFXDrawer drawer;
@InjectViewModel
private
MainViewModel viewModel;
public
void
initialize
(
) {
final
ViewTuple navigationView =
FluentViewLoader.fxmlView
(
NavigationView.class
).load
(
);
drawer.setSidePane
(
navigationView.getView
(
));
drawer.setOverLayVisible
(
false
);
// traitement des messages du controller
viewModel.subscribe
(
MainViewModel.SIDE_PANE, (
k,v) ->
openCloseSidePane
(
));
// Affichage du conteneur qui contient le contenu
final
ViewTuple contentView =
FluentViewLoader.fxmlView
(
ContentView.class
).load
(
);
drawer.setContent
(
contentView.getView
(
));
}
private
void
openCloseSidePane
(
) {
if
(
drawer.isClosed
(
) ||
drawer.isClosing
(
)) {
drawer.open
(
);
}
else
{
drawer.close
(
);
}
}
}
- Pour l’instant le contrôleur ne fait rien mais c’est lui qui recevra les notifications par « scope »
public
class
MainViewModel implements
ViewModel {
public
static
final
String SIDE_PANE =
"Show/Hide side pane"
;
}
IV-B. Traitement des événements par Scope▲
Nous ajoutons un objet scope spécifique qui implémente l’interface « Scope » que nous fournit le framework MVVMFX. Le principe est que les événements sont transmis par contexte et donc à une partie de l’application. Il faut garder en tête que les composants sont en structure d’arbre et que le contexte sera disponible pour le nœud et tous ses fils. N’hésitez pas à consulter le wiki des scopes.
import
de.saxsys.mvvmfx.Scope;
public
class
MenuScope implements
Scope {
public
static
final
String SIDE_PANE =
"SIDE_PANE"
;
public
static
final
String MAIN_CONTENT =
"ADD TAB IN MAIN CONTENT"
;
}
Nous déclarons deux String pour identifier les messages.
Puis dans MainViewModel, qui est un des plus haut nœud de l’aplication, nous ajoutons la déclaration de scope à l’aide de l’annotation « @ScopeProvider » et nous l’injectons pour pouvoir l’utiliser dans l’initialize qui écoute les evenements du scope et qui notifie la vue ensuite :
@ScopeProvider
(
scopes =
{
MenuScope.class
}
)
public
class
MainViewModel implements
ViewModel {
@InjectScope
MenuScope menuScope;
public
static
final
String SIDE_PANE =
"Show/Hide side pane"
;
public
void
initialize
(
) {
menuScope.subscribe
(
MenuScope.SIDE_PANE, (
k,v) ->
publish
(
SIDE_PANE));
}
}
Il reste à envoyer un evenement sur le scope lors du click sur le hamburger. Dans MenuView, on appelle une méthode du controleur et dans ce dernier, nous injectons le scope puis publions l’evenement :
public
void
hamburgerClicked
(
MouseEvent mouseEvent) {
menuViewModel.notifyShowHideSidePane
(
);
}
Et le controleur :
public
class
MenuViewModel implements
ViewModel {
@InjectScope
MenuScope menuScope;
public
void
notifyShowHideSidePane
(
) {
menuScope.publish
(
MenuScope.SIDE_PANE);
}
}
Et voilà, vous pouvez maintenant ouvrir ou ferme le menu latérale en cliquant sur l’icône « hamburger ».
IV-C. Traitement par événements globaux▲
Pour ajouter des onglets au contenu principal quand on clique sur un des menus latérale, nous utilisons des événements globaux disponible via l’objet « NotificationCenter » fourni par MVVMFX. Il s’agit d’un mécanisme similaire aux Scopes sauf qu’il n’y a pas de déclaration de scope à faire.
Dans la vue du menu latérale, nous ajoutons l’appel de la méthode du contrôleur :
public
class
NavigationView implements
FxmlView<
NavigationViewModel>
{
@InjectViewModel
private
NavigationViewModel viewModel;
public
void
showDashboard
(
) {
viewModel.addMainContent
(
NavigationViewModel.DASHBOARD);
}
public
void
showPortfolios
(
) {
viewModel.addMainContent
(
NavigationViewModel.PORTFOLIOS);
}
public
void
showInstruments
(
) {
viewModel.addMainContent
(
NavigationViewModel.INSTRUMENTS);
}
}
Puis dans le contrôleur, nous envoyons le message via le NotificationCenter que nous avons injecté:
public
class
NavigationViewModel implements
ViewModel {
@Inject
private
NotificationCenter notificationCenter;
public
static
final
String MAIN_CONTENT =
"ADD TAB IN MAIN CONTENT"
;
public
static
final
String DASHBOARD =
"Dashboard"
;
public
static
final
String PORTFOLIOS =
"Portfolios"
;
public
static
final
String INSTRUMENTS =
"Instruments"
;
public
void
addMainContent
(
String tabType) {
notificationCenter.publish
(
MAIN_CONTENT, tabType);
}
}
Il faut créer le contenu des 2 onglets manquants en ajoutant pour chacun nos 3 fichiers d’un composant (Vue FXML, Vue Java et VueModel).
Dans le « Content » qui gère l’ajout d’onglet dans le conteneur principale, nous ajoutons un écouteur d’événement sur le « NotificationCenter » qui va ensuite prévenir la vue d’ajouter un onglet d’un type passé en paramètre dans l’événement.
public
class
ContentViewModel implements
ViewModel {
@Inject
NotificationCenter notificationCenter;
public
static
final
String ACTION_ADD_COMPONENT =
"Add component in the view"
;
public
void
initialize
(
) {
notificationCenter.subscribe
(
NavigationViewModel.MAIN_CONTENT, (
k, v) ->
{
if
(
v.length==
1
&&
v[0
] instanceof
String) {
String tabType =
(
String) v[0
];
addTab
(
tabType);
}
}
);
}
private
void
addTab
(
String tabType) {
publish
(
ACTION_ADD_COMPONENT, tabType);
}
}
Et la vue écoute l’événement du contrôleur :
public
class
ContentView implements
FxmlView<
ContentViewModel>
{
public
JFXTabPane tabPane;
@InjectViewModel
private
ContentViewModel viewModel;
public
void
initialize
(
) {
// Charge un onglet par defaut
final
ViewTuple dashboard =
FluentViewLoader.fxmlView
(
DashboardView.class
).load
(
);
tabPane.getTabs
(
).add
(
new
Tab
(
"Dashboard"
, dashboard.getView
(
)));
// Ecoute l"evenement pour ajouter un nouvel onglet
viewModel.subscribe
(
ContentViewModel.ACTION_ADD_COMPONENT, (
key, payload) ->
{
String tabType =
(
String) payload[0
];
Parent tabView =
null
;
switch
(
tabType) {
case
NavigationViewModel.DASHBOARD:
tabView =
FluentViewLoader.fxmlView
(
DashboardView.class
).load
(
).getView
(
);
break
;
case
NavigationViewModel.INSTRUMENTS:
tabView =
FluentViewLoader.fxmlView
(
InstrumentsView.class
).load
(
).getView
(
);
break
;
case
NavigationViewModel.PORTFOLIOS:
tabView =
FluentViewLoader.fxmlView
(
PortfolioView.class
).load
(
).getView
(
);
break
;
}
if
(
tabView!=
null
) {
Parent finalTabView =
tabView;
Platform.runLater
((
)->
{
Tab newTab =
new
Tab
(
tabType, finalTabView);
tabPane.getTabs
(
).add
(
newTab);
tabPane.getSelectionModel
(
).select
(
newTab);
}
);
}
}
);
}
}
V. Conclusion partie 2▲
À ce stade, vous devriez avoir une application qui ressemble à ceci :
V-A. Remerciements▲
Je tiens à remercier l'équipe de Developpez.com et plus particulièrement Mickael Baron pour son aide et son accompagnement et escartefigue pour sa relecture attentive.