Java Swing

Este tutorial é bem básico, feito para quem não tem prática na criação de interfaces gráficas em java.

Certo, queremos criar uma simples interface com o usuário que seja uma janela padrão (com funções de minimizar, maximizar e fechar) contendo um caixa de texto dentro (digamos que seja um início para o nosso notepad em java: 00ps:).

Sigamos o raciocínio: primeiro fazemos a janela. Como em java trabalhamos com objetos em tudo então precisamos de um objeto que seja uma janela. Então nossa classe de janela vai herdar da classe de janela do Swing (prefira-o ao awt, na maioria dos casos):

  1. import javax.swing.JFrame;  
  2.   
  3. public class MeuNote extends JFrame {  
  4. ...  
  5. }   


Bem, jé temos uma classe que cria objetos que são janelas. Agora precisamos construir a infra-estrutura da janela, modificando-a e adicionando coisas. Isso faz parte do processo de construção da janela e nenhum lugar melhor para isso que no construtor da classe!

Antes uma pequena pausa para explicar um detalhe. As API gráficas do java provêm dois tipos de componentes visuais: componentes propriamente ditos e containers.

Um componente é uma peça (widget) que tem alguma função na interface. Um container é um componente que tem a capacidade de abrigar outros componentes (conteiners possuem um método add (), que recebe qualquer componente como parâmetro e o adiciona a sua lista de componentes " abrigados ").

Sem entrar em muitos méritos (esse é um tutorial curto), o swing tem uma característica interessante adicional: todos os componentes swing, todos mesmo são também containers (possuem um método add ())!: shock:

Retomando a nossa trilha agora: nosso próximo objetivo é abrigar uma caixa de texto na janela. Para isso, simplesmente criaríamos um objeto JTextArea (é o componente visual para grandes áreas de texto não formatado (como no notepad do windows) no swing) e o passaríamos para o método add ().

Mas há um pulo do gato aí! Até o Java 1.4, não era possível adicionar componentes gráficos diretamente em um JFrame. Isso pq ele é um container especial que só permite um número limitado de componentes (containers) abrigados nele. Em um JFrame existem alguns containers " invisíveis " sobrepostos. O container mais superficial é chamado de " contentPane " (painel de conteúdo) e é a superfície na qual os componentes devem ser dispostos.

Usar o método add () do JFrame faria com que nosso componente ficasse embaixo do contentPane , sem efeito visual. Por isso, no J2SE1.4 para trás, o compilador reclama com vc. se isso for feito.

Mas então como raios eu adiciono o componente? Simples, obtenha uma referência ao contentPane e use o método add () desse container:

  1.    import javax.swing.JFrame;  
  2.    import javax.swing.JTextArea;  
  3.      
  4.    public class MeuNote extends JFrame {  
  5.       / * Componentes devem estar no contexto da instância,  
  6.         * para que possam ser acessados em todos os métodos  
  7.         * não-estáticos da classe  
  8.         * /  
  9.        private JTextArea texto = new JTextArea ();  
  10.      
  11.        public MeuNote () {  
  12.           // Define o título da janela  
  13.           super ("Meu Notepad");  
  14.           this.montaJanela ();  
  15.        }  
  16.      
  17.        private void montaJanela () {  
  18.           this.getContentPane (). add (texto);  
  19.        }     
  20.        ...  
  21.    }           


No Java 1.5 (J2SE5.0 - Tiger), se vc. usa o método add() do JFrame (this.add(texto)), o compilador já supõe que vc. quer adicionar um componente no contentPane e gera um bytecode equivalente a this.getContentPane().add(texto). Boa notícia, não? Mas lembre-se: só funciona da versão 5.0 do Java em diante.


Nosso objetivo está quase pronto, já temos nossa janela. Agora só precisamos exibir a mesma. Uma arquitetura melhor seria criar outra classe executável para usar a nossa janela, inicializando a aplicação criando e exibindo. Mas para sermos simplistas, vamos usar essa classe mesmo, tornando-a uma classe funcional (executável).

  1.   import javax.swing.JFrame;  
  2.   import javax.swing.JTextArea;  
  3.     
  4.   public class MeuNote extends JFrame {  
  5.     / * Componentes devem estar no contexto da instância,  
  6.     para que possam ser acessados em todos os métodos  
  7.     não-estáticos da classe  
  8.     * /  
  9.     private JTextArea texto = new JTextArea ();  
  10.       
  11.     public MeuNote () {  
  12.       / / Define o título da janela  
  13.       super ("Meu Notepad");  
  14.       this.montaJanela ();  
  15.     }  
  16.       
  17.     private void montaJanela () {  
  18.       this.getContentPane (). add (texto);  
  19.     }  
  20.       
  21.     public static void main (String [] args) {  
  22.       / / Cria objeto:  
  23.         MeuNote janela = new MeuNote ();  
  24.     }  
  25.   }   



Ponha isso pra rodar e vc. vai ter... nada ! Vc. criou o objeto da janela, mas em momento algum disse para torná-lo visivel ao usuário. Acrescente então no método main () a linha:

  1. janela.setVisible (true);    


Ao rodar isso vc. obtém sua janela... Mas ops! ela fica pequenininha lá no canto esquerdo. Vc. tem que definir um tamanho para sua janela!

Acrescente a linha:

  1.   /* A medida de tamanho é em pixels por polegada, igual a da resolução da sua tela */  
  2.   janela.setSize (640480);  



Dica: Faça isso antes de exibir sua janela. Assim a JVM não precisa enviar mensagens ao Sistema Operacional para redimensionar a janela. Defina o tamanho antes (deixando que os componentes dentro da janela se organizem para o tamanho da mesma) e mostre depois!

Obs.: Ao invés do setSize(), vc. também pode utilizar o método pack() (sem parâmetros dessa vez) para que ele configure a janela com o melhor parâmetro que abrigue todos os componentes, de acordo com seus tamanhos. Em nosso exemplo não vai adiantar nada, pq não definimos um tamanho (preferencial ou específico) para o JTextArea.

Enfim, eis o código que funciona perfeitamente:
  1. import javax.swing.JFrame;  
  2. import javax.swing.JTextArea;  
  3.   
  4. public class MeuNote extends JFrame {  
  5. /* Componentes devem estar no contexto da instância, 
  6.     para que possam ser acessados em todos os métodos 
  7.     não-estáticos da classe */  
  8.     
  9.    private JTextArea texto = new JTextArea ();  
  10.   
  11.    public MeuNote () {  
  12.      //Define o título da janela  
  13.      super ("Meu Notepad");  
  14.      this.montaJanela ();  
  15.    }  
  16.   
  17.    private void montaJanela () {  
  18.      this.getContentPane (). add (texto);  
  19.    }  
  20.   
  21.    public static void main (String [] args) {  
  22.      // Cria objeto:  
  23.      MeuNote janela = new MeuNote ();  
  24.      janela.setSize (640480);  
  25.      janela.setVisible (true);  
  26.    }  
  27. }   


E é o fim de nosso pequeno tutorial. Esse é um ponto de partida para evoluir nossas habilidades com alguns temas mais avançados como:

  • 1. Como fazer a minha aplicação interagir com o usuário (tratamento de eventos)?
  • 2. Como colocar uma janela dentro da outra (MDI - Multiple Document Interface)?
  • 3. Como construir um menu e barras de ferramentas?
  • 4. Como tornar meus comandos de aplicação (novo, abrir, salvar, e outros mais complexos) portáveis para outras aplicações que eu fizer?
  • 5. A minha GUI está um pouco feia ou inadequada para usuários acostumados a um determinado Sistema Operacional... Há como mudar a aparência e/ou o comportamento dela (Look And Feel - Aparência e Comportamento)?
  • 6. Como evitar que a janela "congele" quando a aplicação executar uma ação "pesada" (execução multitarefa)?
  • 7. Como eu integro minha aplicação ao "icon tray" do Windows?


Para tentar afiar mais suas habilidades em swing e ajudar a atender à demanda do Ronaldo, neste tutorial vamos estender as capacidades do nosso MeuNote com duas novidades: a construção de comandos reutilizáveis, para botões de barra de ferramentas e menu. No próximo artigo vamos expandir nossa aplicação para usar MDI.

Como criar comandos reutilizáveis?


Uma grande evolução na forma como tratamos os comandos de usuário é encapsular os procedimentos a serem executados em objetos que possuem uma interface comum. Assim poderemos tratar todos os comandos do usuário da mesma forma. Um usuário aperta um botão de salvar e um arquivo é salvo, um usuário aperta um botão e um arquivo é aberto... A forma de acionar a ação é comum (apertar um botão), mas o procedimento executado é diferente. Um padrão de projeto que resolve este tipo de problema é o chamado padrão Command (pesquise por Command - Design Patterns - GoF). O Swing implementa esse padrão através da interface Action e da classe AbstractAction.

Para mostrar em termos simples como a coisa funciona, vamos dotar nosso notepad com uma barra de ferramentas, uma barra de menus e alguns comandos básico de novo, abrir e salvar textos. Primeiramente, vamos construir o novo esqueleto da janela:
  1. import java.wat. *;  
  2. import java.awt.event. *;  
  3. import javax.swing. *;  
  4. import javax.swing.event. *;  
  5.    
  6.   
  7. // Procure no javadoc cada umas das classes que vc. n ã o conhece neste exemplo. Assim vc. saber á em que pacotes elas est ã o e as conhecer á melhor!; -)  
  8. public class MeuNote extends JFrame {  
  9.   // Componentes  
  10.   private JToolBar toolbar = new JToolBar (" Ferramentas ");  
  11.   private JMenuBar menubar = new JMenuBar ();  
  12.   private JMenu arquivo = new JMenu (" Arquivo ");  
  13.   private JTextArea texto = new JTextArea ();  
  14.   
  15.   // A ç õ es:  
  16.   private Action novo = new NovoAction (this.texto);  
  17.   private Action salvar = new SalvarAction (this.texto);  
  18.   private Action abrir = new AbrirAction ();  
  19.   
  20.   public MeuNote () {  
  21.     super (" Meu Notepad ");  
  22.     // Desliga  automaticamente a aplica ç ã o quando o usu á rio fecha a janela.  
  23.     this.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);  
  24.     Container interno = this.getContentPane ();  
  25.     this.montaMenu ();  
  26.     this.montaToolBar ();  
  27.     this.montaGUI ();  
  28.   }  
  29.   
  30.   private void montaMenu () {  
  31.     // JMenuItem pode ser constru í do a partir de um objeto que implementa a interface Action  
  32.     JMenuItem itemNovo = new JMenuItem (this.novo);  
  33.     JMenuItem itemSalvar = new JMenuItem (this.salvar);  
  34.     JMenuItem itemAbrir = new JMenuItem (this.abrir);  
  35.     this.arquivo.add (itemNovo);  
  36.     this.arquivo.add (itemSalvar);  
  37.     this.arquivo.add (itemAbrir);  
  38.     this.menubar.add (this.arquivo);  
  39.     this.setJMenuBar (this.menubar);  
  40.   }  
  41.   
  42.   private void montaToolBar () {  
  43.     // Barras de ferramenta swing tamb é m aceitam objetos que implementam Action como par â metro de construtor  
  44.     this.toolbar.add (this.novo);  
  45.     this.toolbar.add (this.salvar);  
  46.     this.toolbar.add (this.abrir);  
  47.   }  
  48.   
  49.   private void montaGUI (Container interno) {  
  50.     interno.setLayout (new BorderLayout ());  
  51.     interno.add (this.toolbar, BorderLayout.NORTH);  
  52.     interno.add (new JScrollPane (this.texto));  
  53.   }  
  54.   
  55.   public static void main (String args []) {  
  56.     // Vc. sabe o que fazer...  
  57.   }  
  58. }  


A principal coisa a notarmos nesse código é referente aos menus e a barra de ferramentas. Passamos a eles objetos das classes NovoAction, SalvarAction e AbrirAction. Notamos também que essas classes possuem a interface Action (javax.swing.Action) em comum. Que tipo de objeto é esse?

Em swing, um objeto Action encapsula todo o necessário para gerar um botão ou item de menu. O objeto sabe informar (a quem perguntar) o nome da ação que executa, a imagem de seu botão, a descrição curta ou extensa sobre o trabalho que realiza e, por fim, ele sabe realizar a tarefa para o qual foi desenhado. Tudo isso em um só objeto (ou seja, ele é mais do que um simples ouvinte de evento)!

Como criar este tipo de objeto? Vou mostrar aqui como escrever as classes NovoAction e SalvarAction, deixando a última, AbrirAction, como exercício pra você, ok? Bem, uma forma bem mais fácil de escrever uma action é criando uma subclasse de java.swing.AbstractAction:

  1. public class NovoAction extends AbstractAction {  
  2.   private JTextArea texto;  
  3.   public NovoAction (JTextArea texto) {  
  4.     // Define o nome da a ç ã o  
  5.     super (" Novo ");  
  6.     
  7.     // Define mais algumas caracter í sticas  
  8.     this.putValue (Action.SMALL_ICON, new ImageIcon (" new.gif "));  
  9.     this.putValue (Action.SHORT_DESCRIPTION, " Limpa a á rea de texto ");  
  10.     // Consultem a documenta ç ã o de javax.swing.Action para outras propriedades  
  11.   }  
  12.   
  13.   // Definimos aqui o procedimento que ser á executado quando NovoAction for acionado  
  14.   public void actionPerformed (ActionEvent ev) {  
  15.     this.texto.setText (" ");  
  16.   }  
  17. }   


  1. public class SalvarAction extends AbstractAction {  
  2.   private JTextArea texto;  
  3.   public SalvarAction (JTextArea texto) {  
  4.     // Define o nome da a ç ã o  
  5.     super (" Salvar ");  
  6.     // Define mais algumas caracter í sticas  
  7.     this.putValue (Action.SMALL_ICON, new ImageIcon (" save.gif "));  
  8.     this.putValue (Action.SHORT_DESCRIPTION, " Salva arquivo texto ");  
  9.     // Consultem a documenta ç ã o de javax.swing.Action para outras propriedades  
  10.   }  
  11.   
  12.  // Definimos aqui o procedimento que ser á executado quando NovoAction for acionado  
  13.   public void actionPerformed (ActionEvent ev) {  
  14.     JFileChooser jfc = new JFileChooser ();  
  15.     int resp = jfc.showSaveDialog (this.texto);  
  16.     if (resp! = JFileChooser.APPROVE_OPTION) return;  
  17.   
  18.     File arquivo = jfc.getSelectedFile ();  
  19.     this.saveFile (arquivo);  
  20.   }  
  21.   
  22.  // Aqui trabalhamos com classes do java.io  
  23.   private void saveFile (File f) {  
  24.     try {  
  25.       FileWriter out = new FileWriter (f);  
  26.       out.write (this.texto.getText ());  
  27.       out.close ();  
  28.     } catch (IOException e) {  
  29.       JOptionPane.showMessageDialog (null, e.getMessage ());  
  30.     }  
  31.   }  
  32. }   


A parte interessante disso tudo é que, se tivermos outro projeto em que seja preciso apagar, salvar ou abrir arquivos de texto em uma JTextArea, poderemos reaproveitar as classes de ação facilmente, pois elas já são razoavelmente auto-suficientes e são pouco dependentes do MeuNote em si (um desafio para você: como aumentar ainda mais o potencial de reutilização dessas actions, de forma que elas não dependam mais da referência a uma JTextArea, mas possam apagar, salvar e abrir arquivos em qualquer lugar?).

Quando construir seu projeto, verá que as ações se tornam menus e botões " automagicamente ". Quando um menu é clicado, ou um botão da barra de ferramentas é criado, o método actionPerformed da ação correspondente á invocado.

Por hora terminamos esse tutorial. Por questão de brevidade, não respondi todas as duvidas possíveis, embora saiba que muitas serão levantadas. Fico no aguardo de vocês para fazerem as perguntas.

Esse tutorial vai facilitar o entendimento do próximo artigo, onde vou mostrar a construção de interfaces MDI. Até lá!

Leia também:
Criando uma Janela Swing 
Mergulhando no SwingX 
NetBeans 6.5, jtree - treeModel e defaultMutableTreeNode