10.3 Controles de la interfaz de usuario
1. Label
La clase Label que reside en el paquete javafx.scene.control de la API de JavaFX se puede usar para mostrar un elemento de texto.
La etiqueta de la izquierda es un elemento de texto de color azul, que se auto-ajusta si cambiamos el tamaño de la ventana, la etiqueta del centro representa texto girado y la etiqueta de la derecha representa un texto con imagen, que además aumenta su tamaño cuando pasamos por encima.
La API de JavaFX proporciona tres constructores de la clase Label para crear etiquetas en su aplicación.
| //Creamos la etiqueta vacia
Label label1 = new Label();
//Creamos la etiqueta con texto
Label label2 = new Label("Etiqueta2");
//Creamos la etiqueta con imágen
Image image = new Image("UD09/label.png");
Label label3 = new Label("Search", new ImageView(image));
|
Una vez que haya creado una etiqueta, puede cambiar sus propiedades con los métodos;
setText(String text) especifica el texto para la etiqueta.
setGraphic(Node graphic): especifica el icono gráfico,
setTextFill especifica el color para pintar el elemento de texto de la etiqueta.
setGraphicTextGap para establecer el espacio entre ellos.
setWrapText para indicar si debe autoajustarse (true) o no (false)
setTextAlignment puede variar la posición del contenido de la etiqueta dentro de su área de diseño
setContentDisplay puede definir la posición del gráfico en relación con el texto aplicando el método y especificando una de las siguientes constantes ContentDisplay: LEFT, RIGHT, CENTER, TOP, BOTTOM .
Cuando se activa el evento MOUSE_ENTERED en la etiqueta, se establece el factor de escala de 1,5 para los métodos setScaleX y setScaleY. Cuando un usuario mueve el cursor del ratón fuera de la etiqueta y ocurre el evento MOUSE_EXITED, el factor de escala se establece en 1.0 y la etiqueta se representa en su tamaño original.
| label3.setOnMouseEntered((MouseEvent event) -> {
label3.setScaleX(1.5);
label3.setScaleY(1.5);
});
label3.setOnMouseExited((MouseEvent event) -> {
label3.setScaleX(1);
label3.setScaleY(1);
});
|
E05_Label.java
| package UD09;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class E05_Label extends Application {
private Parent createContent() {
GridPane grid = new GridPane();
grid.setAlignment(Pos.CENTER);
grid.setHgap(10);
grid.setVgap(10);
grid.setPadding(new Insets(25, 25, 25, 25));
//Creamos la etiqueta vacia
Label label1 = new Label();
//añadimos texto una vez creada
label1.setText("Texto añadido después de la creación, autoajustable");
//cambiamos la fuente y tamaño
label1.setFont(new Font("Ubuntu", 12));
//establecemos su color
label1.setTextFill(Color.web("#0076a3"));
//activamos la propiedad de autoajustable a true
label1.setWrapText(true);
//añadimos la etiqueta a la columna 0 fila 0
grid.add(label1, 0, 0);
//Creamos la etiqueta con texto
Label label2 = new Label("Etiqueta2");
grid.add(label2, 1, 0);
label2.setFont(Font.font("FreeMono", 32));
label2.setRotate(270);
//Creamos la etiqueta con imágen
Image image = new Image("UD09/label.png");
Label label3 = new Label("Search", new ImageView(image));
label3.setGraphicTextGap(20);
grid.add(label3, 2, 0);
label3.setOnMouseEntered((MouseEvent event) -> {
label3.setScaleX(1.5);
label3.setScaleY(1.5);
});
label3.setOnMouseExited((MouseEvent event) -> {
label3.setScaleX(1);
label3.setScaleY(1);
});
return grid;
}
@Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent(), 500, 200));
stage.setTitle("Ejemplo con Label");
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
|
La clase Button disponible a través de la API de JavaFX permite a los desarrolladores procesar una acción cuando un usuario hace clic en un botón. La clase Button es una extensión de la clase Labeled. Puede mostrar texto, una imagen o ambos.
Puede crear un control Button en una aplicación JavaFX usando tres constructores de la clase Button como se muestra a continuación
| //Creamos el botón vacio
Button button1 = new Button();
//Creamos el botón con texto
Button button2 = new Button("Sí");
//Creamos el botón con texto e imágen
Image image = new Image("UD09/ok.png");
Button button4 = new Button("Aceptar", new ImageView(image));
|
La clase Button puede usar los siguientes métodos:
setText(String text): especifica el título de texto para el botón
setGraphic(Node graphic): especifica el icono gráfico
setOnAction: La función principal de cada botón es producir una acción cuando se hace clic en él. En nuestro ejemplo cambiar el texto de un label.
| button2.setOnAction((ActionEvent e) -> {
label.setText("Aceptado");
});
|
Vemos cómo procesar un ActionEvent, de modo que cuando un usuario presiona button2 el título de texto de la etiqueta label se establece en "Aceptado".
Puede usar la clase Button para establecer tantos métodos de manejo de eventos como necesite para causar el comportamiento específico o aplicar efectos visuales.
Debido a que la clase Button amplía la clase Node, puede aplicar cualquiera de los efectos del paquete javafx.scene.effect para mejorar la apariencia visual del botón. En este caso añadimos una sobra al botón con imagen cuando pasamos el ratón sobre el.
| //Creamos el estilo de sombra
DropShadow shadow = new DropShadow();
//Añadimos la sombra cuando pasamos sobre el botón
button4.addEventHandler(MouseEvent.MOUSE_ENTERED, (MouseEvent e) -> {
button4.setEffect(shadow);
});
//Eliminamos la sombra al salir del botón
button4.addEventHandler(MouseEvent.MOUSE_EXITED, (MouseEvent e) -> {
button4.setEffect(null);
});
|
El siguiente paso para mejorar la apariencia visual de un botón es aplicar estilos CSS definidos por la clase Skin. Usar CSS en aplicaciones JavaFX 2 es similar a usar CSS en HTML, porque cada caso se basa en la misma especificación de CSS.
Puede definir estilos en un archivo CSS separado y habilitarlos en la aplicación usando el método getStyleClass.
El fichero style.css:
| .button1{
-fx-font: 22 ubuntu;
-fx-base: #b6e7c9;
}
|
La propiedad -fx-font establece el nombre y el tamaño de la fuente para el button1. La propiedad -fx-base anula el color predeterminado aplicado al botón.
Y desde java usamos:
| //Asociamos el fichero css a nuestra escena
scene.getStylesheets().add("UD09/style.css");
[...]
//establecemos la clase correspondiente del css
button1.getStyleClass().add("button1");
|
Como resultado, el button1 es de color verde claro con un tamaño de texto mayor.
E06_Button.java
| package UD09;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.effect.DropShadow;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class E06_Button extends Application {
private Parent createContent() {
GridPane grid = new GridPane();
grid.setAlignment(Pos.CENTER_LEFT);
grid.setHgap(10);
grid.setVgap(10);
grid.setPadding(new Insets(25, 25, 25, 25));
//Creamos el botón vacio
Button button1 = new Button();
//añadimos texto una vez creado
button1.setText("Botón CSS");
//establecemos la clase correspondiente del css
button1.getStyleClass().add("button1");
//añadimos el botón a la columna 0 fila 0 con colspan 3 y rowspan 1
grid.add(button1, 0, 0, 3, 1);
//Creamos el botón con texto
Button button2 = new Button("Sí");
grid.add(button2, 0, 1);
Button button3 = new Button("No");
grid.add(button3, 1, 1);
//Añadimos el label que cambiará segun el botón presionado
Label label = new Label("Aceptado");
grid.add(label, 2, 1);
//Creamos el botón con texto e imágen
Image image = new Image("UD09/ok.png");
Button button4 = new Button("Aceptar", new ImageView(image));
grid.add(button4, 0, 2, 2, 1);
//métodos para cambiar el label segun el botón pulsado
button2.setOnAction((ActionEvent e) -> {
label.setText("Aceptado");
});
button3.setOnAction((ActionEvent e) -> {
label.setText("Denegado");
});
//Creamos el estilo de sombra
DropShadow shadow = new DropShadow();
//Añadimos la sombra cuando pasamos sobre el botón
button4.addEventHandler(MouseEvent.MOUSE_ENTERED, (MouseEvent e) -> {
button4.setEffect(shadow);
});
//Eliminamos la sombra al salir del botón
button4.addEventHandler(MouseEvent.MOUSE_EXITED, (MouseEvent e) -> {
button4.setEffect(null);
});
return grid;
}
@Override
public void start(Stage stage) throws Exception {
Scene scene = new Scene(createContent(), 300, 200);
scene.getStylesheets().add("UD09/style.css");
stage.setScene(scene);
stage.setTitle("Ejemplo con Button");
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
|
Un control RadioButton se puede seleccionar o deseleccionar. Por lo general, se combinan en un grupo donde solo se puede seleccionar un botón a la vez.
Abajo se muestran tres capturas de pantalla del ejemplo RadioButton, en las que se agregan tres botones de opción a un grupo.
| Opción 1 |
Opción 2 |
Opción 3 |
|
|
|
La clase RadioButton disponible en el paquete javafx.scene.control del SDK de JavaFX proporciona dos constructores con los que puede crear un botón de opción.
| //Creamos el botón vacio
RadioButton rButton1 = new RadioButton();
//añadimos texto una vez creado
rButton1.setText("Coche");
//Creamos los RadioButton con texto
RadioButton rButton2 = new RadioButton("Moto");
|
Puede seleccionar explícitamente un botón de radio utilizando el método setSelected y especificando su valor como true. Si necesita verificar si un usuario seleccionó un botón de radio en particular, aplique el método isSelected.
Debido a que la clase RadioButton es una extensión de la clase Labeled, puede especificar no solo una leyenda de texto, sino también una imagen. Utilice el método setGraphic para especificar una imagen.
| //Añadimos las imágenes a los Radio Button
ImageView imageCoche = new ImageView("UD09/coche.png");
rButton1.setGraphic(imageCoche);
|
Los RadioButton se utilizan normalmente en un grupo para presentar varias opciones mutuamente excluyentes. El objeto ToggleGroup proporciona referencias a todos los botones de radio asociados con él y los administra para que solo se pueda seleccionar uno de los botones de radio a la vez. A continuación se muestra como se crea un grupo de alternancia y especifica qué botón debe seleccionarse cuando se inicia la aplicación.
| //Creamos el grupo de alternancia
final ToggleGroup grupo = new ToggleGroup();
rButton1.setToggleGroup(grupo);
rButton1.setSelected(true); //si queremos que la primera opción este marcada por defecto
rButton2.setToggleGroup(grupo);
rButton3.setToggleGroup(grupo);
|
Normalmente, la aplicación realiza una acción cuando se selecciona uno de los botones de opción del grupo. En este caso cambiará la imágen que acompaña al grupo de alternancia.
| //Añadimos una imágen que cambiara al cambiar la selección
ImageView image = new ImageView();
grid.add(image, 1, 0, 1, 3);
//añadimos el listener al grupo para que capture el evento cuando se cambie la selección
grupo.selectedToggleProperty().addListener(
(ObservableValue<? extends Toggle> ov, Toggle old_toggle, Toggle new_toggle) -> {
if (grupo.getSelectedToggle() != null) {
image.setImage(new Image("UD09/" + grupo.getSelectedToggle().getUserData().toString() + ".png"));
}
});
|
Los datos de usuario se asignaron para cada botón de opción. El objeto ChangeListener<Toggle> verifica un conmutador seleccionado en el grupo. Utiliza el método getSelectedToggle para identificar qué botón de opción está actualmente seleccionado y extrae sus datos de usuario llamando al método getUserData. Luego, los datos del usuario se aplican para construir un nombre de archivo de imagen para cargar.
Por ejemplo, cuando se selecciona rButton3, el método getSelectedToggle devuelve "rButton3" y el método getUserData devuelve "coche" Por lo tanto, la imágen será "UD09/coche.png".
E07_RadioButton.java
| package UD09;
import javafx.application.Application;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.RadioButton;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class E07_RadioButton extends Application {
private Parent createContent() {
GridPane grid = new GridPane();
grid.setAlignment(Pos.CENTER_LEFT);
grid.setHgap(10);
grid.setVgap(10);
grid.setPadding(new Insets(25, 25, 25, 25));
//Creamos el botón vacio
RadioButton rButton1 = new RadioButton();
//añadimos texto una vez creado
rButton1.setText("Coche");
//añadimos el RadioButton a la columna 0 fila 0 con colspan 3 y rowspan 1
grid.add(rButton1, 0, 0);
//Creamos los RadioButton con texto
RadioButton rButton2 = new RadioButton("Moto");
grid.add(rButton2, 0, 1);
//Creamos un RadioButton con imágen
RadioButton rButton3 = new RadioButton("A pie");
grid.add(rButton3, 0, 2);
//Añadimos las imágenes a los Radio Button
//ImageView imageCoche = new ImageView("UD09/coche.png");
//rButton1.setGraphic(imageCoche);
//Creamos el grupo de alternancia
final ToggleGroup grupo = new ToggleGroup();
rButton1.setToggleGroup(grupo);
//rButton1.setSelected(true); //si queremos que la primera opción este marcada por defecto
rButton2.setToggleGroup(grupo);
rButton3.setToggleGroup(grupo);
//Añadimos un valor personalizado a cada control con el nombre de la imágen correspondiente
rButton1.setUserData("coche");
rButton2.setUserData("moto");
rButton3.setUserData("pie");
//Añadimos una imágen que cambiara al cambiar la selección
ImageView image = new ImageView();
grid.add(image, 1, 0, 1, 3);
//añadimos el listener al grupo para que capture el evento cuando se cambie la selección
grupo.selectedToggleProperty().addListener(
(ObservableValue<? extends Toggle> ov, Toggle old_toggle, Toggle new_toggle) -> {
if (grupo.getSelectedToggle() != null) {
image.setImage(new Image("UD09/" + grupo.getSelectedToggle().getUserData().toString() + ".png"));
}
});
return grid;
}
@Override
public void start(Stage stage) throws Exception {
Scene scene = new Scene(createContent(), 300, 200);
stage.setScene(scene);
stage.setTitle("Ejemplo RadioButton");
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
|
4. CheckBox
Aunque las casillas de verificación se parecen a los RadioButton, no se pueden combinar en grupos de alternancia.
Más abajo se muestra una captura de pantalla de una aplicación en la que se usan tres casillas de verificación para habilitar o desactivar iconos en la barra de herramientas de una aplicación.
A continuación se crean tres casillas de verificación simples.
| //Creamos el CheckBox vacio
CheckBox check1 = new CheckBox();
//añadimos texto una vez creado
check1.setText("Coche");
//Creamos los CheckBox con texto
CheckBox check2 = new CheckBox("Moto");
//Hademos aparezca marcado por defecto
CheckBox check3 = new CheckBox("A pie");
check3.setSelected(true);
|
Una vez que haya creado una casilla de verificación, puede modificarla utilizando los métodos disponibles a través de las API de JavaFX. Por ejemplo el método setText define el título de texto de la casilla de verificación check1. El método setSelected se establece en true para que la casilla de verificación check3 se seleccione cuando se inicie la aplicación.
La casilla de verificación puede estar definida o indefinida. Cuando está definido, puede seleccionarlo o deseleccionarlo. Sin embargo, cuando la casilla de verificación está indefinida, no se puede seleccionar ni deseleccionar. Se usa una combinación de los métodos setSelected y setIndeterminate de la clase CheckBox para especificar el estado de la casilla de verificación. En la tabla se muestran tres estados de una casilla de verificación en función de sus propiedades INDETERMINADO y SELECCIONADO.
| Valores de propiedad |
Apariencia de la casilla de verificación |
INDETERMINADO = falso
SELECCIONADO = falso |
 |
INDETERMINADO =falso
SELECCIONADO = verdadero |
 |
INDETERMINADO = verdadero
SELECCIONADO = verdadero/falso |
 |
Es posible que deba habilitar tres estados para las casillas de verificación en su aplicación cuando representan elementos de la interfaz de usuario que pueden estar en estados mixtos, por ejemplo, "Sí", "No", "No aplicable". La propiedad allowIndeterminate del objeto CheckBox determina si la casilla de verificación debe pasar por los tres estados: seleccionada, deseleccionada e indefinida. Si la variable es "verdadera", el control recorrerá los tres estados. Si es falso, el control recorrerá los estados seleccionado y deseleccionado. La aplicación descrita en la siguiente sección construye tres casillas de verificación y habilita solo dos estados para ellas.
| //Ahora crearemos los 3 checkboxes en un bucle y tendran 3 estados
final String[] nombres = new String[]{"Coche3", "Moto3", "Pie3"};
final CheckBox[] checkBox = new CheckBox[nombres.length];
for (int i = 0; i < nombres.length; i++) {
final CheckBox cb = checkBox[i] = new CheckBox(nombres[i]);
cb.setAllowIndeterminate(true);
grid.add(cb, 1, i);
}
|
En la siguiente imagen se puede observar como la columna derecha de checkbox permite los 3 estados:
E08_CheckBox.java
| package UD09;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class E08_CheckBox extends Application {
private Parent createContent() {
GridPane grid = new GridPane();
grid.setAlignment(Pos.CENTER_LEFT);
grid.setHgap(10);
grid.setVgap(10);
grid.setPadding(new Insets(25, 25, 25, 25));
//Creamos el CheckBox vacio
CheckBox check1 = new CheckBox();
//Añadimos texto una vez creado
check1.setText("Coche");
//Añadimos el CheckBox a la columna 0 fila 0
grid.add(check1, 0, 0);
//Creamos los CheckBox con texto
CheckBox check2 = new CheckBox("Moto");
grid.add(check2, 0, 1);
//Hademos aparezca marcado por defecto
CheckBox check3 = new CheckBox("A pie");
check3.setSelected(true);
grid.add(check3, 0, 2);
//Ahora crearemos los 3 checkboxes en un bucle y tendran 3 estados
final String[] nombres = new String[]{"Coche3", "Moto3", "Pie3"};
final CheckBox[] checkBox = new CheckBox[nombres.length];
for (int i = 0; i < nombres.length; i++) {
final CheckBox cb = checkBox[i] = new CheckBox(nombres[i]);
cb.setAllowIndeterminate(true);
grid.add(cb, 1, i);
}
return grid;
}
@Override
public void start(Stage stage) throws Exception {
Scene scene = new Scene(createContent(), 300, 200);
stage.setScene(scene);
stage.setTitle("Ejemplo CheckBox");
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
|
5. TextField y PasswordField
Ahora veremos los componentes para campos de texto, la clase TextField implementa un control de interfaz de usuario que acepta y muestra la entrada de texto. Junto con otro control de entrada de texto, PasswordField, esta clase amplía la clase TextInput, una superclase para todos los controles de texto disponibles a través de la API de JavaFX.
Puede aplicar el método setPrefColumnCount de la clase TextInput para establecer el tamaño del campo de texto, definido como el número máximo de caracteres que puede mostrar a la vez.
| //Creamos el TextkField vacio
TextField textField1 = new TextField();
//Establecemos el número de caracteres que mostrará por defecto
textField1.setPrefColumnCount(10);
|
Puede crear un campo de texto campo con un dato de texto particular en él. Para crear un campo de texto con el texto predefinido:
| TextField tFComentario = new TextField();
//Establecemos el contenido por defecto del campo de texto
tFComentario.setText("Comentario por defecto");
//Seria lo mismo que haber creado el TextField de esta manera:
TextField tFComentario2 = new TextField("Comentario por defecto");
|
En lugar de añadir etiquetas para acompañar a los campos de texto en este fragmento de código se han añadido los subtítulos, que notifican a los usuarios qué tipo de datos deben ingresar en los campos de texto. El método setPromptText define la cadena que aparece en el campo de texto cuando se inicia la aplicación.
| //definimos setPromptText para que indique la información que espera el campo
tFNombre.setPromptText("Nombre");
|
Puede obtener el valor de un campo de texto en cualquier momento llamando al método getText. Aquí por ejemplo hacemos que se muestre el campo oculto en un label:
| Button btnMostraTexto = new Button("Mostrar apellidos");
Label label = new Label();
btnMostraTexto.setOnAction((ActionEvent e) -> {
label.setText(tFApellidos.getText());
});
|
Revise algunos métodos útiles que puede usar con los campos de texto.
copy()– transfiere el rango actualmente seleccionado en el texto al portapapeles, dejando la selección actual.
cut()– transfiere el rango actualmente seleccionado en el texto al portapapeles, eliminando la selección actual.
selectAll() - selecciona todo el texto en la entrada de texto.
pegar()– transfiere el contenido del portapapeles a este texto, reemplazando la selección actual.
E09_TextBox.java
| package UD09;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class E09_TextBox extends Application {
private Parent createContent() {
GridPane grid = new GridPane();
grid.setAlignment(Pos.CENTER_LEFT);
grid.setHgap(10);
grid.setVgap(10);
grid.setPadding(new Insets(25, 25, 25, 25));
//Creamos el TextkField vacio
TextField tFNombre = new TextField();
//Establecemos el número de caracteres que mostrará por defecto
tFNombre.setPrefColumnCount(10);
//definimos setPromptText para que indique la información que espera el campo
tFNombre.setPromptText("Nombre");
grid.add(tFNombre, 0, 0);
//Creamos el campo PasswordField que no mostrará por pantalla la información
PasswordField tFApellidos = new PasswordField();
tFApellidos.setPrefColumnCount(10);
tFApellidos.setPromptText("Apellidos");
grid.add(tFApellidos, 0, 1);
TextField tFComentario = new TextField();
//Establecemos el contenido por defecto del campo de texto
tFComentario.setText("Comentario por defecto");
//Seria lo mismo que haber creado el TextField de esta manera:
//TextField tFComentario = new TextField("Comentario por defecto");
tFComentario.setPrefColumnCount(10);
tFComentario.setPromptText("Comentario");
grid.add(tFComentario, 0, 2);
Button btnMostraTexto = new Button("Mostrar apellidos");
Label label = new Label();
btnMostraTexto.setOnAction((ActionEvent e) -> {
label.setText(tFApellidos.getText());
});
grid.add(label, 1, 0);
grid.add(btnMostraTexto, 1, 1);
return grid;
}
@Override
public void start(Stage stage) throws Exception {
Scene scene = new Scene(createContent(), 500, 200);
stage.setScene(scene);
stage.setTitle("Ejemplo TextField y PasswordField");
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
|
6. Mucho más
https://docs.oracle.com/javase/8/javafx/user-interface-tutorial/ui_controls.htm