Arena - Guía de componentes

A continuación, explicamos los mecanismos de binding que tiene cada uno de los componentes de Arena (llamados widgets), así como también cómo lucen y para qué se suele utilizar.

Jerarquía de Widgets en Arena

La clase más abstracta de componentes en Arena es Widget. A partir de ella podemos ver la jerarquía con todos los componentes que existen.


Pueden explorar esas clases para información extra a esta página.

Window

La clase window es el contenedor principal de los controles. Se le puede configurar
  • minHeight
  • minWidth
que son el ancho y el largo mínimo que debe tener la pantalla, a menos de que se requiera más espacio (en ese caso se utilizará más ancho o alto).

ErrorsPanel

Todas las ventanas que heredan de SimpleWindow tienen un ErrorsPanel, que es básicamente un label que escucha mensajes de error que aparecen por la interacción del usuario con la aplicación. Se le puede configurar 
  • la cantidad de líneas preferida
Para eso, o bien construimos un ErrorsPanel en una ventana MainWindow o Window (que no heredan de SimpleWindow):

public class ErrorsPanelWindow extends MainWindow<Persona> {

    @Override
    public void createContents(Panel mainPanel) {
        new ErrorsPanel(mainPanel, "Estamos listos para comenzar. Estamos listos para comenzar.\n", 
             4 /* es la cantidad de líneas que queremos */);

    }

o bien tendremos que redefinir el método createErrorsPanel si heredamos de SimpleWindow... para cambiar la cantidad de líneas que queremos mostrar por defecto.

    @Override
    protected ErrorsPanel createErrorsPanel(Panel mainPanel) {
        return new ErrorsPanel(mainPanel, "Estamos listos para comenzar. Estamos listos para comenzar.\n", 
                   4 /* es la cantidad de líneas que queremos */);
    }

Si nuestra intención es simplemente mostrar los errores en una línea y nuestra ventana hereda de SimpleWindow, no es necesario hacer nada.

Control

org.uqbar.arena.widgets.Control es la subclase de Widget más importante en Arena. Es abstracta, sirve para proveer los siguientes bindings genéricos:
  • enabled: indica si está habilitado el control, se asocia a una propiedad que devuelve un boolean.
  • visible: indica si el control está visible, se asocia a una propiedad que devuelve un boolean.
  • background: el fondo del control, se asocia a una propiedad que devuelve un ViewObservable.
El binding de estas propiedades se puede hacer de dos maneras:
  1. enviando el mensaje bindXXXToProperty(String nombreDeLaPropiedadDelModelo)
    • si trabajás en Xtend tenés el operador <=> como shortcut (fijate en esta página)
  2. enviando el mensaje bindXXX(ObservableProperty)
donde XXX es el nombre de la propiedad de la vista.

Vemos un ejemplo de cada caso: 
checkResumen.bindEnabledToProperty("habilitaResumenCuenta")
checkResumen.enabled <=> "habilitaResumenCuenta" // solo en Xtend

en este caso el checkbox se habilita en base a la propiedad "habilitaResumenCuenta" definido en el modelo de la vista. Recordemos que la propiedad se implementa con un método boolean getHabilitaResumenCuenta(). 

Vemos el mismo ejemplo creando un ObservableProperty:

checkResumen.bindEnabled(new ObservableProperty(this.modelObject, "habilitaResumenCuenta"))

La ventaja de esta alternativa es que permite modificar el modelo default de la vista para un control particular (reemplazando this.modelObject por otro objeto observable).

Además se puede asignar manualmente las siguientes propiedades (no bindeables):
  • width(int)
  • height(Int)

IMPORTANTE: Todos los controles se instancian pasando como parámetro un panel que los contiene


Checkbox

org.uqbar.arena.widgets.CheckBox representa un cuadro que se asocia a un valor verdadero (seleccionado) o falso (no seleccionado)
Al extender de Control hereda las propiedades bindeables enabled, visible, background y las propiedades manuales height y width.
Un checkbox debe bindear la propiedad
  • value: el valor booleano true/false 
Ejemplo de bindings para un checkbox:

// En Java
CheckBox checkResumen = new CheckBox(form);
checkResumen.bindEnabledToProperty("habilitaResumenCuenta");
checkResumen.bindValueToProperty("recibeResumenCuenta");

// En Xtend
new CheckBox(form) => [
    enabled <=> "habilitaResumenCuenta"
    value <=> "recibeResumenCuenta"
]


Selector / Combo

org.uqbar.arena.widgets.Selector<T> es un control que muestra una lista desplegable de opciones y permite al usuario seleccionar una de ellas. Se visualiza como un ComboBox:


Un selector tiene dos propiedades que deben bindearse:

  • value: el elemento seleccionado (un T)
  • items: la lista de opciones disponibles a mostrar. Es un conjunto de elementos T, donde T debe ser observable.
Otros bindings opcionales son:
  • onSelection(Action): evento que se dispara cuando se selecciona una opción 
Al extender de Control hereda las propiedades bindeables enabled, visible, background y las propiedades manuales height y width.

Ejemplo de un selector que muestra el país de fabricación de una computadora. Cada opción muestra la descripción del país: 
// Xtend
new Selector<Computadora>(form) => [
     allowNull(false)
     value <=> "country"
     val bindingItems = items <=> new ObservableProperty(repoPaises, "countries")
     bindingItems.adapter = new PropertyAdapter(typeof(Country), "description")
]

// Java
Selector<Computer> computerSelector = new Selector<Computer>(form)
    .allowNull(false);
computerSelector.bindValueToProperty("country");
Binding<Country, Selector<Country>, ListBuilder<Country>> bindingItems = 
    computerSelector.bindItems(new ObservableProperty(repoCountries, "countries"));
bindingItems.setAdapter(new PropertyAdapter(Country.class, "description"));

List

org.uqbar.arena.widgets.List<T> extiende de Selector y tiene el mismo comportamiento pero se muestra como una lista que permite visualizar varios valores al mismo tiempo:


Ejemplo que muestra cómo llenar un List de Productos para un pedido (dentro de un panel con layout horizontal):

// En Xtend
new Panel(it) => [
      layout = new HorizontalLayout
      new List(it) => [
            items <=> "productos")
            value <=> "productoSeleccionado"
            width = 220
            height = 220
      ]
]

// En Java
List<Producto> lstProductos = new List<Producto>(mainPanel);
lstProductos.bindItemsToProperty("productos");
lstProductos.bindValueToProperty("productoSeleccionado");
lstProductos.setWidth(220);
lstProductos.setHeight(220);

Para más referencias ver el control Selector

Radio Selector

org.uqbar.arena.widgets.RadioSelector<T> extiende de Selector y tiene el mismo comportamiento pero se muestra como una lista de radio buttons (botones con selección excluyente):



Constructores: además del constructor default de todos los controles (que reciben el panel padre que hace de container) tenemos el
  • RadioSelector(Container container, String itemsProperty)
que permite establecer la propiedad de donde salen los ítems a seleccionar.

Para más referencias ver el control Selector

Ejemplo: una vista que tiene una propiedad estadosCiviles sólo lectura
def getEstadosCiviles() {
      #["Soltero", "Casado", "Viudo", "Separado", "Divorciado"]       
}
 
...
 
      new RadioSelector(mainPanel) => [
            bindItems(new ObservableProperty(this, "estadosCiviles"))
            bindValueToProperty("estadoCivil")
      ]

Skinnable Controls

Todos los controles que heredan de SkinnableControls aceptan estas propiedades configurables manualmente:
  • foreground(Color)
  • background(Color)
  • fontSize(int)
Además heredan las propiedades bindeables del control (enabled, visible, height, width)

Label

org.uqbar.arena.widgets.Label representa un texto con el formato de una etiqueta. Tiene este formato:



Puede o no tener binding, en el primer caso se trabaja con la propiedad value, en el segundo caso se setea en el inicio una constante String a partir de la propiedad text que no es observable.

Ejemplo de un label fijo:
// Xtend
new Label(editorPanel).text = "Monto"

// Java
new Label(editorPanel).setText("Monto");

muestra el valor fijo "Monto"

Ejemplo de un label que se bindea contra una propiedad (es dinámico, responde ante los cambios en el modelo):
// Xtend
new Label(mainPanel) => [
    background = Color.ORANGE
    value <=> "kilometros"
]

// Java
new Label(mainPanel) //
    .setBackground(Color.ORANGE)
    .bindValueToProperty("kilometros");

La propiedad contra la que se bindea un label suele ser de sólo lectura (aunque no es obligatorio que así lo sea).

Es obligatorio para cada label definir la propiedad text o bien la propiedad value (no tiene sentido definir ambas ya que el value estará pisando el text original).

Propiedades opcionales son:
  • las que hereda de Control: 
    • manuales: height y width. También admite mensajes para alineación: alignLeft(), alignRight(), alignCenter()
    • bindeables: enabled, visible 
  • las que hereda de SkinnableControl: fontSize, foreground, background, todas manuales
Imágenes: un label puede tener asociada una imagen (siempre que no tenga binding contra un value). Por ejemplo, podemos establecer una propiedad en el modelo contra el path donde se encuentra una imagen

@Observable
class EjemploImagen {
    
   def getPathImagen() {
       "foto.jpg"
   }

Y luego en el Label establecer el binding contra un transformer que tome el path y lo convierta a una imagen:

override createContents(Panel mainPanel) {
   new Label(mainPanel) => [
       bindImageToProperty("pathImagen", [ imagePath |
           new Image(imagePath)
       ])
   ]

Es conveniente que la imagen esté en un source folder definido en el pom:

<build>
   <resources>
      <resource>
         <directory>src/main/resources</directory>
      </resource>
   </resources>

Para más información recomendamos descargar este ejemplo.

Link

org.uqbar.arena.widgets.Link permite definir el link a una acción


Es obligatorio el binding 
  • de la propiedad caption, ya sea 
    • manual mediante la asignación de la propiedad caption
    • o bindeable a través del mensaje bindCaptionToProperty
  • de la propiedad onClick(Action). 
Ejemplo:

// Xtend
new Link(mainPanel) => [
    caption = "Buscar"
    onClick [ | modelObject.buscar ]
]

// Java
new Link(mainPanel)
    .setCaption("Buscar")
    .onClick( () -> this.modelObject.buscar() );

Propiedades opcionales son:
  • las que hereda de Control: 
    • manuales: height y width
    • bindeables: enabled, visible 
  • las que hereda de SkinnableControl: fontSize, foreground, background, todas manuales

Button

org.uqbar.arena.widgets.Button permite definir un botón para que el usuario dispare una acción.


Es obligatorio el binding de la propiedad onClick. Además es conveniente definirle
  • la propiedad caption (bindeable o como un string fijo)
  • o bien la propiedad image (bindeable)
Propiedades opcionales son:
  • las que hereda de Control: 
    • manuales: height y width
    • bindeables: enabled, visible 
  • las que hereda de SkinnableControl: fontSize, foreground, background, todas manuales
  • setAsDefault: se asocia con el <Enter> del usuario
  • disableOnError: cuando la vista recibe una excepción se deshabilita el botón hasta un nuevo evento
Ejemplos de uso: 

// Xtend
new Button(actionsPanel) => [
      caption = "Jugar"
      setAsDefault
      onClick[ | jugar  ]
      enabled <=> "puedeJugar"
      disableOnError
]

// Java
new Button(actions)
    .setCaption("Aceptar")
    .onClick(this::accept)
    .setAsDefault()
    .disableOnError();



FileSelector

org.uqbar.arena.widgets.FileSelector extiende de Button y además permite seleccionar un archivo mediante un cuadro de diálogo


Es obligatorio establecer el binding de la propiedad value, contra una propiedad del modelo:

Al apretar el botón, se abrirá un dialog para seleccionar el archivo.



Lo que va a bindear es el path del archivo seleccionado a la propiedad "archivo" de nuestro modelo.

Se pueden configurar las siguientes propiedades manuales:
  • title: el título de la ventana de diálogo al abrirse
  • caption: label del botón
  • path: el directorio a visualizar al abrir la ventana
  • extensions: las extensiones de archivo a filtrar. Esto es opcional, si no se configura, se puede seleccionar cualquier archivo. 
/ Xtend
new FileSelector(actions) => [
    caption = "Open"
    value <=> "archivo"
    extensions("*.example")
]

// Java
new FileSelector(actions)
    .setCaption("Open")
    .bindValueToProperty("archivo")
    .extensions("*.example");


TextBox

org.uqbar.arena.widgets.TextBox permite ingresar valores al usuario. Por default todos los textboxes asumen que se pueden cargar tanto números como caracteres alfabéticos y símbolos, así que el valor asociado es un String. Se visualiza como una caja de texto:

   
Es obligatorio establecer el binding de la propiedad value, contra una propiedad del modelo:

// Xtend
new TextBox(form) => [
    value <=> "nombre"
    width = 200 
]

// Java
new TextBox(form)
    .setWidth(250)
    .bindValueToProperty("nombre");

Recordemos que las opciones para bindear el value son:
  • a partir del bindValueToProperty
  • o a través del bindValue(new ObservableProperty(this, "millas"))
Propiedades que pueden setearse manualmente, además de las del control:
  • las que hereda de Control: 
    • manuales: height y width. También admite mensajes para alineación: alignLeft() y alignRight(). El mensaje alignCenter() no tiene efecto ya que SWT no lo permite.
    • bindeables: enabled, visible 
  • las que hereda de SkinnableControl: fontSize, foreground, background, todas manuales
  • withFilter(TextFilter): permite filtrar el input del control (para más información ver Filter controllers)

NumericField

org.uqbar.arena.widgets.NumericField es un TextBox adaptado (tiene un filtro que evita los caracteres alfabéticos, y solo permite ingresar dígitos numéricos, comas y puntos). A partir de la versión 3.6.3 de Arena 

  • se alinea a derecha
  • se pueden ingresar valores negativos
  • por defecto admite decimales (tomados de la configuración regional como coma o punto). Si se requiere ingresar solo valores enteros, debe construirse como new Numeric(contenedor, false) donde el segundo parámetro indica si se permite trabajar con decimales
// Xtend
new NumericField(form) => [
    value <=> "numero"
    width = 100
]

new NumericField(form, false)     // Para ingresar valores sin decimales
    .value <=> "edad"


// Java
new NumericField(form)
    .setWidth(150)
    .bindValueToProperty("numero");

PasswordField

org.uqbar.arena.widgets.PasswordField es un TextBox adaptado para datos sensibles que no muestra los caracteres ingresados en pantalla. Es ideal para contraseñas.

// Xtend
new PasswordField(mainPanel).value <=> "password"

// Java
new PasswordField(form)
    .setWidth(150)
    .bindValueToProperty("password");


Table/Grid

org.uqbar.arena.widgets.tables.Table<R> permite crear una grilla para mostrar elementos de tipo R:


Es obligatorio definir
  • la propiedad items contra una colección de elementos de tipo R
  • al menos una columna
Opcionalmente se puede definir una propiedad value para bindear el elemento seleccionado.

Propiedades que pueden setearse manualmente, además de las del control:
  • las que hereda de Control: 
    • bindeables: enabled, visible 
  • las que hereda de SkinnableControl: fontSize, foreground, background, todas manuales
  • IMPORTANTE: las propiedades height y width no tienen efecto, van algunos tips
    • ¿Cómo cambiar el alto? Definiendo la cantidad de filas a mostrar mediante la propiedad numberVisibleRows(int)
    • ¿Cómo cambiar el ancho? Definiendo un tamaño fijo para cada columna mediante la propiedad fixedSize(int), esto redibuja finalmente el ancho de la tabla.
Ejemplo: para definir una tabla que muestra los clientes de una empresa de celulares
// Xtend
val table = new Table<Celular>(mainPanel, typeof(Celular)) => [
    items <=> "resultados"
    value <=> "celularSeleccionado"
]

// Java
Table<Celular> table = new Table<Celular>(mainPanel, Celular.class);
table.bindItemsToProperty("resultados");
table.bindValueToProperty("celularSeleccionado");

Para agregar una columna que muestre el nombre de un cliente de esa empresa:

// Xtend
new Column<Celular>(table) => [
    title = "Nombre"
    fixedSize = 200
    bindContentsToProperty("nombre")
]

// Java
new Column<Celular>(table) //
    .setTitle("Nombre")
    .setFixedSize(150)
    .bindContentsToProperty("nombre");

La propiedad nombre está asociada a un celular.

Las columnas 

  • se pueden alinear mediante los mensajes alignLeft(), alignRight() y alignCenter()
  • se relacionan con una propiedad o bien podemos aplicarle un Transformer, como se explica en esta página.

Spinner

org.uqbar.arena.widgets.Spinner permite editar un valor numérico con dos botones que permiten incrementar/decrementar ese valor.

 
La propiedad value se bindea mediante el mensaje bindValueToProperty.

Opcionalmente se pueden definir:
  • minimumValue: el valor mínimo que puede tomar
  • maximumValue: el valor máximo que puede tomar
  • las que hereda de Control: 
    • manuales: height y width
    • bindeables: enabled, visible 
  • las que hereda de SkinnableControl: fontSize, foreground, background, todas manuales
Ejemplo: tenemos un pedido y queremos asignar la cantidad a comprar para un producto:
// Xtend
new Spinner(mainPanel) => [ 
    value <=> "cantidadIngresada"
    width = 200 
]

// Java
new Spinner(mainPanel)
    .setWidth(200)
    .bindValueToProperty("cantidadIngresada");

El pedido tiene una propiedad cantidadIngresada int o Integer.

Tree

org.uqbar.arena.widgets.tree.Tree<T> permite definir un control que muestre nodos en forma jerárquica

<TODO: Imagen>

<TODO: Hablar con alguien que lo desarrolló>

Panel

org.uqbar.arena.widgets.Panel sirve como contenedor de widgets o controles. 

Todo panel tiene
  • un model, que puede coincidir con el de la pantalla principal
  • un layout, que por default es vertical

Group Panel

org.uqbar.arena.widgets.GroupPanel extiende de Panel y le agrega un borde + un título (propiedad title manual).


Comments