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)