Cap 13: Interface Gráfica com Swing¶
Por que Swing, e não uma biblioteca mais nova?
Esta parte da disciplina usa a biblioteca Swing apenas como veículo para destacar conceitos de orientação a objetos já vistos — herança, interfaces, o padrão Observer — não como um curso de desenvolvimento desktop. Vale registrar, de forma honesta: hoje, para aplicações desktop novas em Java, a alternativa mais moderna é o JavaFX (separado do JDK desde o Java 11, distribuído como módulo independente), com um modelo de layout mais flexível (CSS, FXML) e melhor suporte a gráficos modernos. O Swing continua relevante por dois motivos práticos: ainda sustenta uma quantidade enorme de software corporativo legado, e — para os fins pedagógicos deste capítulo — seu modelo de eventos é simples o suficiente para ilustrar bem o padrão Observer sem esconder os conceitos atrás de muita configuração.
🖼️ AWT e Swing¶
O AWT (Abstract Window Toolkit) foi o primeiro conjunto de ferramentas gráficas do Java — com componentes de aparência dependente de plataforma, recursos limitados (dependia do suporte de cada sistema operacional) e uma história conhecida de bugs de portabilidade. O Swing construiu sobre a base do AWT, mantendo compatibilidade, mas resolvendo os dois problemas centrais: um conjunto muito mais rico de componentes, e o conceito de Look and Feel — a aparência visual passa a ser decidida pela própria biblioteca Java, não pelo sistema operacional, eliminando as inconsistências entre plataformas.
🧱 Componentes e containers¶
Uma interface gráfica é composta de componentes (os elementos visíveis — botões, campos de texto, menus) organizados dentro de containers, que também podem aninhar outros containers.
public class SwingOla {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(200, 200);
frame.setVisible(true);
}
}
Como JFrame é um container, novos componentes podem ser adicionados a ele:
JFrame frame = new JFrame();
JButton bt = new JButton("Diga Olá");
frame.add(bt);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(200, 200);
frame.setVisible(true);
Layout¶
O Java precisa de uma política para organizar os componentes adicionados a um container. O padrão é o BorderLayout, que divide a janela em cinco regiões (norte, sul, leste, oeste, centro); adicionar um componente sem especificar região o coloca no centro:
O FlowLayout posiciona componentes da esquerda para a direita, quebrando linha quando necessário — como um editor de texto organizando palavras:
JFrame frame = new JFrame();
frame.setLayout(new FlowLayout());
frame.setSize(250, 60);
frame.add(new JButton("Diga Olá"));
frame.add(new JButton("Diga Adeus"));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
O GridLayout divide o container em células invisíveis de tamanho igual:
JFrame frame = new JFrame();
frame.setLayout(new GridLayout(2, 3));
frame.setSize(250, 150);
for (int i = 1; i < 7; i++) {
frame.add(new JButton("Botão " + i));
}
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
Combinando JPanel (um container invisível) com layouts diferentes em cada painel, é possível compor layouts arbitrariamente complexos aninhando painéis.
🎯 Eventos: o padrão Observer em ação¶
O Swing organiza interação com o usuário em torno de eventos: objetos são divididos entre fontes (sources, como um JButton) e receptores (listeners), que reagem quando um evento — clique de mouse, tecla pressionada — acontece. Isso é, literalmente, o padrão de projeto Observer citado no Capítulo 9: a fonte notifica todos os seus listeners registrados quando seu estado muda (no caso, "foi clicado").
Para capturar o clique de um botão, é preciso um receptor que implemente a interface ActionListener:
class Finaliza implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
System.exit(1);
}
}
Em vez de uma classe nomeada, uma classe anônima evita o boilerplate de declarar Finaliza separadamente:
btFechar.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent ev) {
System.exit(1);
}
});
E como ActionListener tem um único método abstrato — é, na definição do Capítulo 8, uma interface funcional — desde o Java 8 é possível usar diretamente uma expressão lambda, muito mais concisa:
Organizando em uma classe própria¶
O padrão mais comum na prática é encapsular toda a montagem da interface dentro de uma subclasse de JFrame:
public class PrimeiroSwing extends JFrame {
public PrimeiroSwing() {
setTitle("Primeiro Swing");
JButton bt = new JButton("Diga Olá");
bt.addActionListener(e ->
JOptionPane.showMessageDialog(this, "Olá!!"));
setLayout(new FlowLayout());
setSize(250, 60);
add(bt);
JButton btFechar = new JButton("Diga Adeus");
btFechar.addActionListener(e -> System.exit(1));
add(btFechar);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
}
public class SwingOla {
public static void main(String[] args) {
var janela = new PrimeiroSwing();
janela.setVisible(true);
}
}
📊 JTable: separando visualização de modelo de dados¶
JTable é o componente do Swing para apresentar dados em formato tabular — e ilustra bem outro princípio já visto (Capítulo 9): separar a visualização do modelo. A JTable não sabe quais objetos está mostrando; ela apenas consulta uma interface TableModel:
JTable— cuida de visualização e interação, delegando os dados ao seuTableModel.TableModel— interface que define o que aJTableprecisa para funcionar:getValueAt(),setValueAt(),getRowCount(),getColumnCount().AbstractTableModel— classe abstrata (Capítulo 8) que já implementa parte do trabalho repetitivo (principalmente o disparo de eventos de mudança), deixando você sobrescrever só o que é específico do seu domínio.TableModelListener— o listener usado pelaAbstractTableModelpara notificar aJTablequando os dados mudam (Observer, de novo).DefaultTableModel— implementação concreta pronta, útil para dados estáticos:
Object[][] dados = {
{"Lucas", "Costa", "Natação", 5},
{"Maria", "Silva", "Futebol", 3},
};
String[] colunas = {"Nome", "Sobrenome", "Esporte", "Tempo"};
var modelo = new DefaultTableModel(dados, colunas);
var tabela = new JTable(modelo);
Para dados vindos de uma fonte dinâmica — como o banco de dados do capítulo anterior — o caminho natural é escrever seu próprio TableModel, estendendo AbstractTableModel e delegando o carregamento a um DAO (padrão que veremos com mais detalhe no próximo capítulo, quando ele se junta a reflexão para eliminar boilerplate repetitivo).
🧪 Laboratório¶
Continuando o projeto do capítulo anterior (JDBC): construa uma tela de cadastro (JFrame com campos de texto e um botão "Salvar", persistindo via JDBC) e uma tela de listagem usando JTable, alimentada pelos dados do banco. A tabela deve permitir editar registros existentes e refletir as mudanças no banco; opcionalmente, adicione um botão para excluir um registro selecionado.
📝 Atividades¶
- Reimplemente o exemplo
PrimeiroSwing, adicionando umJTextFielde um botão que exibe, em umJOptionPane, o texto digitado. Identifique a fonte e o listener envolvidos. - Explique, em termos do padrão Observer (Capítulo 9), o papel de
ActionListenere deaddActionListener. - Pesquise a diferença entre AWT, Swing e JavaFX — em que cenário cada um ainda faz sentido hoje?
- No exemplo de
JTablecomDefaultTableModel: o que aconteceria se os dados viessem de um banco que muda em tempo real (outro processo inserindo linhas)? Que papel a interfaceTableModel(em vez da classe concretaDefaultTableModel) desempenharia nesse cenário?