Capítulo 5

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.

Você não está nessa página a toa

Você chegou aqui porque a Caelum é referência nacional em cursos de Java, Ruby, Agile, Mobile, Web e .NET.
Faça curso com quem escreveu essa apostila.

Consulte as vantagens do curso Laboratório Java com Testes, XML e Design Patterns.

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:

look_and_feel1.pnglook_and_feel2.pnglook_and_feel3.pnglook_and_feel4.png

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
}

Seus livros de tecnologia parecem do século passado?

Conheça a Casa do Código, uma nova editora, com autores de destaque no mercado, foco em ebooks (PDF, epub, mobi), preços imbatíveis e assuntos atuais.
Com a curadoria da Caelum e excelentes autores, é uma abordagem diferente para livros de tecnologia no Brasil. Conheça os títulos e a nova proposta, você vai gostar.

Casa do Código, livros para o programador.

5.6 - Exercícios: Escolhendo o XML com JFileChooser

  1. 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 um JOptionPane para mostrar o resultado.

    Crie a classe EscolhedorDeXML no pacote br.com.caelum.argentum.ui. Escreva o método escolhe:

     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 seu Desktop).

    jfilechooser.pngjoptionpane.png
  2. 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);
        //...
    
  3. (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.

Agora é a melhor hora de aprender algo novo

Se você gosta de estudar essa apostila aberta da Caelum, certamente vai gostar dos novos cursos online que lançamos na plataforma Alura. Você estuda a qualquer momento com a qualidade Caelum.

Conheça a Alura.

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

  1. Vamos começar nossa interface gráfica pela classe chamada ArgentumUI que estará no pacote br.com.caelum.argentum.ui terá um método montaTela que desenha a tela em Swing e um método main 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 }
    
  2. É 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 do Argentum.

    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.

  3. 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étodo mostraJanela, 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 a janela 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);
      }
    }
    
  4. 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 seus ActionListeners. Lembre-se, também, que em cada um desses métodos será necessário dar new 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);
    }
    
  5. O código dos métodos que criam os botões, seus ActionListeners e adicionam ao painelPrincipal. 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);
    }
    
  6. 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:

    primeira-tela.png

    Teste os botões e o disparo dos eventos.

  7. (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.

  8. (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.

Você pode também fazer o curso FJ-16 dessa apostila na Caelum

Querendo aprender ainda mais sobre boas práticas de Java, testes e design patterns? Esclarecer dúvidas dos exercícios? Ouvir explicações detalhadas com um instrutor?
A Caelum oferece o curso FJ-16 presencial nas cidades de São Paulo, Rio de Janeiro e Brasília, além de turmas incompany.

Consulte as vantagens do curso Laboratório Java com Testes, XML e Design Patterns.

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:

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

  1. 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étodo escolhe 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 o emptyList() para devolver uma lista vazia genérica - no caso, achamos essa uma opção melhor do que devolver null.

  2. Na classe ArgentumUI, localize o método montaTela() (use o ctrl + O para isso) e adicione a chamada a esse método dentro do método montaTela logo acima das chamadas aos botões:

    private void montaTela() {
      preparaJanela();
      preparaPainelPrincipal();
      preparaTabela();      // linha adicionada!
      preparaBotaoCarregar();
      preparaBotaoSair();
      mostraJanela();
    }
    
  3. 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 }
    
  4. Crie a classe NegociosTableModel que é um AbstractTableModel e sobrescreva os métodos obrigatórios. Para isso, faça ctrl + N para criar a classe, indique o pacote br.com.caelum.argentum.ui e indique a AbstractTableModel como superclasse. Altere a implementação dos métodos para lidar com o tipo Negocio, 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 }
    
  5. 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);
      }
    });
    
  6. 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 o SimpleDateFormat:

    @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 "";
    }
    
  7. Rode a aplicação e veja o resultado:

    tabela-1.png

Tire suas dúvidas no novo GUJ Respostas

O GUJ é um dos principais fóruns brasileiros de computação e o maior em português sobre Java. A nova versão do GUJ é baseada em uma ferramenta de perguntas e respostas (QA) e tem uma comunidade muito forte. São mais de 150 mil usuários pra ajudar você a esclarecer suas dúvidas.

Faça sua pergunta.

5.15 - Exercícios opcionais: melhorando a apresentação

  1. (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étodo getColumnName:

     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:

    tabela-2.png
  2. (opcional) Vamos adicionar um título à nossa janela, usando um JLabel. Na classe ArgentumUI, crie o componente como indicado abaixo e não esqueça de adicionar a chamada desse método ao montaTela:

    private void preparaTitulo() {
      JLabel titulo = new JLabel("Lista de Negócios", SwingConstants.CENTER);
      titulo.setFont(new Font("Verdana", Font.BOLD, 25));
      painelPrincipal.add(titulo);  
    }
    
  3. (opcional) Façamos com que a tabela formate também os valores monetários. Para isso, altere novamente o método getValueAt na classe NegociosTableModel:

    @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

5.17 - Discussão em sala de aula: Listeners como classes top level, internas ou anônimas?

Nova editora Casa do Código com livros de uma forma diferente

Editoras tradicionais pouco ligam para ebooks e novas tecnologias. Não conhecem programação para revisar os livros tecnicamente a fundo. Não têm anos de experiência em didáticas com cursos.
Conheça a Casa do Código, uma editora diferente, com curadoria da Caelum e obsessão por livros de qualidade a preços justos.

Casa do Código, ebook com preço de ebook.