Drag and drop

Netbeans fornisce una serie di applicazioni di esempio per JavaFX. La prima che vediamo é sull'uso del drag and drop.

Delle due versioni presentate, analizziamo la seconda, basata sullo script Main2.fx che richiama la classe DragBehavior definita nell'omonimo file.

E' stato fatto qualche cambiamento al codice, per renderlo più adeguato al gusto di chi scrive, ma sostanzialmente ci si può ritrovare anche guardando il codice originale.

Si tratta di visualizzare una palla, che potrà essere trascinata su tutta la scena, ma senza che esca dai limiti, nemmeno parzialmente.

Cominciamo a definire lo stage e la scena della nostra applicazione in Main.fx, che alla fine risulterà molto simile a Main2.fx:

package draganddrop;

import javafx.scene.Scene;
import javafx.stage.Stage;

def scene : Scene = Scene {
width: 240, height: 320
}

Stage {
title: "Drag And Drop"
scene: scene
}

Coloriamo il fondale della scena con una sfumatura che va dal bianco sporco al marrone.

import javafx.scene.paint.Color;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
...
def scene : Scene = Scene {
...
fill: LinearGradient {
proportional: true, startX: 0, startY: 0, endX: 0, endY: 1

stops: [
Stop { offset: 0.0, color: Color.WHITESMOKE },
Stop { offset: 1.0, color: Color.BROWN },
]
}
}

Creiamo un immagine e mettiamola nella scena. l'immagine é fornita con l'esempio, noi ci aggiungiamo una ombreggiatura per darle un effetto tridimensionale. Nota che nell'esempio di Netbeans si usa l'altro .png disponibile, che ha un effetto incluso nell'immagine stessa, che però la rende di gestione più complessa per il problema del movimento sui bordi. L'uso di ball2.png permette di risovere la questione in modo decisamente più elegante.

import javafx.scene.effect.InnerShadow;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
...
def ball = ImageView {
image: Image { url: "{__DIR__}images/ball2.png" }
effect: InnerShadow{offsetX: 8, offsetY: 8, color: Color.PINK}
};

def scene : Scene = Scene {
...
content: ball
...
}

Quando ridimensionsioniamo la finestra della applicazione vogliamo evitare che la palla resti fuori dalla nostra visuale. Dunque creiamo due variabili collegate alle dimensioni della scena al fine di associare un trigger che, in caso di restringimento, riporti la palla al bordo estremo.

var width : Number = bind scene.width on replace oldValue {
if(width < oldValue) {
ball.translateX = 0;
}
};

var height : Number = bind scene.height on replace oldValue {
if(height < oldValue) {
ball.translateY = 0;
}
};

La nostra scena é a posto, resta ora da definire la modalità di funzionamento del drag and drop sulla palla, che deleghiamo a una apposita classe, DragDrop - basata sulla DragBehavior fornita da Netbeans, che definiamo nel suo specifico file DragDrop.fx:

package draganddrop;

import javafx.scene.Node;

public class DragDrop {
public-init var target : Node;
public-init var targetWidth : Number;
public-init var targetHeight : Number;

public var maxX;
public var maxY;
}

Dichiariamo tre variabili che definiscono il bersaglio del drag and drop:
  • target, rappresenta l'oggetto come viene gestito all'interno della scena
  • targetWidth e targetHeight, che rappresentano le dimensioni del bersaglio
é stata data loro una visibilità public-init, dato che una volta inizializzate non le si vuole più cambiare.
Poi abbiamo altre due variabili, maxX e maxY, che rappresentano la dimensione della scena, e sono quindi il limite estremo che i nostro oggetto può raggiungere nella finestra verso destra e verso il basso. Dato che é possibile ridimensionare la finestra, il loro valore può cambiare nel corso dell'esecuzione del programma, e quindi abbiamo dato loro visibilità public.

Data questa definizione della classe DragDrop, possiamo completare il nostro script aggiungendo la sua instaziazione, che renderà disponibile all'applicazione le funzionalità di drag and drop.

DragDrop {
target: ball
targetWidth: ball.image.width
targetHeight: ball.image.height
maxX: bind scene.width
maxY: bind scene.height
};

Il obiettivo del drag and drop é la palla che abbiamo messo nella scena. La dimensione dell'obiettivo viene ricavata dall'ampiezza e altezza dell'immagine associata alla palla.
Leghiamo poi maxX e maxY all'ampiezza e altezza della scena, in modo che l'aggiornamento delle dimensioni della scena si rifletta automaticamente sulle variabili membro della classe DragDrop.

E quindi questo é il codice completo per main.fx:

package draganddrop;

import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.stage.Stage;

import javafx.scene.effect.InnerShadow;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

var width : Number = bind scene.width on replace oldValue {
if(width < oldValue) {
ball.translateX = 0;
}
};

var height : Number = bind scene.height on replace oldValue {
if(height < oldValue) {
ball.translateY = 0;
}
};

def ball = ImageView {
image: Image { url: "{__DIR__}images/ball2.png" }
effect: InnerShadow{offsetX: 8, offsetY: 8, color: Color.PINK}
};

def scene : Scene = Scene {
width: 240
height: 320
content: ball

fill: LinearGradient {
proportional: true, startX: 0, startY: 0, endX: 0, endY: 1

stops: [
Stop { offset: 0.0, color: Color.WHITESMOKE },
Stop { offset: 1.0, color: Color.BROWN },
]
}
}

DragDrop {
target: ball
targetWidth: ball.image.width
targetHeight: ball.image.height
maxX: bind scene.width
maxY: bind scene.height
};

Stage {
title: "Drag And Drop"
scene: scene
}

Ci resta ora da implementare le funzionalità per il drag and drop. Per far questo definiamo un blocco init all'interno della DragDrop in cui dichiariamo per il nostro target il comportamento che deve tenere per due eventi del mouse, quando lo si preme - tramite la funzione onMousePressed(), e quando lo si trascina - funzione onMouseDragged():

import javafx.scene.input.MouseEvent;
...
init {
target.onMousePressed = function(e : MouseEvent) : Void {
println("click!");
}

target.onMouseDragged = function(e : MouseEvent) : Void {
println("moving");
}
}

Il nostro target (e solo lui) ora risponde ai click del mouse. Vediamo di fare in modo che risponda muovendo l'oggetto. Quando clicchiamo il mouse sull'oggetto vogliamo registrare la posizione in cui lo facciamo relativamente all'oggetto stesso. Per far questo dichiariamo due variabili startX e startY e assegnamo loro un valore nella onMousePressed() nel modo seguente:

var startX;
var startY;
...
target.onMousePressed = function(e : MouseEvent) : Void {
startX = e.sceneX - target.translateX;
startY = e.sceneY - target.translateY;
println("click [{startX}, {startY}]");
}

Definiamo startX come la ascissa del punto dove abbiamo cliccato meno l'ascissa del target (ovvero del suo punto in alto a sinistra), ottenendo in questo modo l'ascissa del click relativa all'oggetto. Lo stesso per startY rispetto alle ordinate. Dunque, se clicco in mezzo alla palla otterrò sempre un valore della coppia di 35, 35. Ovunque sia la palla nella scena.


Riscriviamo ora la onMouseDragged(), per muovere il nostro oggetto sulla scena mentre il mouse viene trascinato.

target.onMouseDragged = function(e : MouseEvent) : Void {
var tx = e.sceneX - startX;
var ty = e.sceneY - startY;

if(tx < 0) { tx = 0; }
else if(tx > maxX - targetWidth) { tx = maxX - targetWidth; }

if(ty < 0) { ty = 0; }
else if(ty > maxY - targetHeight) { ty = maxY - targetHeight; }

target.translateX = tx;
target.translateY = ty;
}

Per prima cosa ci calcoliamo le nuove coordinate dell'oggetto. La sua ascissa sarà pari all'ascissa del mouse sulla scena meno il valore dell'ascissa del mouse relativa all'oggetto. E facciamo lo stesso con l'ordinata.

Prima di spostare l'oggetto (cosa che si fa semplicemente cambiando i suoi valori translateX e translateY - ci pensa JavaFX ai dettagli) vogliamo fare in modo di restringere lo spostamento alla scena stessa.

Per cui, se l'ascissa del target che abbiamo calcolato risulta inferiore di zero, la mettiamo a zero (non é possibile andare più a sinistra del margine sinistro della finestra), se é più grande dell'ampiezza della finestra meno l'ampiezza dell'oggetto, allora le assegnamo questo valore limite (non é possibile che il nostro oggetto "trasbordi" a destra del margine della finestra). E facciamo lo stesso con le ordinate.

Il codice risultante per DragDrop.fx é questo:

package draganddrop;

import javafx.scene.Node;
import javafx.scene.input.MouseEvent;

public class DragDrop {
// the target won't change
public-init var target : Node;
public-init var targetWidth : Number;
public-init var targetHeight : Number;
// the scene size may vary
public var maxX;
public var maxY;

// for target dragging, relative cursor position in the target
var startX;
var startY;

init {
// register the mouse position within the target
target.onMousePressed = function(e : MouseEvent) : Void {
startX = e.sceneX - target.translateX;
startY = e.sceneY - target.translateY;
// println("click [{startX}, {startY}]");
}

// move the target, but not outside the scene!
target.onMouseDragged = function(e : MouseEvent) : Void {
var tx = e.sceneX - startX;
var ty = e.sceneY - startY;

if(tx < 0) { tx = 0;}
else if(tx > maxX - targetWidth) { tx = maxX - targetWidth; }

if(ty < 0) { ty = 0; }
else if(ty > maxY - targetHeight) { ty = maxY - targetHeight; }

target.translateX = tx;
target.translateY = ty;
}
}
}

Nessun commento:

Posta un commento