JavaFX: botão com ícone a partir de svg e em TableView

Ter botões com ícones para chamar alguma ação em uma aplicação é uma forma muito intuitiva de planejar uma interface gráfica, pois através do ícone o usuário já tem uma noção sobre o que aquele botão irá fazer.

Foto de ícones
Foto por Harpal Singh em Unsplash

Em páginas Web inserir um ícone através do HTML é muito prático. Um exemplo disso é esse ícone para editar, fornecido pelo Font Awesome:

<i class="fas fa-pen"></i>

E toda a parte visual do ícone pode ser estilizada pelo CSS.

Porém algo tão simples assim em HTML pode acabar gerando um pouco de dor de cabeça quando desenvolvemos uma aplicação em JavaFX.

#Primeiro Caso

Uma das situações que podemos nos deparar é quando precisamos simplesmente fazer com que um botão (que não está dentro de uma linha de uma tabela) tenha um ícone dentro dele. Nesse caso o mais prático é salvar o ícone como uma imagem e então adicionar essa imagem dentro de um botão.

Para demonstrar esse caso irei baixar um ícone em png pelo site https://material.io/resources/icons/ que fornece opções de download tanto em svg quanto png, e deixar armazenado em uma das pastas do projeto. Mas poderia ser baixado por qualquer outro local sendo que o ideal é no mínimo que o ícone esteja em um png com transparência. O tamanho da imagem vai depender exclusivamente da sua necessidade.

Nesse caso, basta adicionar um Button no SceneView e em seguida adicionar dentro do mesmo um ImageView, fazendo com que na propriedade do caminho da imagem você selecione o ícone em png que foi salvo. Porém ele virá no tamanho original salvo, para resolver isso você pode ir nas configurações de Layout do ImageView e alterar o Fit Witdh e o Fit Height. Caso você queira exibir o botão só com o ícone, basta remover o texto padrão do botão. Abaixo é demonstrado isso:

Mas precisamos concordar que esse fundo cinza que existe por padrão não é nem um pouco elegante, para resolver isso pode ser criado uma classe icon-buttonno arquivo CSS que a aplicação irá usar, para deixar o fundo transparente e já de quebra pode ser adicionado uma regra para quando o cursor passar em cima do botão ele ficar com o formato da mãozinha de clique:

E então é só fazer com que o botão tenha essa classe. Para fazer com que o SceneBuilder reconheça as classes criadas dentro do arquivo CSS é só ir em : Preview -> Scene Style Sheets -> Add a Style Sheet. Demonstro isso abaixo:

#Segundo Caso

O processo se torna um pouco mais complexo quando temos a seguinte situação: imagine que temos uma tabela populada com algumas pessoas, e queremos ter uma coluna que tenha um botão para editar e excluir a pessoa daquela linha. Algo visualmente semelhante a isso:

O foco desse post é sobre como adicionar esses botões e fazer com que algo aconteça quando eles forem clicados. Caso tenha interesse em configurar e popular um TableView recomendo esse artigo:

Antes de apresentar o caminho que eu encontrei para sanar essa demanda, confira os arquivos criados para exibir essa tabela sem os botões:

Arquivo Main:

application.css:

Case2.fxml:

Case2Controller.fxml:

Antes de tudo é preciso esclarecer que quando o botão for pressionado queremos fazer algo relacionado a pessoa que está naquela determinada linha da tabela, portanto é preciso definir que a TableColumn em que irá ficar o botão tenha o tipo genérico sendo o mesmo do tipo de conteúdo que irá ter nele, ficando algo assim:

@FXML private TableColumn<Person, Person> columnEdit;

@FXML private TableColumn<Person, Person> columnDelete;

Agora, para facilitar tanto a minha vida e a sua eu criei uma classe utilitária com métodos estáticos genéricos para inserir um botão que pode ser utilizada em qualquer TableColumn independente da entidade que será populada na tabela. O código abaixo está comentado, mas abaixo deixo como você pode utilizá-lo.

Nesse caso, para utilizar esse método é preciso que você tenha o path (caminho ?) do ícone em svg. Para encontrar esse path para gerar o ícone, você pode salvar um arquivo em svg e em seguida abrir ele com o bloco de notas por exemplo

Para não colocar todo esse caminho solto no código, você pode criar uma variável final com o nome do ícone:

public final String PEN_SOLID = “M290.74 93.24l128.02 128.02–277.99 277.99–114.14 12.6C11.35 513.54–1.56 500.62.14 485.34l12.7–114.22 277.9–277.88zm207.2–19.06l-60.11–60.11c-18.75–18.75–49.16–18.75–67.91 0l-56.55 56.55 128.02 128.02 56.55–56.55c18.75–18.76 18.75–49.16 0–67.91z”;

Agora vamos para os argumentos do método para inicializar os botões:

public static <T> void initButtons(TableColumn<T, T> tableColumn, int size, String svgIcon, String colorClassName, BiConsumer<T, ActionEvent> buttonAction) { … }

TableColumn<T, T> tableColumn: a coluna que irá ter o botão

int size: o tamanho do ícone

String svgIcon: o Path do svg do ícone

String colorClassName: nome da classe já definida no arquivo CSS para determinar a cor do ícone (ver application.css acima)

BiConsumer<T, ActionEvent>: Consumer que será executado quando o botão for pressionado

Exemplo de uso:

Você pode conferir o código completo usado nesse exemplo no GitHub:

Conclusão

Trabalhar com TableView em JavaFX pode ser algo realmente trabalhoso em um primeiro momento e isso se torna ainda mais complicado quando você pretende ter uma boa interface gráfica inserindo ícones, já que é usado diversos conceitos (como Generics, Lambdas, Consumer, etc)…

A ideia desse post surgiu pela falta de conteúdo falando sobre isso em português, então espero que esse artigo possa ajudar alguém. Se isso acabou te ajudando de alguma forma deixa algum comentário aqui embaixo.

Um apaixonado por Java e UI/UX. Saiba um pouco mais aqui: https://www.felypeganzert.com/

Um apaixonado por Java e UI/UX. Saiba um pouco mais aqui: https://www.felypeganzert.com/