Interattività per elementi GUI

Ultima lezione, la numero 8, del corso introduttivo SDN (Sun Developer Network) sulla costruzione di applicazioni GUI con JavaFX.

Per gestire l'interazione con l'utente, JavaFX usa un meccanismo di gestione degli eventi, dove ogni oggetto che può essere di interfaccia ha variabili locali che mappano funzioni che hanno a che fare con eventi.

Common Profile

Lo script che scriviamo visualizzerà un bottone a due stati (Play-Pause) che potrà essere mosso per la scena.

Quattro immagini per un bottone sulla scena

Scarichiamo dal sito sun le quattro immagini che ci serviranno per visualizzare il nostro bottone. Il play normale e premuto, lo stop normale e premuto, e mettiamole nel folder del nostro progetto.

Quindi iniziamo a scrivere il nostro script fx. Importiamo le classi per il nostro stage e la scena, poi consideriamo che avremo un gruppo come contenuto della scena, e infine le classi per l'immagine e la sua vista.
Carichiamo le nostre quattro immagini in altrettante costanti, e assegnamo una di queste alla variabile image.
Creiamo una vista per un immagine, che sarà legata alla variabile immagine appena definita. Il trucco, evidentemente, sarà quello di sfruttare il binding tra queste due variabili per gestire il cambiamento di stato del bottone.

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.Group;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

def playNormal = Image { url: "{__DIR__}play_onMouseExited.png" };
def playPressed = Image { url: "{__DIR__}play_onMousePressed.png" };
def stopNormal = Image { url: "{__DIR__}stop_onMouseExited.png" };
def stopPressed = Image { url: "{__DIR__}stop_onMousePressed.png" };

var image = playNormal;
var button = ImageView {image: bind image}

Stage {
title: "Play Button"
scene: Scene {
width: 300
height: 240
content: Group {
content: button
}
}
}

Gestione degli eventi

Vogliamo gestire tre eventi del mouse: la sua pressione, il suo rilascio e il dragging.

Definiamo quindi, prima del nostro stage, tre variabili per tener traccia dello stato del bottone e della sua posizione, in caso lo si trascini per la scena.

var mode = true; //true for Play, false for Stop
var X: Number;
var Y: Number;

Assegnamo ora al Gruppo che é il content della scena i valori relativi alla gestione degli eventi del mouse.

Gestire la pressione del mouse

Quando il mouse viene premuto (onMousePressed) teniamo traccia delle coordinate correnti del bottone e cambiamo l'immagine visualizzata.

onMousePressed: function(event) {
X = event.sceneX - event.node.translateX;
Y = event.sceneY - event.node.translateY;
image = if (mode) { playPressed; } else { stopPressed; };
}

Gestire il rilascio del mouse

Quando rilasciamo il tasto del mouse (onMouseReleased) dobbiamo cambiare l'immagine del bottone e cambiarne lo stato:

onMouseReleased: function(event) {
image = if(mode) stopNormal else playNormal;
mode = not mode;
}

Gestire il trascinamento del mouse

In questo caso non cambiamo l'immagine del bottone, inoltre il movimento é limitato dai limiti stabiliti della scena, per cui controlliamo se l'offset risultante sia esterno alla scena (minore di zero o tale che il bottone sia anche solo parzialmente esterno all'altro lato), nel qual caso forziamo il valore al limite accetabile:

onMouseDragged: function(event) {
def offsetX = event.sceneX - X;
event.node.translateX = if(offsetX <> 300 - image.width) 300 - image.width
else event.sceneX - X;

def offsetY = event.sceneY - Y;
event.node.translateY = if(offsetY <> 240 - image.height) 240 - image.height
else event.sceneY - Y;
}

L'applicazione é così completa. Rivediamo ora il codice completo:

package uiTutotial;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.Group;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

def playNormal = Image { url: "{__DIR__}play_onMouseExited.png" };
def playPressed = Image { url: "{__DIR__}play_onMousePressed.png" };
def stopNormal = Image { url: "{__DIR__}stop_onMouseExited.png" };
def stopPressed = Image { url: "{__DIR__}stop_onMousePressed.png" };

var image = playNormal;
var button = ImageView {image: bind image}

// event handling
var mode = true; //true for Play, false for Stop
var X: Number;
var Y: Number;
// event handling

Stage {
title: "Play Button"
scene: Scene {
width: 300
height: 240
content: Group {
content: button

// event handling
onMousePressed: function(event) {
X = event.sceneX - event.node.translateX;
Y = event.sceneY - event.node.translateY;
image = if (mode) { playPressed; } else { stopPressed; };
}
onMouseReleased: function(event) {
image = if(mode) stopNormal else playNormal;
mode = not mode;
}
onMouseDragged: function(event) {
def offsetX = event.sceneX - X;
event.node.translateX = if(offsetX <> 300 - image.width) 300 - image.width
else event.sceneX - X;

def offsetY = event.sceneY - Y;
event.node.translateY = if(offsetY <> 240 - image.height) 240 - image.height
else event.sceneY - Y;
} // function
} // group
} // scene
}

Desktop Profile

Scarichiamoci dal sito Sun altre due immagini che ci torneranno utili adesso, la rappresentazione del bottone al passaggio del mouse quando in stato Play e in stato Stop, e salviamole nel folder del nostro progetto.

...
def playHover = Image { url: "{__DIR__}play_onMouseEntered.png"};
def stopHover = Image { url: "{__DIR__}stop_onMouseEntered.png"};


Tooltip

Per aggiungere un Tooltip abbiamo bisogno di far riferimento alle classi Text, Font e Color, che quindi importiamo nel nostro script, dichiariamo quindi il nostro tooltip che avrà il testo definito in funzione della modalità corrente, e la sua posizione sarà legata alla posizione corrente del nostro bottone:

import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
...
var tooltip = Text {
content: bind if (mode) "Play Button" else "Stop Button"
translateX: bind button.translateX
translateY: bind button.translateY + 80
opacity: 0.0
font: Font { size: 12, name: "Tahoma" }
fill: Color.BLACK
...
Stage {
...
scene: Scene {
...
content: Group {
content: [button, tooltip]
...
}
}
};

L'ingresso del puntatore

Vogliamo che il tooltip che abbiamo definito venga mostrato all'entrata del puntatore nell'area del bottone, quindi gestiamo l'evento onMouseEntered. Per rendere più piacevole l'effetto usiamo una Timeline (classe che importiamo) che rende visibile il tooltip gradualmente in mezzo secondo, inoltre cambiamo anche la gestione dell'evento onMouseReleased, visto che vogliamo visualizzare le nuove immagini del bottone:

import javafx.animation.Timeline;
...
def appear = Timeline {
keyFrames: [
at(0s) { tooltip.opacity => 0.0 },
at(0.5s) { tooltip.opacity => 1.0 }
]
}
...
Stage {
...
scene: Scene {
...
content: Group {
...
onMouseReleased: function(event) {
image = if(mode) stopHover else playHover;
mode = not mode;
}
onMouseEntered: function(event) {
image = if (mode) playHover else stopHover;
appear.rate = 1;
appear.play();
}
} // group
} // scene
}

L'uscita del puntatore

In pratica, quando il puntatore esce dall'area del bottone, facciamo l'opposto di quello che abbiamo fatto all'ingresso:

onMouseExited: function(event) {
image = if (mode) playNormal else stopNormal;
appear.rate = -1;
appear.play();
}

Rivediamo il codice con le ultime modifiche, e con questo chiudamo lezione e tutorial.

package uiTutorial;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.Group;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

import javafx.scene.paint.Color; // tooltip
import javafx.scene.text.Font; // tooltip
import javafx.scene.text.Text; // tooltip
import javafx.animation.Timeline; // tooltip

def playNormal = Image { url: "{__DIR__}play_onMouseExited.png" };
def playPressed = Image { url: "{__DIR__}play_onMousePressed.png" };
def stopNormal = Image { url: "{__DIR__}stop_onMouseExited.png" };
def stopPressed = Image { url: "{__DIR__}stop_onMousePressed.png" };
def playHover = Image { url: "{__DIR__}play_onMouseEntered.png"};
def stopHover = Image { url: "{__DIR__}stop_onMouseEntered.png"};

var image = playNormal;
var button = ImageView {image: bind image}

// event handling
var mode = true; //true for Play, false for Stop
var X: Number;
var Y: Number;
// event handling

var tooltip = Text {
content: bind if (mode) "Play Button" else "Stop Button"
translateX: bind button.translateX
translateY: bind button.translateY + 80
opacity: 0.0
font: Font { size: 12, name: "Tahoma" }
fill: Color.BLACK
};

def appear = Timeline {
keyFrames: [
at(0s) { tooltip.opacity => 0.0 },
at(0.5s) { tooltip.opacity => 1.0 }
]
}

Stage {
title: "Play Button"
scene: Scene {
width: 300
height: 240
content: Group {
content: [button, tooltip]

// event handling
onMousePressed: function(event) {
X = event.sceneX - event.node.translateX;
Y = event.sceneY - event.node.translateY;
image = if (mode) { playPressed; } else { stopPressed; };
}
onMouseReleased: function(event) {
image = if(mode) stopHover else playHover;
mode = not mode;
}
onMouseDragged: function(event) {
def offsetX = event.sceneX - X;
event.node.translateX = if(offsetX <> 300 - image.width) 300 - image.width
else event.sceneX - X;

def offsetY = event.sceneY - Y;
event.node.translateY = if(offsetY <> 240 - image.height) 240 - image.height
else event.sceneY - Y;
}
onMouseEntered: function(event) {
image = if (mode) playHover else stopHover;
appear.rate = 1;
appear.play();
}
onMouseExited: function(event) {
image = if (mode) playNormal else stopNormal;
appear.rate = -1;
appear.play();
}
} // group
} // scene
}

Nessun commento:

Posta un commento