Mostrando postagens com marcador avançado. Mostrar todas as postagens
Mostrando postagens com marcador avançado. Mostrar todas as postagens

28 de fev. de 2016

Criando controles customizados com JavaFX

Essa é uma tradução do artigo criado por Hendrik. Veja mais no blog GuiGarage

Por Hendrik Ebbers


Uma coisa que eu frequentemente faço com Java Swing é a customização de componentes e a criação de novos tipos de componentes. Um exemplo é o JGrid. Desde que JavaFX foi lançado, eu quis portar o JGrid. Depois de muitos experimentos e protótipos ruins, eu acho que encontrei a forma correta de fazer isso. As apresentações de Gerrit Grunwald e Jonathan Giles no JavaOne me ajudaram muito a fazer isso. As gravações dessas apresentações estão disponívels online (link e link - NOTA: links quebrados), então eu recomendo quem estiver interessado nesse tópico a investir algum tempo e assistir elas.


Primeiros passos



Como componente de interface no JavaFX é composto por um control(controle), uma skin("casca") e um behaviour(comportamento). Em um caso ideal, há também a parte do CSS.


A melhor forma de começar é criar uma classe que herda de  javafx.scene.control.Control. Essa classe é basicamente compara ao JComponent. O Control deve ter as propriedades do componente e atuar como a classe principal dele porque instâncias dessa classe serão depois criadas automaticamente no códido da aplicação e serão adicionadas para a árvore da interface com o usuário(UI):

MyCustomControl myControl = new MyCustomControl();
panel.getChildren().add(myControl);

Quando programando componentes Swing na forma correta você coloca tudo que depende da visualização ou a interação com o usuário em uma classe de UI (veja LabelUI por exemplo). JavaFX vai além e disponibiliza uma classe de skin para toda a visualização e o layout relacionado com o código e a classe behaviour para todas as interações com o usuário.



Para fazer isso em JavaFX você deve entender como as classes se relacionam. Aqui está um pequeno diagrama que mostra as relações entre essas classes:



Criando o comportamento (classe Behaviour)

Se o seu componente é somente para visualização de dados e não há interação, é baste simples criar um comportamento. Assim, você simplesmente precisa criar um classe que herde de com.sun.javafx.scene.control.behavior.BehaviorBase.

public class MyCustomControlBehavior extends BehaviorBase {
public MyCustomControlSkin(MyCustomControl control) {
super(control, new MyCustomControlBehavior(control));
    }
}

Alguns de vocês podem ficar confusos quando ver o pacote da classe BehavioyBase. No momento é uma API privada e normalmente você não deveria usar essas classes no seu código, mas os caras da Oracle sabem desse problema e irão providencias a classe BehaviorBase como uma API com o JavaFX 8. Então uma boa prática é usar essa classe privada agora e refatorar assim que o JavaFX for lançado(*).

NOTA: O javaFX 8 foi lançado já!

Criando a casca (classe Skin)

Depois que a classe de comportamento é criada, podemos dar uma olhada na skin. Sua classe de skin vai muito provavelmente herdar de com.sun.javafx.scene.control.skin.BaseSkin e criar um novo comportamento para o seu Control. O código normalmente se parece com o seguinte:

public class MyCustomControlSkin extends SkinBase{
public MyCustomControlSkin(MyCustomControl control) {
super(control, new MyCustomControlBehavior(control)); }
}

Criando o controle

A última classe que precisamos é o controle. Primeiro criamos uma classe vazia:

public class MyCustomControl extends Control {
public MyCustomControl() { }
}

Nesse ponto nós temos uma falha nas dependências das nossas classes. A skin conhece o behavior e o control. Aqui tudo parece correto, no entanto, no código da aplicação você vaim simplesmente criar um novo controle e usar como eu mostrei antes. O problema é que a classe de controle não sabe nada sobre a skin ou o behavior. Esse foi um dos maiores desafios quando eu estava aprendendo JavaFX.

Juntando as coisas


O que inicialmente parece um grande problema, é na verdade parte do poder disponibilizado pelo JavaFX. Com JavaFX é muito fácil criar diferente visualizações (skins) para os controles. Nessa parte você pode customizar a aparência dos componentes usando CSS. Como a skin é a parte principal da aparência, ela deve ser definida no CSS também. Assim, invés criar um objeto skin para o controle manualmente, você simplesmente define a classe skin que deve ser usada no seu controle. A criação e tudo mais é automaticamente feito pelas APIs do JavaFX. Para fazer isso, você deve ligar o seu controle a uma classe CSS.
Primeiramente, crie um novo arquivo no seu projeto. Eu acredito que a melhor prática é usar o mesmo pacote que o controle está e não um arquivo CSS criado sob src/main/resource:


Dentro do seu CSS você deve especificar um novo seletor para o seu componente e adicionar a skin como uma propriedade. Isso deveria parecer como o seguinte exemplo:

.custom-control {
-fx-skin: "com.guigarage.customcontrol.MyCustomControlSkin";
}

Assim que você criar o CSS, você tem que definir o mesmo no seu controle. Portanto, você deve configurar o caminho para o arquivo css e o seletor dos seus componentes:

public class MyCustomControl extends Control {
public MyCustomControl() {
getStyleClass().add("custom-control");
}
protected String getUserAgentStylesheet() {
return MyCustomControl.class.getResource("customcontrol.css").toExternalForm();
}
}

Depois que todas essas coisas foram feitas corretamente, JavaFX vai criar uma instância de skin para o seu controle. Você não precisa se preocupar com instanciação ou o mecanismo de depdendëncias. Nesse ponto eu gostaria de agradecer Jonathan Giles, que dedicou um tempo para codificar a integração do css para o gridfx  comigo e explicou todo o mecanismo e benefícios.

Acesso à Skin e Behavior

Normalmente não há a necessidade de acessar a Skin e o Behavior através do controle, mas se você precisar disso, você pode acessá-los da seguinte forma:


Como controler.getSkin() rece um javafx.scene.control.Skin e não a classe SkinBase você deve fazer um cast se precisar do behaviour:


((SkinBase)getSkin()).getBehavior();

Uma solução para os que odeiam CSS

Para alguns de vocës esse mecanimos parece um pouco exagerado. Talvez vocë simplesmente precise de um controle na sua aplicação e você não planeja usar uma skin com CSS e fazer tudo isso. Para esse caso de uso, há um ótimo workaround na API do JavaFX. Você pode ignorar toda a parte do CSS e configurar a classe skin no seu controle usando código:

public class MyCustomControl extends Control {
public MyCustomControl() {
setSkinClassName(MyCustomControlSkin.class.getName());
}
}


O benefício dessa forma de trabalho é que refatorar pacotes e nome de classes não vai quebrar o seu código e você não precisa de um arquivo CSS extra. Por outro lado, há uma grande desvantagem. Você não poderia usar skins definidas por CSS em qualquer extensão do seu controle. Acho que toda API pública, como o GridFX, deveria usar a forma com CSS. Em alguns casos de uso internos, a forma hardcoded(essa que falamos agora) é muito mais rápida.

Conclusão

Agora nós criamos um controle, uma skin e um behavior que está funcionando bem e pode ser adicionado na árvore  UI da sua aplicação. Mas, como acontece com o Swing, simplesmente herdar de JComponent não levará você a ver nada na tela. Então o próximo passo é adicionar estilo e organizar o layout do seu componente. Irei falar disso no meu próximo artigo.
Se você quiser dar uma olhada em algum componente existente veja  jgridfx ou JFXtras. No jgridfx os seguintes arquivos batem com esse artigo:

  • com.guigarage.fx.grid.GridView (control)
  • com.guigarage.fx.grid.skin.GridViewSkin (skin)
  • com.guigarage.fx.grid.behavior.GridViewBehavior (behavior)
  • /src/main/resources/com/guigarage/fx/grid/gridview.css (css)





28 de ago. de 2014

Threads e JavaFX

Uma das coisas mais comuns que fazemos em nossa aplicação é executar algo "pesado". Por exemplo, usualmente lemos arquivos gigantes, acessamos páginas com muita informação, aguardamos a resposta do servidor, etc. Imagine que você queria fazer isso em paralelo na sua aplicação JavaFX, mas se deparada com erros. Como devemos proceder? Nesse artigo vamos falar como executar ações em paralelo na nossa aplicação sem causar transtorno aos nossos usuários.

Um caso de uso

Imagine que temos uma aplicação que mostra o código de uma página HTML dentro de um campo de texto. Para pegar o código de uma página, temos que abrir uma conexão com uma URL, ler o InputStream e em seguida atualizar o campo de texto com o resultado. Vamos as nossas soluções.

Caso 1: Abrir URL ao clique do botão

Nesse primeiro exemplo, iremos abrir a URL quando o usuário clicar no botão, veja o código:

EventHandler cenario1 = e -> {
 try {
  txtResultado.setText(carregaPagina(txtUrl.getText()));
 } catch (Exception e1) {
  e1.printStackTrace();
 }
};


Bem, esse código funciona, mas como ele trava a execução, a aplicação fica travada quando isso acontece:



Na imagem não fica nítido, mas a aplicação fica com o botão como se estivesse apertado. Como resolver isso?

Caso 2: Abrir URL em uma thread separada

Essa solução é a mais óbvia para quem já tem um pouco de intimidade com Java. O que podemos fazer é simplesmente abrir a URL em uma thread separada e pronto! A operação de abrir a URL seria em paralelo e atualização do campo de texto só feita quando terminassemos de ler a URL, veja o código:

EventHandler cenario2 = e -> {
 Thread t = new Thread(() -> {
  try {
   txtResultado.setText(carregaPagina(txtUrl.getText()));
  } catch (Exception e1) {
   e1.printStackTrace();
  }
 });
 t.start();
};

Legal, mas ao clicar algumas vezes no botão teremos o seguinte erro:

java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-3

E agora?

Threads e JavaFX

O ponto é que como JavaFX é quem coordena a modificação da parte visual da aplicação, ele não deixa que coisas feitas em outra thread tente atuar diretamente na view, assim ele tem controle da atualização da tela. Claro que os desenvolvedores da API já sabiam que fazer coisas em paralelo eram normais, assim eles arrumaram uma forma de ajudar você a criar aplicações que tenham execução de tarefas pesadas e não travar a thread do JavaFX!

Platform.runLater

Com esse método estático da classe Platform, podemos infomar ao JavaFX uma thread que será executada sob o controle dele, assim não teríamos o erro que tivemos no caso 2. Veja o nosso caso do clique:

Massa, agora não temos erro! No entanto, notem que essa thread que criamos está sob o controle do JavaFX, mas mesmo assim ela trava o botão... Como resolver isso de vez?

Task
As tasks são a solução! Com elas podemos executar algo em paralelo e pegar o resultado depois para sim atualizar a nossa aplicação. O funcionamento é simples, vamos focar em três principais métodos:


  • call: É onde fazemos nossa tarefa pesada. Esse médoto o JavaFX não está cuidando, logo não devemos alterar nada da view aqui;
  • succeded: Quando há o sucesso na execução do método call, esse método é chamado e dele podemos pegar o resultado do call usando o método getValue;
  • failed: Esse método já é chamado quando temos uma exceção na execução do método call. Nele também podemos pegar a exceção lançada com o método getExeception.
Pronto! Isso é o suficiente para começar a usar a Task, mas notem que um ponto importante é que a Task contém um tipo genérico, dessa forma temos segurança nos tipos dos dados e evitamos espalhar "casting" pelo código. Enfim, vamos ao nosso exemplo do botão com uma Task do tipo String:

EventHandler cenario4 = e -> {
 carregando.setVisible(true);
 Task tarefaCargaPg = new Task() {

  @Override
  protected String call() throws Exception {
   return carregaPagina(txtUrl.getText());
  }

  @Override
  protected void succeeded() {
   String codigo = getValue();
   carregando.setVisible(false);
   txtResultado.setText(codigo);
  }
 };
 Thread t = new Thread(tarefaCargaPg);
 t.setDaemon(true);
 t.start();
};

Ótimo! Veja nossa aplicação final abaixo. Para deixar tudo mais legal, colocamos um ProgressIndicator, assim quando a página está sendo carregada, a opção de carregar nova página fica também desabilitada.


Conclusão

Mostramos como fazer atividades em paralelo no JavaFX não é um bicho de 7 cabeças.
O nosso código está no github!

10 de ago. de 2014

Algumas boas práticas para criação de aplicações sérias com JavaFX

Olá Pessoal! Essa semana aconteceu o TDC 2014, edição de São Paulo. Eu tive a honra de mais uma vez palestrar e dessa vez eu coloquei um conteúdo mais avançado nos slides, que seriam boas práticas para criação de aplicações sérias JavaFX.
A apresentação em si não se limitou aos slides, pois falamos do JavaFX em geral e tivemos vários demos (como uma aplicação JavaFX que lê RFID), no entanto, nos slides abaixo vocês podem ter um gostinho sobre como foi a palestra.