11 de ago de 2014

API do JavaFX: Properties, Listeners e Bindings

Uma das características mais interessantes do JavaFX também está na API não gráfica, onde podemos facilitar com que elementos de interface respondam a variações em classes Java, usualmente classes que representam um negócio. Hoje vamos falar das classes de Properties do JavaFX e dar pequenos exemplos para entendimento das mesmas.

Como fazer com que a mudança de dados reflita na aplicação?

Imagine que sua aplicação você tem um campo de entrada texto, um TextField, e em seguida um texto, um Label, para mostrar o que tem nesse campo, em tempo real, como você faria?
  
Bem, a forma mais usual é adicionar um key handler ao campo de texto e quando esse for acionado, configurar o texto do Label. Veja o código:

TextField txtNome = new TextField();
Label lblNome = new Label();
txtNome.setOnKeyReleased(e -> {
 lblNome.setText(txtNome.getText());
});
lblNome.setFont(Font.font("Verdana", FontWeight.BOLD, 30));


Esse cenário, no entanto, pode ser muito mais complexo e levar você a ter que espalhar listeners na aplicação para fazer com que duas propriedades reflitam uma a outra e, o pior de tudo, o código pode não ficar tão legível e de difícil manutenção.

Properties


Felizmente o JavaFX tem uma API de properties, onde foram criadas diversas classes que adicionam "superpoderes" aos tipos mais comuns do java e que podem ser extensíveis para usarmos com nosso objetos da aplicação.
O melhor ainda é que toda a API do JavaFX está preparada para isso. Todos os campos de um elemento do JavaFX, como o Node, têm um equivalente com property. Por exemplo, um campo de texto tem um textProperty para o valor de texto do mesmo.

Binding

Um super poder muito notável das properties é o binding. Realizar binding significa fazer com que uma propriedade tenha seu valor modificado em função da outra. Exemplificando, se temos o texto A e queremos que o texto B seja o mesmo que o A, mas mais ainda, que B atualize quando B atualizar, dizemos que B está amarrado à A (bound). Quando isso acontece, B não pode ser modificado diretamente ou teremos um erro. Se quiser mudar B, teremos que mudar A. Isso em código fica como mostrado a seguir (reescrevendo nosso exemplo anterior):

TextField txtNome = new TextField();
Label lblNome = new Label();
lblNome.textProperty().bind(txtNome.textProperty());  
lblNome.setFont(Font.font("Verdana", FontWeight.BOLD, 30)); 

Simples, não? Aqui pode não parecer tão intessante, mas imagine que estamos em uma aplicação que traz objetos do banco de dados e queremos refletir os dados desses objetos na view em tempo real. Não seria essa uma boa alternativa? Adicionalmente podemos realizar um binding bidirecional, onde se au altero A, B também será alterado, e se eu altero B, A também terá o conteúdo de B!

Note que por enquanto falamos somente de texto, mas isso é possível com valores boleanos, inteiros e do tipo double:

StringProperty: Para textos (textProperty no TextField ou no Label)
BooleanProperty: Valores booleanos( visibleProperty, disableProperty no Node)
DoubleProperty: Valores double (value no Slider)
IntegerProperty: Valores inteiros (int)
ObjectProperty: Objetos no geral.


Cada uma das properties acima tem um equivalente para ser instanciado caso você queria usar as properties em sua aplicação, por exemplo: SimpleStringProperty, SimpleBooleanProperty, etc..
Agora, para introduzir a API, vamos mostrar brevemente tudo isso. Futuramente voltamos a aprofundar nesse tópico.

Binding com expressões

Imagine agora que você queira não só amarrar um texto, mas amarrar um texto com outro e ainda um texto fixo no meio, como fazer? Ou, se estiver usando números, quer que um valor seja sempre o resultado da soma desses dois números. Isso é muitp fácil e é possível através das expressões das properties.
Por exemplo, para  String, temos a expressão concat,  onde passamos passar outra String literal ou outra String property e cancat irá retornar uma StringProperty que é atualizada com o resultado daquela concatenação, veja um simples exemplo:





TextField txtNome = new TextField();
TextField txtSaudacao = new TextField();
Label lblNome = new Label();
lblNome.textProperty().bind(
  txtNome.textProperty().concat(", ")
  .concat(txtSaudacao.textProperty()
));



Claro que nem sempre queremos que o resultado dessa expressão seja outro texto, logo, temos também expressões da String que retornam uma propriedade de outro tipo. Por exemplo, a expressão greaterThanOrEqualTo irá retornar uma BooleanProperty de comparação de duas StringProperty. Um outro exemplo é gerar um resultando de soma de duas DoubleProperty e uma String que contenha a representação em texto, veja:







Slider sld1 = new Slider(0, 100, 50);
Slider sld2 = new Slider(0, 100, 50);
Label lblResultado = new Label();
lblResultado.textProperty().bind(
  sld1.valueProperty()
  .add(sld2.valueProperty())
  .asString()
);



Bem, paramos por aqui, mas a javadoc é sua amiga. Leia por lá e navegue na API que em breve você está mestre nisso!

Observando mudanças


Lembra do padrão observer? Um super poder das properties é também deixar que você observe ela. Assim, quando ela muda temos um pedaço de código notificado. Esse código notificado irá receber três parâmetros: o valor observável que sofreu a mudança, o valor que era antes, o novo valor. Simples, não? Com Java 8 é ainda mais simples pois podemos usar Lambdas para definir os ChangeListeners. Veja o seguinte exemplo que observamos as mudanças de um combo box:



String valores[] = { "Garrafa", "Copo", "Monitor", "Notebook",
  "Celular" };
// na próxima falamos de FXCollections
ComboBox cmbValores = new ComboBox(
  FXCollections.observableArrayList(valores));
Label lblAntigo = new Label();
Label lblNovo = new Label();
cmbValores.valueProperty().addListener((obs, velho, novo) -> {
 lblAntigo.setText("Valor Antigo: " + velho);
 lblNovo.setText("Valor Novo: " + novo);
});


Conclusão

Ah, chegamos ao fim... Essa API é muito abrangente e divertida, por isso iremos voltar ao tópico no futuro. Veja o código da nossa aplicação de teste no github e a carinha dela final:


2 comentários:

  1. Muito bom o artigo, estou desenvolvendo um software com JavaFX e foi de grande valia este post, procurei por muito na web e até que enfim consegui uma explanação sobre o assunto.
    Parabéns!

    ResponderExcluir
  2. Parabéns pelo ótimo post! Como falou o amigo acima, o conteúdo ajuda muito no desenvolvimento de aplicações jfx.
    Valeu!

    ResponderExcluir