Interfaces gráficas com Swing
"Não tenho medo dos computadores. Temo a falta deles."
5.1 - Interfaces gráficas em Java
Atualmente, o Java suporta, oficialmente, dois tipos de bibliotecas gráficas: AWT e Swing. A AWT foi a primeira API para interfaces gráficas a surgir no Java e foi, mais tarde, superada pelo Swing (a partir do Java 1.2), que possui diversos benefícios em relação a seu antecessor.
As bibliotecas gráficas são bastante simples no que diz respeito a conceitos necessários para usá-las. A complexidade no aprendizado de interfaces gráficas em Java reside no tamanho das bibliotecas e no enorme mundo de possibilidades; isso pode assustar, em um primeiro momento.
AWT e Swing são bibliotecas gráficas oficiais inclusas em qualquer JRE ou JDK. Além destas, existem algumas outras bibliotecas de terceiros, sendo a mais famosa, o SWT - desenvolvida pela IBM e utilizada no Eclipse e em vários outros produtos.
5.2 - Portabilidade
As APIs de interface gráfica do Java favorecem, ao máximo, o lema de portabilidade da plataforma Java. O look-and-feel do Swing é único em todas as plataformas onde roda, seja ela Windows, Linux, ou qualquer outra. Isso implica que a aplicação terá exatamente a mesma interface (cores, tamanhos etc) em qualquer sistema operacional.
Grande parte da complexidade das classes e métodos do Swing está no fato da API ter sido desenvolvida tendo em mente o máximo de portabilidade possível. Favorece-se, por exemplo, o posicionamento relativo de componentes, em detrimento do uso de posicionamento fixo, que poderia prejudicar usuários com resoluções de tela diferentes da prevista.
Com Swing, não importa qual sistema operacional, qual resolução de tela, ou qual profundidade de cores: sua aplicação se comportará da mesma forma em todos os ambientes.
5.3 - Look And Feel
Look-and-Feel (ou LaF) é o nome que se dá à "cara" da aplicação (suas cores, formatos e etc). Por padrão, o Java vem com um look-and-feel próprio, que se comporta exatamente da mesma forma em todas as plataformas suportadas.
Mas, às vezes, esse não é o resultado desejado. Quando rodamos nossa aplicação no Windows, por exemplo, é bastante gritante a diferença em relação ao visual das aplicações nativas. Por isso é possível alterar qual o look-and-feel a ser usado em nossa aplicação.
Além do padrão do Java, o JRE 5 da Sun ainda traz LaF nativos para Windows e Mac OS, além do Motif e GTK. E, fora esses, você ainda pode baixar diversos LaF na Internet ou até desenvolver o seu próprio.
Veja esses screenshots da documentação do Swing mostrando a mesma aplicação rodando com 4 LaF diferentes:




5.4 - Componentes
O Swing traz muitos componentes para usarmos: botões, entradas de texto, tabelas, janelas, abas, scroll, árvores de arquivos e muitos outros. Durante o treinamento, veremos alguns componentes que nos ajudarão a fazer as telas do Argentum.
A biblioteca do Swing está no pacote javax.swing
(inteira, exceto a parte de
acessibilidade, que está em javax.accessibility
).
5.5 - Começando com Swing - Mensagens
A classe mais simples do Swing é a JOptionPane
que mostra janelinhas de mensagens,
confirmação e erros, entre outras.
Podemos mostrar uma mensagem para o usuário com a seguinte linha:
JOptionPane.showMessageDialog(null, "Minha mensagem!");
A classe JFileChooser
é a responsável por mostrar uma janela de escolha de
arquivos. É possível indicar o diretório inicial, os tipos de arquivos a serem
mostrados, selecionar um ou vários e muitas outras opções.
Para mostrar a mensagem:
JFileChooser fileChooser = new JFileChooser(); fileChooser.showOpenDialog(null);
O argumento do showOpenDialog
indica qual o componente pai da janela de mensagem
(pensando em algum frame aberto, por exemplo, que não é nosso caso). Esse método
retorna um int
indicando se o usuário escolheu um arquivo ou cancelou. Se ele
tiver escolhido um, podemos obter o File
com getSelectedFile
:
JFileChooser fileChooser = new JFileChooser(); int retorno = fileChooser.showOpenDialog(null); if (retorno == JFileChooser.APPROVE_OPTION) { File file = fileChooser.getSelectedFile(); // faz alguma coisa com arquivo } else { // dialogo cancelado }
5.6 - Exercícios: Escolhendo o XML com JFileChooser
-
Vamos escrever um programa simples que permita ao usuário escolher qual XML ele quer abrir e exibe uma mensagem com o preço do primeiro negócio.
Usaremos um
JFileChooser
e umJOptionPane
para mostrar o resultado.Crie a classe
EscolhedorDeXML
no pacotebr.com.caelum.argentum.ui
. Escreva o métodoescolhe
:1 public void escolhe() { 2 try { 3 JFileChooser chooser = new JFileChooser(); 4 int retorno = chooser.showOpenDialog(null); 5 6 if (retorno == JFileChooser.APPROVE_OPTION) { 7 FileReader reader = new FileReader(chooser.getSelectedFile()); 8 List<Negocio> negocios = new LeitorXML().carrega(reader); 9 10 Negocio primeiroNegocio = negocios.get(0); 11 String mensagem = "Primeiro negócio: " + primeiroNegocio.getPreco(); 12 JOptionPane.showMessageDialog(null, mensagem); 13 } 14 } catch (FileNotFoundException e) { 15 e.printStackTrace(); 16 } 17 }
Adicione o método
main
nessa mesma classe dando new e chamando o método que acabamos de criar:public static void main(String[] args) { new EscolhedorDeXML().escolhe(); }
Rode a classe e escolha o arquivo negocios.xml (ele está na pasta
caelum/16
no seuDesktop
). -
Por padrão, o
JFileChooser
abre na pasta do usuário. Para abrir inicialmente em outra pasta, podemos usar o argumento no construtor. Para abrir na pasta do curso nas máquinas da Caelum, por exemplo, adicionaríamos o parâmetro:public void escolhe() { try { JFileChooser chooser = new JFileChooser("/caelum/cursos/16"); int retorno = chooser.showOpenDialog(null); //...
-
(opcional) Do jeito que fizemos, o usuário pode selecionar qualquer arquivo e não apenas os XMLs. Podemos filtrar por extensão de arquivo adicionando a seguinte linha antes da chamada ao
showOpenDialog
:chooser.setFileFilter(new FileNameExtensionFilter("Apenas XML", "xml"));
5.7 - Componentes: JFrame, JPanel e JButton
Os componentes mais comuns já estão frequentemente prontos e presentes na API do Swing.
Contudo, para montar as telas que são específicas do seu projeto, será necessário compor
alguns componentes mais básicos, como JFrames
, JPanels
, JButtons
, etc.
A tela que vamos para o Argentum terá vários componentes. Para começarmos, faremos dois botões importantes: um dispara o carregamento do XML e outro permite ao usuário sair da aplicação.
Criar um componente do Swing é bastante simples! Por exemplo, para criar um botão:
JButton botao = new JButton("Carregar XML");
Contudo, esse botão apenas não faz nossa tela ainda. É preciso adicionar o botão para sair da aplicação. O problema é que, para exibirmos vários componentes organizadamente, é preciso usar um painel para agrupar esses componentes: o JPanel
:
JButton botaoCarregar = new JButton("Carregar XML"); JButton botaoSair = new JButton("Sair"); JPanel painel = new JPanel(); painel.add(botaoCarregar); painel.add(botaoSair);
Repare que a ordem em que os componentes são adicionados importa! Repare também que não definimos posicionamento algum para os nossos componentes: estamos usando o comportamento padrão dos painéis - falaremos mais sobre os layout managers logo mais.
Por fim, temos apenas objetos na memória. Ainda é preciso mostrar esses objetos na
tela do usuário. O componente responsável por isso é o JFrame
, a moldura da
janela aberta no sistema operacional:
JFrame janela = new JFrame("Argentum"); janela.add(panel); janela.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); janela.pack(); janela.setVisible(true);
O método pack()
de JFrame
, chamado acima, serve para organizar os componentes do
frame para que eles ocupem o menor espaço possível. E o setVisible
recebe um
boolean
indicando se queremos que a janela esteja visível ou não.
Adicionamos também um comando que indica ao nosso frame que a aplicação deve ser terminada quando o usuário fechar a janela (caso contrário a aplicação continuaria rodando).
5.8 - O design pattern Composite: Component e Container
Toda a API do Swing é feita usando os mais variados design patterns, procurando deixar sua arquitetura bastante flexível, extensível e modularizada.
Uma prática comum em tutoriais e explicações sobre Swing é estender os componentes para simplificar o trabalho com a API. O problema dessa abordagem é que as classes Component e Container têm um ciclo de vida complicado e é fácil cometer um engano ao reescrever seus métodos.
Além disso, com uma observação mais orientada a objetos do Swing fica claro que, assim como já vimos em cursos anteriores, a API foi construida para promover o uso de composição em vez de herança.
Effective Java
Item 16: Favoreça composição à herança
Um dos patterns base usados pelo Swing é o Composite aplicado aos componentes e containers. A idéia é que todo componente é também um container de outros componentes. E, dessa forma, vamos compondo uma hierarquia de componentes uns dentro dos outros.
No nosso exemplo anterior, o JPanel é um componente adicionado ao JFrame. Mas o JPanel também é um container onde adicionamos outros componentes, os JButtons. E, poderíamos ir mais além, mostrando como JButtons podem ser containers de outros componentes como por exemplo algum ícone usado na exibição.
5.9 - Tratando eventos
Até agora os botões que fizemos não têm efeito algum, já que não estamos tratando os eventos que são disparados no componente. E quando falamos de botões, em geral, estamos interessados em saber quando ele foi disparado (clicado). No linguajar do Swing, queremos saber quando uma ação (action) aconteceu com o botão.
Mas como chamar um método quando o botão for clicado? Como saber esse momento?
O Swing, como a maioria dos toolkits gráficos, nos traz o conceito de Listeners (ouvintes), que são interfaces que implementamos para sobrescrever métodos que serão disparados pelas ações sobre um botão, por exemplo. Os eventos do usuário são capturados pelo Swing que chama o método que implementamos.
No nosso caso, para fazer um método disparar ao clique do botão, usamos a interface
ActionListener
. Essa interface nos dá um método actionPerformed
:
public void actionPerformed(ActionEvent e) { // ... tratamento do evento aqui... }
Agora, precisamos indicar que essa ação (esse ActionListener
) deve ser disparada
quando o botão for clicado. Fazemos isso através do método addActionListener
,
chamado no botão. Ele recebe como argumento um objeto que implementa ActionListener
:
ActionListener nossoListener = ????; botao.addActionListener(nossoListener);
A dúvida que fica é: onde implemento meu ActionListener
? A forma mais simples
e direta seria criar uma nova classe normal que implemente a interface ActionListener
e tenha o método actionPerformed
. Depois basta dar new
nessa classe e
passar para o botão.
Essa forma funciona consideravelmente bem para botões que têm o mesmo comportamento em diversas telas do sistema, mas torna-se um problema para aqueles botões que devem chamar métodos diferentes de acordo com o contexto da página onde estão inseridos.
Pense, por exemplo em um botão de Ok: os métodos que precisam ser chamados quando um botão com esse texto é clicado podem ser os mais variados! Nesses casos, que são a maioria, o mais comum é encontrar a implementação dos ActionListeners
através de
classes internas e anônimas.
5.10 - Classes internas e anônimas
Em uma tela grande frequentemente temos muitos componentes que disparam eventos e muitos eventos diferentes. Temos que ter um objeto de listener para cada evento e temos que ter classes diferentes para cada tipo de evento disparado.
Só que é muito comum que nossos listeners sejam bem curtos, em geral chamando algum método da lógica de negócios ou atualizando algum componente. E, nesses casos, seria um grande problema de manutenção criarmos uma classe top level para cada evento.
Voltando ao exemplo do botão Ok, seria necessário criar uma classe para cada um deles e, aí, como diferenciar os nomes desses botões: nomes de classes diferentes? Pacotes diferentes? Ambas essas soluções causam problemas de manutenibilidade.
O mais comum para tais casos é criarmos classes internas que manipula os componentes que desejamos tratar junto à classe principal.
Classes internas são classes declaradas dentro de outras classes:
public class Externa { public class Interna { } }
Uma classe interna tem nome Externa.Interna
pois faz parte do objeto da classe
externa. A vantagem é não precisar de um arquivo separado e que classes internas
podem acessar tudo que a externa possui (métodos, atributos etc). É possível
até encapsular essa classe interna marcando-a como private
. Dessa forma, apenas
a externa pode enxergar.
Fora isso, são classes normais, que podem implementar interfaces, ter métodos, ser instanciadas etc.
Uma forma mais específica de classe interna é a chamada classe anônima que é
muito vista em códigos com Swing. De forma simples, cria-se uma classe sem mesmo declarar
seu nome em momento algum, isto é, o código
public class SairListener implements ActionListener{
não existirá em lugar algum.
Em um primeiro momento, a sintaxe das classes anônimas pode assustar - tente não criar preconceitos. Vamos usar uma classe anônima para tratar o evento de clique no botão que desliga a aplicação:
JButton botaoSair = new JButton("Sair"); ActionListener sairListener = new ActionListener() { public void actionPerformed(ActionEvent e) { System.exit(0); } }; botaoSair.addActionListener(sairListener);
Repare que precisamos de um objeto do tipo ActionListener
para passar para nosso
botão. Normalmente criaríamos uma nova classe para isso e daríamos new
nela. Usando
classes anônimas damos new e implementamos a classe ao mesmo tempo, usando a sintaxe
que vimos acima.
E podemos simplificar mais ainda sem a variável local sairListener
:
JButton botaoSair = new JButton("Sair"); botaoSair.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.exit(0); } });
Cuidado para não entender errado. Embora a sintaxe diga new ActionListener()
sabemos
que é impossível criar um objeto a partir de uma interface Java. O que essa sintaxe
indica (e as chaves logo após o parêntesis indicam isso) é que estamos dando new
em uma nova classe que implementa a interface ActionListener
e possui o método
actionPerformed
cuja implementação chama o System.exit(0)
.
Como criamos uma classe no momento que precisamos, é comum nos perguntarmos qual é o
nome dessa classe criada. A resposta para essa pergunta é exatamente a esperada: não
sabemos! Ninguém definiu o nome para essa implementação particular de um
ActionListener
, então, por não ter nome definido, classes como essas são chamadas de
classes anônimas.
5.11 - Exercícios: nossa primeira tela
-
Vamos começar nossa interface gráfica pela classe chamada
ArgentumUI
que estará no pacotebr.com.caelum.argentum.ui
terá um métodomontaTela
que desenha a tela em Swing e um métodomain
que apenas dispara a montagem da tela:1 public class ArgentumUI { 2 3 public static void main(String[] args) { 4 new ArgentumUI().montaTela(); 5 } 6 7 private void montaTela() { 8 // TODO Auto-generated method stub 9 10 } 11 }
-
É boa prática quando usamos Swing quebrar a declaração dos componentes em pequenos métodos que fazem tarefas simples. Vamos usar os componentes e conceitos vistos até aqui para criar nossa tela organizadamente.
Para começar, vá ao método
montaTela()
e preencha-o com as seguintes chamadas aos métodos que formarão a tela doArgentum
.public void montaTela() { preparaJanela(); preparaPainelPrincipal(); preparaBotaoCarregar(); preparaBotaoSair(); mostraJanela(); }
Volte, de baixo para cima, e, com ajuda do ctrl + 1, faça com que o Eclipse crie os esqueletos dos métodos para nós.
-
Preencha o método
preparaJanela
com a criação do JFrame e, como essa é a principal janela da aplicação, configure-a para terminar o programa Java quando a fecharem. Aproveite para preencher também o métodomostraJanela
, organizando os componentes (pack
), configurando o tamanho (setSize
) e mostrando-a (setVisible
).Como é necessário utilizar o JFrame no método
mostraJanela
, será preciso que ajanela
seja um atributo em vez de uma variável local. Corrija os erros de compilação que o Eclipse vai acusar usando o ctrl + 1 Create field janela.Ao fim, seu código deve estar parecido com o seguinte:
public class ArgentumUI { private JFrame janela; // main e montaTela private void preparaJanela() { janela = new JFrame("Argentum"); janela.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } // outros metodos prepara... private void mostraJanela() { janela.pack(); janela.setSize(540, 540); janela.setVisible(true); } }
-
Agora, só falta preencher os métodos que preparam os componentes da tela: o
painelPrincipal
, que conterá os botões, e cada um dos botões com seusActionListeners
. Lembre-se, também, que em cada um desses métodos será necessário darnew
no componente e adicioná-lo ao seu componente pai.Note que, como será necessário adicionar os botões ao
painelPrincipal
, este também deve ser criado como um atributo. Novamente, use o ctrl + 1 Create field painelPrincipal.Comece preenchendo o método
preparaPainelPrincipal
:private void preparaPainelPrincipal() { painelPrincipal = new JPanel(); janela.add(painelPrincipal); }
-
O código dos métodos que criam os botões, seus
ActionListeners
e adicionam aopainelPrincipal
. Ao fim, eles devem estar assim:private void preparaBotaoCarregar() { JButton botaoCarregar = new JButton("Carregar XML"); botaoCarregar.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { new EscolhedorDeXML().escolhe(); } }); painelPrincipal.add(botaoCarregar); } private void preparaBotaoSair() { JButton botaoSair = new JButton("Sair"); botaoSair.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.exit(0); } }); painelPrincipal.add(botaoSair); }
-
O resultado final tem bastante código, mas temos tudo bem separado em pequenos métodos. Rode a classe acima e você deve ter o seguinte resultado:
Teste os botões e o disparo dos eventos.
-
(opcional) Diversos usuários preferem acessar a aplicação somente através de teclado. Para esses, é essencial que configuremos o atalho que chamará cada botão. Esse caracter que, somado ao alt, equivale a um clique do mouse é chamado de mnemonic do botão.
Explore os objetos do tipo
JButton
para que alt + s saia da aplicação e alt + c abra o escolhedor de XML. -
(opcional) Procure sobre a forma de trocar a aparência da sua aplicação em Swing. A classe responsável por isso é a
UIManager
. Procure sobre como mudar o Look and Feel do Argentum.Aproveite para conhecer um projeto opensource chamado NapkinLaF, que provê uma aparência bem diferente ao seu projeto!
Layout managers e separação
No próximo capítulo, veremos porque os componentes ficaram dispostos dessa maneira e como mudar isso. Veremos também como organizar melhor esse nosso código.
5.12 - JTable
Para completar a primeira fase da tela, ainda queremos mostrar os resultados das negociações em uma tabela de 3 colunas: preço, quantidade e data do negócio.
Para mostrarmos tabelas em Swing, usamos o JTable que é um dos componentes mais
complexos de todo o Java. Tanto que tem um pacote javax.swing.table
só para ele.
Um JTable
é extremamente flexível. Ele serve para exibição e edição de dados,
pode ter outros componentes dentro, reorganizar as colunas, fazer drag and drop,
ordenação, entre outros.
Basicamente, o que precisamos é o nosso JTable
, que representa a tabela, e um
TableModel
, que representa os dados que queremos exibir na tabela. Podemos
ainda definir muitas outras configurações, como o comportamento das colunas com TableColumn
.
Começamos criando o JTable
:
JTable table = new JTable(); // por padrão, vem sem bordas, então colocamos: table.setBorder(new LineBorder(Color.black)); table.setGridColor(Color.black); table.setShowGrid(true);
Um JTable
não tem comportamento de rolagem para tabelas muito grandes. Mas podemos
adicionar esse comportamento compondo com um JScrollPane
:
JScrollPane scroll = new JScrollPane(); scroll.getViewport().setBorder(null); scroll.getViewport().add(table); scroll.setSize(450, 450);
Ou seja, adicionamos a tabela ao scrollpane. Depois esse painel com barra de rolagem
será adicionado ao painelPrincipal
.
5.13 - Implementando um TableModel
Um table model é responsavel por devolver para a tabela os dados necessarios para
exibicao. Há implementaçoes com Vectors ou Object[][]
. Ou o que é mais comum,
podemos criar nosso próprio estendendo da classe AbstractTableModel
.
Essa classe tem três métodos abstratos que somos obrigados a implementar:
-
getColumnCount
- devolve a quantidade de colunas -
getRowCount
- devolve a quantidade de linhas -
getValueAt(row, column)
- dada uma linha e uma coluna devolve o valor correspondente.
Podemos, então, criar uma NegociosTableModel
simples que responde essas 3 perguntas
baseadas em uma List<Negocio>
que lemos do XML:
1 public class NegociosTableModel extends AbstractTableModel { 2 3 private final List<Negocio> negocios; 4 5 public NegociosTableModel(List<Negocio> negocios) { 6 this.negocios = negocios; 7 } 8 9 @Override 10 public int getColumnCount() { 11 return 3; 12 } 13 14 @Override 15 public int getRowCount() { 16 return negocios.size(); 17 } 18 19 @Override 20 public Object getValueAt(int rowIndex, int columnIndex) { 21 Negocio n = negocios.get(rowIndex); 22 23 switch (columnIndex) { 24 case 0: 25 return n.getPreco(); 26 case 1: 27 return n.getQuantidade(); 28 case 2: 29 return n.getData(); 30 } 31 return null; 32 }
Só isso já seria suficiente, mas queremos fazer o botão funcionar: ao clicar em carregar
XML, precisamos pegar os dados do arquivo e popular a tabela, ou melhor, popular o
TableModel
responsável por representar os dados da tabela.
Primeiro, vamos fazer o método escolhe
da classe EscolhedorDeXML
devolver a
List<Negocio>
de que precisamos (em vez de manipulá-la para mostrar apenas o preço
do primeiro negócio).
public class EscolhedorDeXML { public List<Negocio> escolhe() { try { JFileChooser chooser = new JFileChooser("/caelum/cursos/16"); chooser.setFileFilter(new FileNameExtensionFilter("Apenas XML", "xml")); int retorno = chooser.showOpenDialog(null); if (retorno == JFileChooser.APPROVE_OPTION) { FileReader reader = new FileReader(chooser.getSelectedFile()); return new LeitorXML().carrega(reader); } } catch (FileNotFoundException e) { e.printStackTrace(); } return Collections.emptyList(); } }
Agora, basta criar um objeto do NegociosTableModel
na classe anônima que trata o evento:
botaoCarregar.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { List<Negocio> negocios = new EscolheXML().escolher(); NegociosTableModel ntm = new NegociosTableModel(negocios); table.setModel(ntm); } });
final de classe anônimas
Apenas um detalhe: para que nossa classe anônima acesse variáveis locais do método
que a cria, essas variáveis precisam ser final
. Portanto, vamos precisar
marcar a variavel table
como final
para que o codigo acima funcione.
5.14 - Exercícios: Tabela
-
Antes de colocarmos a tabela, vamos alterar a classe
EscolhedorDeXML
para devolver a lista de negócios em vez de mostrar o pop-up de mensagem. Altere o métodoescolhe
para devolver uma lista de negócios. Note que tanto a linha 9 quanto a 14 são alterações!1 public List<Negocio> escolhe() { 2 try { 3 JFileChooser chooser = new JFileChooser("/caelum/cursos/16"); 4 int retorno = chooser.showOpenDialog(null); 5 6 if (retorno == JFileChooser.APPROVE_OPTION) { 7 FileReader reader = 8 new FileReader(chooser.getSelectedFile()); 9 return new LeitorXML().carrega(reader); 10 } 11 } catch (FileNotFoundException e) { 12 e.printStackTrace(); 13 } 14 return Collections.emptyList(); 15 }
Collections.emptyList()
A classe
Collections
tem ótimos métodos para trabalharmos com diversas coleções. No exemplo acima, usamos oemptyList()
para devolver uma lista vazia genérica - no caso, achamos essa uma opção melhor do que devolvernull
. -
Na classe
ArgentumUI
, localize o métodomontaTela()
(use o ctrl + O para isso) e adicione a chamada a esse método dentro do métodomontaTela
logo acima das chamadas aos botões:private void montaTela() { preparaJanela(); preparaPainelPrincipal(); preparaTabela(); // linha adicionada! preparaBotaoCarregar(); preparaBotaoSair(); mostraJanela(); }
-
Essa nova linha não compila porque o método ainda não existe. Use o
ctrl + 1
para que o Eclipse crie o método para você. Então, preencha o método com a criação da tabela e o painel com barra de rolagem.Atente para o detalhe de que a tabela é um atributo (field) e não uma variável local (local variable): uma das opções que o
ctrl + 1
oferece é para transformar essa variável em atributo.1 private void preparaTabela() { 2 tabela = new JTable(); 3 4 JScrollPane scroll = new JScrollPane(); 5 scroll.getViewport().add(tabela); 6 7 painelPrincipal.add(scroll); 8 }
-
Crie a classe
NegociosTableModel
que é umAbstractTableModel
e sobrescreva os métodos obrigatórios. Para isso, façactrl + N
para criar a classe, indique o pacotebr.com.caelum.argentum.ui
e indique aAbstractTableModel
como superclasse. Altere a implementação dos métodos para lidar com o tipoNegocio
, adicione o atributo que falta e gere o construtor de acordo:1 public class NegociosTableModel extends AbstractTableModel { 2 3 private final List<Negocio> lista; 4 5 // ctrl + 3 constructor 6 7 @Override 8 public int getRowCount() { 9 return lista.size(); 10 } 11 12 @Override 13 public int getColumnCount() { 14 return 3; 15 } 16 17 @Override 18 public Object getValueAt(int linha, int coluna) { 19 Negocio negocio = lista.get(linha); 20 switch(coluna) { 21 case 0: 22 return negocio.getPreco(); 23 case 1: 24 return negocio.getQuantidade(); 25 case 2: 26 return negocio.getData(); 27 } 28 return ""; 29 } 30 }
-
Agora, precisamos alterar a classe anônima que trata o evento do botão de carregar XML para pegar a lista de negócios e passá-la para um
NegociosTableModel
, que será passado para a tabela:JButton botaoCarregar = new JButton("Carregar XML"); botaoCarregar.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { List<Negocio> lista = new EscolhedorDeXML().escolhe(); NegociosTableModel ntm = new NegociosTableModel(lista); tabela.setModel(ntm); } });
-
Se você rodar a aplicação agora, verá que a data ainda não está devidamente formatada! Modifique o método
getValueAt
para devolver a data formatada utilizando oSimpleDateFormat
:@Override public Object getValueAt(int linha, int coluna) { Negocio negocio = lista.get(linha); switch(coluna) { case 0: return negocio.getPreco(); case 1: return negocio.getQuantidade(); case 2: SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); return sdf.format(negocio.getData().getTime()); } return ""; }
-
Rode a aplicação e veja o resultado:
5.15 - Exercícios opcionais: melhorando a apresentação
-
(Opcional) Há alguns detalhes que podemos ainda melhorar. Por exemplo, podemos mudar os nomes das colunas para algo que faça mais sentido. Vá à classe
NegociosTableModel
e sobrescreva o métodogetColumnName
:1 @Override 2 public String getColumnName(int column) { 3 switch (column) { 4 case 0: 5 return "Preço"; 6 case 1: 7 return "Quantidade"; 8 case 2: 9 return "Data"; 10 } 11 return ""; 12 }
Rode a aplicacao e veja o resultado:
-
(opcional) Vamos adicionar um título à nossa janela, usando um
JLabel
. Na classeArgentumUI
, crie o componente como indicado abaixo e não esqueça de adicionar a chamada desse método aomontaTela
:private void preparaTitulo() { JLabel titulo = new JLabel("Lista de Negócios", SwingConstants.CENTER); titulo.setFont(new Font("Verdana", Font.BOLD, 25)); painelPrincipal.add(titulo); }
-
(opcional) Façamos com que a tabela formate também os valores monetários. Para isso, altere novamente o método
getValueAt
na classeNegociosTableModel
:@Override public Object getValueAt(int linha, int coluna) { Negocio negocio = lista.get(linha); switch(coluna) { case 0: Locale brasil = new Locale("pt", "BR"); NumberFormat formatadorMoeda = NumberFormat.getCurrencyInstance(brasil); return formatadorMoeda.format(negocio.getPreco()); case 1: return negocio.getQuantidade(); case 2: SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy"); return sdf.format(negocio.getData().getTime()); } return ""; }
Rode novamente a aplicação e veja o resultado.
Locale
Se não passarmos o Locale
para o getCurrencyInstance
, ele vai usar o padrão
da máquina virtual, o que pode ser bastante desejável se nossa aplicação for
internacionalizada.
Contudo, o modo mais comum de forçar um Locale
é alterá-lo em todo o sistema
de uma vez só, usando seu método estático setLocale
logo no início da
aplicação. Você pode mudar isso colocando no método main
da classe
ArgentumUI
:
public static void main(String[] args) { Locale.setDefault(new Locale("pt", "BR")); new ArgentumUI().montaTela(); }
5.16 - Para saber mais
- Consultar o javadoc do Swing pode não ser muito simples. Por isso, a Sun disponibiliza um ótimo tutorial online sobre Swing, que hoje está hospedado no site da Oracle: http://docs.oracle.com/javase/tutorial/uiswing/
- Aplicações grandes com Swing podem ganhar uma complexidade enorme e ficar difíceis de
manter. Alguns projetos tentam minimizar esses problemas; há, por exemplo, o famoso
projeto Thinlet, onde um XML substitui classes como a nossa
ArgentumUI
. - Existem diversos frameworks, como o JGoodies, que auxiliam na criação de telas através de APIs teoricamente mais simples que o Swing. Não são editores, mas sim bibliotecas mais amigáveis. Apesar da existência de tais bibliotecas, é importante conhecer as principais classes e interfaces e seu funcionamento no Swing.