JavaFx es un lenguage declarativo de scripting. Esto quiere decir que con solo declarar los objetos ya es suficiente para tenerlos. por lo que puedes hacer algo tan básico com :
println("Hello World");
Sin tener que declarar una clase. Esto no quiere decir que no soporte clases.
Vamos a ver como se resuelven en JavaFX los temas básicos, declaración de variables, arrays, funciones, etc.
Como puedes ver las variables se pueden declarar con var o con def.
Puedes ver tambien que no se ha especificado que tipo de variables son. El compilador lo deduce.
Las secuencias son listas ordenadas de objetos. Debido a que son utilizadas tan a menudo JavaFX soporta secuencias como una clase de primer orden.
Para declarar una secuencia simplemente se utilizan corchetes con los elementos separados por una coma.
public def meses = ["enero","febrero", "marzo", "abril", "mayo","junio", "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre"];
Para declarar una secuencia vacía simplemente se pondrían los corchetes. He aqui dos formas de inicializar una secuencia
public var meses:String[]; public var meses:String[] = [];
Con números también puedes hacer:
var numerosPositivos = [0..10]; var numerosNegativos = [0..-10]; // Secuencia Vacía var numerosNegativos = [0..-10 step -1]; // 0,-1,-2,...-10 var numerosNegativos = [0..<-10 step -1]; // 0,-1,-2,...,-9
Puedes indicar el valor inicial, el valor final y si quieres el paso que defines entre un valor y el próximo. El paso por defecto es +1 es por eso que el segundo ejemplo da una secuencia vacía.
Por supuesto está el iterador por defecto:
for( mes in meses) {
println("{mes}");
}
Las funciones también se declaran fácil y cómodamente como has podido comprobar:
function divide() {
result = numOne / numTwo;
println("{numOne} / {numTwo} = {result}");
}
En el caso de que acepten o devuelvan valores la sintaxis es:
function add(argOne: Integer, argTwo: Integer) : Integer {
result = argOne + argTwo;
println("{argOne} + {argTwo} = {result}");
return result;
}
Y como es java permite constructores sobrecargados como puedes ver en el ejemplo completo que te dejo aqui:
/*
* Main.fx
*
* Created on 20-jul-2009, 18:22:03
*/
package calculadora;
/**
* @author juantxu
*/
def numOne = 100;
def numTwo = 2;
var result;
add();
add(22, 44);
subtract();
multiply();
divide();
function add() {
result = numOne + numTwo;
println("{numOne} + {numTwo} = {result}");
}
function add(argOne: Integer, argTwo: Integer) : Integer {
result = argOne + argTwo;
println("{argOne} + {argTwo} = {result}");
return result;
}
function subtract() {
result = numOne - numTwo;
println("{numOne} - {numTwo} = {result}");
}
function multiply() {
result = numOne * numTwo;
println("{numOne} * {numTwo} = {result}");
}
function divide() {
result = numOne / numTwo;
println("{numOne} / {numTwo} = {result}");
}
el resultado será:
init: deps-jar: compile: jar: standard-run: 100 + 2 = 102 22 + 44 = 66 100 - 2 = 98 100 * 2 = 200 100 / 2 = 50 browser-run: jws-run: midp-run: run: BUILD SUCCESSFUL (total time: 2 seconds)
Para declarar clases se hace como en java
class MiClase {// clase privada
var x:Number;
var y:Number;
}
public class MiClase {
var location: Point;
}
public class Porsche911 extends Porsche {
}
public abstract class MyAbstractClass {
}
JavaFX introduce un tipo de herencia llamada herencia mixin ( de mezcla). Para hacer esto introduce un nuevo tipo de clases que se llaman mixin clases. Este tipo de clase provee ciertas funcinalidades a las clases que las heredan. Perno no es una interfaz. Ellas pueden implementar funcionalidades e inicializar variables.
public mixin class Positioner {
protected bound function centerX(
node: Node, within: Node) : Number {
(within.layoutBounds.width -
node.layoutBounds.width)/2.0 -
node.layoutBounds.minX;
}
protected bound function centerY(node: Node,
within: Node) : Number {
(within.layoutBounds.height -
node.layoutBounds.height)/2.0 -
node.layoutBounds.minY;
}
}
Las subclases que quieran implementar su propia version de una función de una clase MIXIN tiene que utilizar la palabra clave override cuando delcare la función.
public class My Positioner extends Positioner {
public override bound function centerX(node: Node,
within: Node) : Number {
(within.boundsInParent.width -
node.boundsInParent.width )/2.0;
}
}
En el caso de que se declaren funciones sin inicializarlas en la clase Mixin deberán ser declaradas como abstractas
public abstract function bottomY(node: Node ) : Number;
Y la clase que las herede las deberán sobreescribir (override)
public class My Positioner extends Positioner {
public override function bottomY(node: Node ) : Number {
...
}
}
En JavaFX los objetos son instanciados usando sus nombres. Esta el la sintaxis declarativa en JavaFx. Para instanciar una clase simplemente se invoca por su nombre y se le proporcionan los inicializadores que se deseen.
Por ejemplo:
var title = Title {
text: "JavaFX mola"
x: 10
y: 50
onMouseClicked: function(e:MouseEvent):Void {
// haz algo
}
};
Aqui declaramos un objeto de la clase Title con el texto “JavaFX Mola”, en la posición 10,20 de la pantalla y cuando pinchemos con el ratón la función que se pasasemos sería invocada.
También puedes sobreescribir funciones abstractas:
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
var listener = ActionListener {
override function
actionPerformed(e: ActionEvent) : Void {
println("Has hecho algo!");
}
}
Las cadenas (Strings) pueden ser declaradas de forma literal por medio de comillas:
var cita = "Pepito grillo dijo:
\"Nunca digas
de ese agua no beberé\""
var quote = 'Pepito grillo dijo:
"Nunca digas
de ese agua no beberé."'
También puedes incluir expresiones dentro de las cadenas:
println ( "My name is {name}" );
El manejo de errores se realiza con el try-catch habitual de java
try {
} catch (e:SomeException) {
} finally {
}
JavaFX es una capa de presenatación. Tu puedes separar completamente la presentación ( con JavaFX) de toda la lógica de negocio ( Quizás con Java). Pero esta separació tiene un límite. En algún momento tendrás que presentar un resultado.
¿Y como asociar un resultado a una variable de JavaFX ? Eso son los bindigns. Los bindings son el procedimiento por el cual vinculamos una variable a otra. De este modo, cuando nuestra variable de negocio cambia, nuestra variable vinculada también.
var v = bind expression;
De este modo podemos enlazar una variable con otra o con el resultado de una expresión:
var x : Integer = 10;
var y = bind x;
var z = x; // z no esa lincada a x
println("x={x}, y={y}, z={z}");
x = 20;
println("x={x}, y={y}, z={z}");
obtendría un resultado como este:
init: standard-run: x=10, y=10, z=10 x=20, y=20, z=10
También se pueden vinculada variables dentro de una clase. Puedes verlo en este ejemplo:
var x : Integer = 10;
class myClass {
var y : Integer;
var z : Integer;
};
var m = myClass { y: bind x, z: x };
println("x={x}, m.y={m.y}, m.z={m.z}");
x = 20;
println("x={x}, m.y={m.y}, m.z={m.z}");
Que te dará este resultado:
init: standard-run: x=10, m.y=10, m.z=10 x=20, m.y=20, m.z=10
Inicialmente una variable sólo puede ser vinculada en el momento de su declaración. Los siguientes códigos producirán error:
var x : Integer = 10; var y : Integer; y = bind x; // <-- ERROR
Igualmente, para manipular una variable vinculada hay que manipular su variable origen. No podemos manipular la variable vinculada directamente.
var x : Integer = 10;
var y = bind x;
println("x={x}, y={y}");
y = 15; // No se puede reasingar una variable enlazada
x = 20;
println("x={x}, y={y}");
Pero vamos a verlo en un ejemplo un poco mas FX. Aqui lo que hacemos es vincular la imagen a mostrar con la variable imagenActual. De ese modo podemos modificar la imagen actual, por ejemplo en un evento y conseguir así la actualización de una forma limpia.
package javafxapplication15;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.image.*;
import javafx.scene.input.*;
var image1 = Image {
url: "{__DIR__}images/1.png"
}
var image2 = Image {
url: "{__DIR__}images/2.png"
}
var imagenActual : Image = image2;
Stage {
title: "Simple bind"
scene: Scene{
height: image1.height * 2
width: image1.width * 2
content: [
ImageView {
image: bind imagenActual
x: image1.width / 2
y: image1.height / 2
onMouseEntered: function(e : MouseEvent)
: Void {
currentImage = image1;
}
onMouseExited: function (e : MouseEvent)
: Void {
currentImage = image2;
}
}
]
}
}
Y aquí tienes el resultado de nuestro experimento:
No olvides que hemos dicho que se vincula a una expersión por lo que puedes hacer cosas como esta:
var a = 3;
var b = 4;
var c = a * b;
var d = 7;
var total = bind c + d;
println("total={total}");
d = 10;
println("total={total}");
b = 5;
println("total={total}");
o esto:
var a = 1;
var b = 2;
var max = bind if (a > b) a else b;
println("max = {max}");
a = 3;
println("max = {max}");
b = 4;
println("max = {max}");
o incluso vincularlo al resultad de una función:
function getMax(i1: Integer, i2: Integer, i3: Integer)
: Integer {
if ((i1 > i2) and (i1 > i3)) { return i1; }
else if ((i2 > i1) and (i2 > i3)) { return i2; }
else { return i3; }
}
var max2 = bind getMax(a, b, c);
o un bucle:
var val = 0;
function incrementVal() : Integer {
return val++;
}
var start = 0;
var end = 4;
var seq = bind for (x in [start..end]) incrementVal();
println(seq);
Pero ¿Que pasa si queremos cambiar el contenido de la variable que vinculamos?
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.ext.swing.SwingTextField;
var str = "Cambia me";
Stage {
title: "Binding unidireccional"
scene: Scene {
content: [
SwingTextField {
columns: 25
text: bind str
editable: true
}
]
}
}
at java.awt.EventQueue.dispatchEvent(EventQueue.java:599)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)
Exacto que peta. Los bindings por naturaleza son unidireccionales. Si cambias la varable vinculada esto se refelaja en la variable origen y entonces intenta cambiar a su vez la vinculada y… peta.
Para eso etán los vinculos bidireccionales para poder cambiar esta variable.
var a bind b with inverse
Prueba ahora el mismo código simplemente añadiendo el with inverse al vinculo.
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.ext.swing.SwingTextField;
var str = "Cambia me";
Stage {
title: "Binding unidireccional"
scene: Scene {
content: [
SwingTextField {
columns: 25
text: bind str with inverse
editable: true
}
]
}
}
JavaFX incorpora triggers que es exactamente lo mismo que los triggers de las bases de datos. Añadiendo un trigger a una variables asocias un trozo de código que se ejecutará cada vez que se actualize el valor de dicha variable.
Un trigger se asocia a una variable añadiendo la expresión on replace a su declaración.
var variable = "foo" on replace viejoValor {
println("\nALERTA! La variable ha cambiado!");
println("Valor Viejo: {viejoValor}");
println("Valor nuevo : {variable}");
};
variable = "bar";
y esto dará:
ALERTA! La variable ha cambiado! Valor Viejo: Valor nuevo: foo ALERTA! La variable ha cambiado! Valor Viejo: foo Valor nuevo: bar
Al inicializar la variable detecta el cambio, pero no hay un valor viejo por lo que la primea ejecución da un valor viejo = nulo. La segunda ejecución si que tiene valor por lo que ya nos dice que el valor viejo era foo.
Fijate que valorViejo es el valor original de la variable. Puedes llamarle como quieras. Siempre estará ahi como variable accesible.
Otro ejemplo es:
class Quijote {
public var frase : String on replace {
s = frase;
}
}
var l = Quijote { frase: "En un lugar" };
var s : String;
println("Quijote={l.frase}, s={s}");
l.frase = "de la mancha";
println("Quijote={l.frase}, s={s}");
// sería ilegal hacer esto s = bind l.frase
s = "de cuyo nombre";
println("Quijote={l.frase}, s={s}");
l.frase = "no quiero acordarme";
println("Quijote={l.frase}, s={s}");
l.frase = "vivia un hidalgo ";
println("Quijote={l.frase}, s={s}");
que nos dará el siguiente reultado:
Quijote=En un lugar, s= Quijote=de la mancha, s=de la mancha Quijote=de la mancha, s=de cuyo nombre Quijote=no quiero acordarme, s=no quiero acordarme Quijote=vivia un hidalgo , s=vivia un hidalgo