Você está na página 1de 61

Um Sistema Java Completo - Criando Aplicações Gráficas com o NetBeans

Parte 1: Prototipação da interface com o usuário

Aprenda a criar aplicações com aparência profissional, desenvolvendo um exemplo


completo no NetBeans, começando com menus, janelas e componentes principais

O NetBeans é hoje o melhor IDE livre para iniciantes em Java, sem no entanto dever
nada em recursos voltados para o profissional mais experiente. A versão 4.0 foi
apresentada na edição anterior, com foco no novo modelo de projetos, que oferece
flexibilidade para os projetos mais complexos. Nesta série de artigos, o enfoque será
demonstrar como construir aplicações gráficas com a biblioteca padrão de componentes
visuais do J2SE, o Swing, usando os recursos do NetBeans.
Para desenvolvedores acostumados ao desenvolvimento com ferramentas RAD como
Delphi ou Visual Basic, haverá certo esforço de adaptação à filosofia de trabalho diferente
do Java visual. Isso inclui a forma de desenhar formulários usando gerenciadores de layout;
o tratamento de eventos baseado em listeners; e a forma de utilização de componentes
mais sofisticados (como tabelas e árvores), que exigem customização baseada na extensão
de classes ou implementação de interfaces.
Nesta parte apresentamos o desenvolvimento da interface gráfica de uma aplicação
de "Lista de Tarefas", ilustrando boas práticas do desenvolvimento Swing e mostrando como
o NetBeans pode auxiliar na implementação dessas práticas.
Se você está começando com o desenvolvimento Swing, não deixe de consultar os
quadros “Conceitos essenciais do Swing” e "Gerenciadores de Layout e o NetBeans". E
leitores atentos ao lançamento recente do NetBeans 4.1 podem consultar o quadro
“NetBeans 3.6, 4.0 e 4.1” para informações sobre as diferenças entre as versões. No
contexto deste artigo, as versões 4.1 e 4.0 são praticamente idênticas, e pouco mudou
desde a versão 3.6 no que se refere ao desenvolvimento visual.

A aplicação de exemplo
A Lista de Tarefas ou “todo list” é um componente comum de aplicações de
produtividade pessoal, que permite cadastrar tarefas com prioridades e datas de conclusão
associadas.
A interface gráfica inclui uma janela principal, que lista as tarefas ordenadas por
prioridade ou por data de conclusão, e um diálogo para edição ou inclusão de uma tarefa. A
janela principal é típica de aplicações desktop, contendo uma barras de menus e de
ferramentas, e uma área central para a visualização das tarefas; essa janela deve também
ser capaz de adaptar-se à resolução de vídeo do sistema ou ao tamanho determinado pelo
usuário. Já o diálogo é um típico formulário de entrada de dados, com caixas de edição,
checkboxes e outros controles posicionados em uma ordem natural de preenchimento, com
os usuais botões Ok e Cancelar na parte inferior.
O termo "formulário" é utilizado de forma genérica para referenciar janelas,
diálogos e painéis, ou seja, qualquer classe que possa ser editada
visualmente pela adição de componentes visuais.
Outras janelas da aplicação, como uma caixa “Sobre”, e um diálogo para exibição de
alertas sobre tarefas próximas de suas datas de conclusão serão construídas nas próximas
partes desta série.
Antes de iniciar a construção de uma interface visual, é sempre bom desenhar um
esboço contendo os principais componentes e sua disposição nas janelas. Papel e caneta ou
um programa de desenho são geralmente melhores para isso do que o seu IDE favorito,
pois permitem que as idéias fluam sem serem “viciadas” pela estrutura e componentes
padrões do IDE. A idéia aqui é fazer brainstorming sobre como deve ser a interface com o
usuário, e não obter uma definição precisa da aparência de cada formulário. Também há o
benefício de se poder focar nos componentes realmente essenciais para o usuário, antes de

1
entrar em detalhes de implementação, como ícones ou atalhos. Veja na Figura 1 um
esboço das duas janelas aplicação de exemplo deste artigo.
Design: por favor, ajustem as cores para deixar o fundo azulado o mais branco
possível:

Arquitetura da aplicação
É um padrão em desenvolvimento orientado a objetos utilizar a arquitetura MVC
como base de uma aplicação interativa. Dessa forma, o código da nossa aplicação será
organizado em classes de modelo, visão e controlador, utilizando para tal uma estrutura de
pacotes.
Neste artigo será realizada apenas a primeira etapa do desenvolvimento da
aplicação, que é a prototipação da interface com o usuário, utilizando os recursos de
desenho de interfaces Swing do NetBeans. Vamos limitar o código ao mínimo que possibilite
a navegação e exibição de informações; assim poderemos validar a usabilidade e a
adequação da interface às necessidades da aplicação. Nas próximas edições, além de novos
formulários, vamos criar as classes de negócios, contendo a lógica de validação e
persistência.

Criação do projeto
Para a aplicação de exemplo, vamos usar o modelo Java Application do NetBeans
(selecione File|New Project, escolha a categoria General e depois Java Application). Forneça
“Todo” como nome do projeto e aceite os padrões, criando assim a classe todo.Main.
Deixaremos esta classe vazia; mas adiante ela será modificada para instanciar a janela
principal da aplicação. Crie também os pacotes todo.modelo, todo.visao e
todo.controle.
Em uma aplicação de produção, o prefixo para os nomes dos pacotes da
aplicação deveria incluir o nome DNS da empresa, por exemplo,
"br.com.javamagazine.todo.modelo", de modo a evitar conflitos com
bibliotecas e componentes de terceiros.

A janela principal
Para criar a janela principal, usamos o modelo JFrame form: selecione File|New File,
depois Java GUI Forms e JFrame Form (veja a Figura 2). Utilize como nome "ListaTarefas"
e modifique o nome do pacote para "todo.visao".
Será aberto o editor visual de classes do NetBeans (veja a Figura 3). Nos lados
esquerdo e direito são exibidas várias visões (ou visualizações) relacionadas com o editor
visual do IDE. À direita temos a paleta (Pallete), onde podem ser encontrados os
componentes visuais do Swing e AWT. Abaixo da paleta, a área de propriedades
(Properties) apresenta as propriedades do objeto selecionado, permitindo sua customização.
À esquerda, logo abaixo da área que exibe as visões de projeto, arquivos e ambiente
de execução (Projects, File e Runtime) temos o inspetor (Inspector), que apresenta a
estrutura hierárquica de objetos visuais. O inspetor é muito útil quando um componente
está sobreposto por outro, ou quando não está visível na área de desenho por algum outro
motivo. O componente selecionado na área de desenho é sempre selecionado no inspetor, e
vice-versa; e as propriedades sempre refletem o componente selecionado. É possível ainda
selecionar vários componentes na área de desenho (ou no inspetor) e modificar
propriedades em todos eles ao mesmo tempo na janela de propriedades.
Há algumas diferenças no posicionamento e configurações padrões dessas partes do
IDE entre as versões 4.1 e 4.0 do NetBeans, entretanto é possível arrastar qualquer visão
para outra posição na janela principal do IDE, além de customizar a aparência da paleta.
Pessoalmente prefiro uma mistura dos padrões das duas versões, configurando a paleta
para uma aparência mais compacta (como na versão 4.0) e colocando o inspetor no lado
esquerdo (como no NetBeans 4.1), de modo a deixar mais espaço para a janela de
propriedades. Para reposicionar o inspetor, basta arrastá-lo pela sua barra de título; para
deixar a paleta mais compacta, clique nela com o botão direito e escolha Hide component
names.

2
Outra dica de customização do NetBeans é colocar a visão de saída (output) no modo
de "auto-esconder", para que ela não reduza o espaço disponível para o inspetor e a paleta;
ela fica como apenas um botão na parte inferior da janela do IDE. Note que a Figura 3 já
apresenta a visão de saída nesta configuração. Basta clicar no ícone da janela de saída,
quando ela for ativada pela próxima compilação ou execução do projeto.
O nosso esboço indica a presença de uma barra de menus e uma de ferramentas na
parte de cima da janela, e com uma tabela ocupando toda a sua área interna. Iniciamos
pela adição da barra de menus: clique no ícone do JMenuBar ( ) na paleta e clique em
qualquer parte da área de desenho. Por enquanto, deixe a barra de menus como está; mais
adiante iremos inserir os demais menus.
Agora selecione o componente JToolbar ( ) e clique logo abaixo da barra de
menus. O resultado é a colocação do JToolbar na posição "norte" do BorderLayout (veja
mais sobre este e outros gerenciadores no quadro "Gerenciadores de layout e o
NetBeans"). Observe que a barra aparece bem estreita, por não conter ainda nenhum
componente.
Selecione o JButton ( ) e clique em seguida no JToolbar (na área de desenho). Ele
irá automaticamente se ajustar ao tamanho do botão. Repita o procedimento algumas
vezes, para inserir os botões de adicionar, editar, excluir e marcar tarefas. Utilize símbolos
como “+” e "*" para o texto dos componentes (altere a propriedade text), enquanto não
criamos os ícones apropriados para cada um.
Selecione o JToogleButton (próximo ao JButton na paleta, com ícone igual) e
acrescente dois botões desse tipo ao JToolbar. Eles correspondem às opções de ordenação
das tarefas por prioridade ou por data de conclusão, e para exibir ou não as tarefas já
concluídas. Por fim, acrescente mais um JButton para a operação de visualização de
alertas. Continue utilizando símbolos como “!” no texto dos botões, como substitutos
provisórios para os ícones que ainda não foram acrescentados.
Para obter o agrupamento e separação visual entre os grupos de botões de
uma JToolbar não irá funcionar o uso de um JSeparator, como faremos mais
adiante para os menus. O problema é que o JSeparator sempre se expande
para ocupar toda a largura do container. Utilize em seu lugar um JLabel
contendo como texto apenas um espaço em branco.
Para criar uma barra de status, insira um JLabel ( ) na parte inferior da janela
principal, de modo que ele seja colocado na posição "sul" do BorderLayout. Finalmente,
insira um JScrollPane ( ) na parte central do JFrame, e dentro dele insira um JTable (
).
Sempre que o conteúdo de um componente puder ser maior do que a sua
área ocupada na janela, o componente deve ser colocado dentro de um
JScrollPane; componentes Swing precisam do JScrollPane para exibir barras
de rolagem.
A janela principal da aplicação já começa a se parecer com o nosso esboço inicial. A
Figura 4 ilustra como está a janela neste momento. Antes de prosseguir, recomendo
escolher nomes (propriedade name) significativos para cada componente, ou pelo menos
para aqueles cuja função é mais do que decorativa. Isso facilita a identificação dos
componentes no inspetor e também será útil posteriormente, quando forem codificados os
eventos. A Figura 5 apresenta o inspetor com os nomes dos componentes alterados.

Formatando a tabela
Durante a prototipação de uma interface gráfica, seja ela desktop ou web, é
importante desde o início inserir dados que sejam o mais próximo possível da realidade.
Caso contrário, não será possível decidir, por exemplo, se o tamanho e a legibilidade dos
componentes são adequados ou se a disposição na tela é intuitiva. Dessa forma, não é
recomendável utilizar “textos falsos” (como “Nonono” ou "Xxxxx"), pois eles não refletem a
informação que será vista pelo usuário final.

3
No caso da janela principal, isso significa não apenas configurar as colunas e os
títulos da tabela, mas também inserir algumas linhas de dados. Selecione o JTable (no
inspetor ou na área de desenho), depois selecione sua propriedade model e clique no botão
de reticências ( ) ao seu lado. Isso exibe o "customizador" da propriedade, que é um
mecanismo padrão do Java para que um componente visual ofereça suporte personalizado à
configuração de suas propriedades, independentemente do IDE utilizado.
Alguns IDEs podem optar por oferecer seus próprios customizadores como
alternativa (ou em adição) aos fornecidos pelos próprios componentes.
O customizador padrão do NetBeans para uma propriedade do tipo TableModel
(mostrado na Figura 6) apresenta duas abas. Na primeira, são definidas as colunas da
tabela, incluindo o nome e o tipo. Na segunda, é possível inserir um conjunto de dados
iniciais para a tabela. Forneça estes dados conforme a figura.
Se você está habituado a IDEs como Delphi ou Visual Basic, poderá achar a
configuração de tabelas do NetBeans um tanto restrita. Mas veremos na próxima edição que
a maioria dessas configurações é na verdade feita por código Java, em classes criadas como
parte da aplicação. Veremos ainda que as tabelas do Swing fornecem flexibilidade bastante
superior à oferecida pelos seus correspondentes em ambientes visuais não-Java.

Editando menus
A edição de menus e itens de menus no NetBeans não é feita na área de desenho,
mas sim no inspetor. Apenas o primeiro nível na barra de menus pode ser configurado e
visualizado na própria área de desenho. Para adicionar novos itens ou submenus, deve ser
utilizado o menu de contexto dos componentes, em vez da paleta.
Clique com o botão direito no JMenuBar e selecione a opção Add JMenu; depois
repita mais duas vezes a operação, de modo a terminar com quatro menus; Altere a
propriedade text dos menus para "Arquivo", "Editar", "Opções" e "Ajuda". Até aqui as
alterações podem ser observadas na área de desenho do editor visual do NetBeans.
Em seguida clique com o botão direito no JMenu "Arquivo" recém-criado e escolha
Add>JMenuItem O item adicionado não é exibido na área de desenho, mas pode ser visto
no inspetor; selecione esse item e mude seu text para “Nova lista de tarefas”. Repita a
operação para criar o item “Abrir lista de tarefas”, depois adicione um JSeparator e mais
um JMenuItem, para a opção “Sair”.
Ao selecionar um item de menu ou botão na IDE para alterar suas
propriedades, é comum dar-se um duplo clique acidentalmente. Isso faz o
NetBeans sair do editor visual e mudar para o editor de código, permitindo
editar o código associado ao evento de ação do botão ou item. Se isso
acontecer, observe no topo do editor de código dois botões: Source e
Design. Um clique em Design retorna ao editor visual; e a qualquer
momento pode-se clicar em Source para ver o código Java correspondente
ao formulário.
Para visualizar a aparência dos itens recém-adicionados ao nosso menu Arquivo,
clique no ícone com um pequeno olho ( ) na barra de ferramentas da área de desenho.
Isso irá executar imediatamente um "clone" do JFrame, contendo os mesmos
componentes, mas sem o código de tratamento de eventos (que ainda não acrescentamos
de qualquer modo). Assim é possível interagir com os menus e visualizar seus itens e
submenus. A Figura 7 mostra um dos menus nessa pré-visualização.
Repita o processo para inserir os demais itens, de acordo com a estrutura
apresentada na Figura 8. Observe na figura o uso de componentes JMenuItem,
JSeparator, JCheckboxMenuItem e JRadioButtonMenuItem.
Você pode mudar a ordem com que os elementos aparecem em cada menu,
arrastando os componentes para outra posição dentro do inspetor.

Ícones e barras de ferramentas

4
Um item de menu ou botão de barra de ferramentas contendo apenas texto é
considerado “pobre” para os padrões atuais de interfaces gráficas: espera-se que ao menos
os itens mais importantes tenham ícones associados, além de teclas de atalho. Todas essas
opções podem ser configuradas pelas propriedades do componente, mas os iniciantes em
Swing costumam ter dificuldades especiais com os ícones.
A primeira dificuldade vem de que a maioria dos IDEs Java (entre eles o NetBeans)
não incluem um conjunto padrão de ícones prontos para uso. A Sun fornece um conjunto de
ícones especialmente adaptados ao look-and-feel Metal padrão do Swing (até o J2SE 1.4)
em java.sun.com/developer/techDocs/hi/repository. Eles podem ser baixados todos em um
pacote jar ou um a um diretamente da página, e podem ser redistribuídos livremente com
sua aplicação.
Preferi, no entanto, copiar os ícones do Workbench do Eclipse, que são mais
modernos em vários aspectos e também podem ser redistribuídos livremente. Os ícones
estão todos na pasta src/icones nos fontes para download deste artigo (embora apenas uma
pequena deles sejam utilizados pela aplicação que estamos construindo).
A segunda dificuldade é na forma de referenciar os ícones, de forma independente do
diretório de instalação no computador do usuário final. Caso contrário você vai precisar
fornecer também um programa de instalação, assim como lidar com questões como letras
de drives (que só existem no Windows) e separadores de diretórios.
A solução recomendada é colocar os ícones junto às classes da aplicação, de modo
que possam ser localizados por uma busca pelo classpath, da mesma forma que são
localizados os arquivos .class. pela JVM. Arquivos de dados encontrados desta maneira são
chamados de recursos da aplicação.
Você pode então copiar a pasta src/icones do pacote de download para o mesmo
local em seu projeto no NetBeans. Por estarem na mesma pasta do código Java da
aplicação, os recursos (no caso os ícones) serão automaticamente copiados para a pasta
build/classes durante a compilação do projeto. Assim estarão junto aos bytecodes das
classes da aplicação, como desejamos.
O próximo passo é configurar os ícones nos itens de menu. Para cada item, selecione
sua propriedade icon e clique no botão do seu customizador ( ). No diálogo exibido
(Figura 9) selecione a opção Classpath e clique em Select File. Escolha o ícone adequado e
observe como a caixa de texto correspondente, em vez de registrar o caminho completo
para o arquivo do ícone no sistema de arquivos, registra apenas o caminho relativo ao
classpath da aplicação, por exemplo /icones/delete_obj.gif.
Caso o NetBeans não exiba os ícones recém-copiados no customizador da
propriedade icon, entre nas propriedades do projeto (clique com o botão
direito sobre o ícone do projeto, na visão de projeto) e adicione uma pasta
qualquer ao seu classpath de compilação (item Libraries, aba Build nas
propriedades do projeto). Ao se confirmar a alternação nas propriedades, o
IDE irá re-escanear o classpath e assim notar a presença dos ícones. Depois
não deixe de voltar às propriedades do projeto e remover esta pasta
adicional.
Quando o projeto for empacotado para distribuição (com Build|Build Main Project) os
arquivos de ícones serão copiados para o pacote jar executável, juntamente com os
bytecodes da aplicação. A aplicação poderá assim ser instalada em qualquer pasta no
computador do usuário final.
Da mesma forma que foi feito para os menus, podemos agora configurar os ícones
para os botões da barra de ferramentas. A Figura 10 apresenta a barra customizada com
os ícones. Tome o cuidado de escolher para os botões os mesmos ícones dos itens de menu
equivalentes.

Aceleradores e atalhos
Aplicações visuais bem-escritas definem mnemônicos para todos os seus itens de
menus, e também aceleradores para os itens utilizados com mais freqüência pelo usuário.

5
Um mnemônico permite navegar pelos menus utilizando apenas o teclado, o que
torna a operação da aplicação mais ágil para usuários freqüentes e mais confortável em
dispositivos como quiosques1[1]. Mnemônicos também são um dos principais itens de
acessibilidade2[2] de uma aplicação. Um mnemônico é em geral a primeira letra do item do
menu, conjugada com a tecla Alt. Além de itens de menus, outros componentes como
botões e labels podem ter mnemônicos, permitindo a navegação pelo teclado em um
formulário de entrada de dados. Mais adiante veremos como fazer isso, quando
detalharmos a construção do diálogo de edição de tarefas.
Já um acelerador permite a execução direta de alguma operação importante, sem a
necessidade de navegar até ela nos menus. Um exemplo é o popular Ctrl+X para o
comando Editar|Recortar.
Mnemônicos e aceleradores são definidos em propriedades dos itens de menus e
demais controles; então é fácil configurá-los no NetBeans. A Figura 11 apresenta essa
configuração para o item Editar|Adicionar tarefa da aplicação de exemplo. E a Figura 12
mostra os menus da aplicação completos, já com ícones, mnemônicos e aceleradores
definidos. Observe que as letras correspondentes aos mnemônicos são exibidas sublinhadas
em cada item de menu, enquanto que os aceleradores definidos são exibidos ao lado do
texto do item correspondente.
Já que falamos de facilidade de uso, aproveitamos para um comentário sobre as
barras de ferramentas. Conceitualmente, elas seriam “atalhos de mouse”, então se uma
operação é importante o suficiente para estar na barra de ferramentas, ela também deve
ter um atalho de teclado associado. Além disso, todo elemento numa barra de ferramentas
deve corresponder a algum item de menu, ou então a alguma opção num diálogo. Em
outras palavras, atalhos e barras de ferramentas nunca devem ser a única forma de se
realizar uma tarefa.
Voltando à aplicação: para que as barras e menus fiquem completos, eles
necessitam apenas da configuração de tooltips, que podem ser configurados alterando a
propriedade toolTipText de cada componente.

A janela de edição de tarefas


Para criar a segunda janela da aplicação, clique com o botão direito no pacote
"todo.visao" no inspetor e selecione New>File/Folder no menu de contexto; depois escolha
o modelo JDialog Form na categoria Java GUI Forms. Chame a classe de "EditaTarefa".
Altere o gerenciador de layout para o valor nulo ("Null Layout"). Assim será possível
posicionar os componentes de forma livre dentro do diálogo. Depois mude para um
GridBagLayout, deixando que o NetBeans gere um conjunto inicial de
GridBagConstraints3[3] para cada componente, que será depois ajustado manualmente. O
quadro “Entenda os GridBagConstraints” descreve o significado de cada uma das
propriedades de layout que teremos que ajustar.
A Figura 13 apresenta o JDialog com componentes posicionados no gerenciador de
layout nulo. Depois da mudança para o GridBagLayout vai parecer que a janela não
mudou, mas ao abrir o customizador do gerenciador de layout (clicando com o botão direito
no JDialog e selecionando Customize Layout), veremos que o conjunto de constraints
gerados pela conversão é menos que ótimo (Figura 14). Há várias colunas e linhas
adicionais e os espaçamentos desiguais em cada célula prejudicam o alinhamento dos
componentes. Apesar disso, ainda não encontrei um desenvolvedor que, depois de algum
tempo com o NetBeans, prefira utilizar o GridBagLayout desde o início, porque será mais
trabalhoso configurar manualmente todas as propriedades a cada componente adicionado.

1[1] Como os quiosques que informam sobre localizações de lojas em shopping


centers ou caixas eletrônicos de bancos
2[2] Uma aplicação é considerada acessível se foi construída levando em
consideração a facilidade de uso por pessoas com deficiências visuais ou motoras
3[3] As propriedades que vemos agrupadas na categoria Layout em um componente são
na verdade as propriedades do objeto de constraints do gerenciador de layout, cuja
classe é específica para cada gerenciador.

6
O posicionamento dos JLabels e caixas de texto (incluindo os dois JSpinner), não
oferece dificuldades. Lembre-se também de colocar o JTextArea dentro de um
JScrollPane. Os botões de Salvar, Cancelar e Remover deverão ser inseridos todos dentro
de um painel; é este painel que será posicionado dentro do diálogo (mais detalhes adiante).
Observe ainda os dois JSeparator, colocados antes e depois da área de texto de
observações, e o JLabel a parte superior do diálogo, que será utilizado como uma área ara
exibição de mensagens de erros.
Daqui em diante entramos em mais detalhes sobre como configurar o diálogo para
chegar à aparência final, conforme a Figura 15. Todos os ajustes serão feitos dentro do
customizador do GridBagLayout, o qual ao fim do processo estará como na Figura 16.
Inicie os ajustes zerando a margem interna (Internal Padding) e colocando o valor 1 na
altura (Grid Height ) de todos os componentes.

Posicionamento e espaçamento no formulário


Foi inserido um espaçamento de cinco pixels entre cada componente e entre os
componentes e as bordas do diálogo, para que não pareçam “grudados”. Observe as áreas
em amarelo na Figura 16; elas indicam onde foram adicionados espaçamentos. Aproveite o
fato de que é possível selecionar múltiplos componentes utilizando a tecla Ctrl + mouse,
para configurar espaçamentos uniformes.
Os labels de “Descrição”, “Prioridade” e “Data de conclusão”, assim como o checkbox
“Gerar alerta” foram ancorados (modificando Anchor) à direita (posição "leste"); os
controles correspondentes (um JTextField, dois JSpinner e um JFormattedTextField)
foram alinhados à esquerda (posição "oeste"). Ambos os grupos receberam espaçamento à
esquerda e abaixo, exceto pela primeira linha (Descrição), que ganhou também
espaçamento acima (caso contrário, ela ficaria colada ao label de mensagem).
Para JScrollPane e os dois separadores, foi acrescentado espaçamento à esquerda,
à direita e abaixo, e eles foram configurados para ocuparem três células de largura
(alterando Grid Width). A caixa de texto de descrição ocupa duas células, assim como a de
data de conclusão. As caixas de prioridade e de dias de antecedência para o alerta ocupam
apenas uma célula de largura.

O campo de observações
Queremos que o diálogo de edição de atividades seja redimensionável, e que o
campo de observações ocupe todo o espaço remanescente. Assim, o campo deve ser
configurado para se expandir tanto na horizontal quanto na vertical (mudando a
propriedade Fill para Both) e receber peso 1 em ambos os sentidos (modifique Weight X e
Weight Y). Observe que estas constraints se aplicam ao JScrollPane que contém o
JTextArea.
Já os dois separadores devem ser configurados para se expandirem apenas na
horizontal (alterando o valor de Fill para Horizontal), mas sem nenhum peso.

Configurando os botões
Um JButton normalmente assume a dimensão mínima que permita exibir seu texto,
gerando telas deselegantes, onde o botão de "Ok" é muito menor do que o de "Cancelar",
por exemplo. A aparência fica melhor quando botões relacionados têm as mesmas
dimensões.
A maneira mais fácil de fazer isso é inserindo os botões dentro do seu próprio painel,
e configurando o layout deste painel para um GridLayout. O painel é então posicionado na
parte inferior do diálogo, posicionado na parte de baixo do GridBagLayout, com duas
células de largura. À esquerda do painel de botões está o checkbox que registra se a tarefa
foi ou não completada. A âncora do painel é colocada na posição "leste", de modo que o
conjunto de botões fique alinhado à direita.

Label de mensagens

7
Para o label de mensagens, posicionado no topo do diálogo, queremos um visual
diferente do padrão. Ele deve ficar claramente diferenciado no formulário de edição, dada
sua função de exibir mensagens informativas.
Obtemos o efeito de “faixa” mudando a cor de fundo do label, e deixado seus
espaçamentos (Insets) zerados, de modo que ele fique colado aos cantos do diálogo;
entretanto, não queremos que o texto fique colado. A solução é definir uma borda:
alteramos a propriedade border do label para EmptyBorder e configuramos esta borda
com espaçamentos de 5 pixels em cada direção.
Quanto às cores de frente e de fundo, foram escolhidos um tom suave de amarelo e
um tom mais forte de azul-claro, para se obter um bom contraste. A mudança na cor de
fundo só será visível se for modificada a propriedade opaque do componente, pois o padrão
é que um JLabel tenha o fundo transparente, incorporando a cor de fundo do seu
container. Já em relação à fonte, foi mantido o padrão do Swing (Dialog); apenas
retiramos o negrito da fonte padrão.

Na maioria das vezes deve-se evitar a customização de fontes e cores em formulários de


entrada de dados, pois isso pode tornar a aplicação deselegante ou ilegível caso o usuário
opte por um look-and-feel customizado, ou por um tema de cores diferente para o look-
and-feel padrão. Caso seja necessário mudar as cores, mesmo que para um tom fixo, tenha
sempre o cuidado de fixar ambas as cores de frente e de fundo do componente.

Mnemônicos no formulário
Para garantir a agilidade na digitação e a acessibilidade do formulário, devem ser
definidos mnemônicos de teclado para os campos de texto e outros componentes, de forma
similar ao que foi feito para os menus.
O procedimento envolve, primeiro, associar cada JLabel ao seu componente de
entrada de dados, por meio da propriedade labelFor. Em seguida, é configurado o campo
displayedMnemonic para a tecla desejada, que será sublinhada no texto do label. Os
"botões" (JButton, JToogleButton, JCheckBox e JRadioButton) são configurados pelas
suas propriedades nmemonic específicas. Use a Figura 15 como referência para definir as
teclas de mnemônico para cada componente.

Testando o protótipo
Até este ponto, as janelas do protótipo foram testadas apenas pela pré-visualização
do editor visual do NetBeans, que nem sempre é fiel ao comportamento das classes Java.
Vamos então inserir o mínimo de código para que as duas telas possam ser iniciadas como
parte de uma aplicação, e verificar se o resultado funciona corretamente.
Localize a classe todo.Main na visão de projeto e dê um clique duplo para abri-la no
editor de código. Edite o código conforme a Listagem 1: dessa forma, a aplicação iniciará
instanciando uma janela ListaTarefas e tornando-a visível.
Em seguida, use o mesmo procedimento para abrir a classe ListaTarefas (caso ela
tenha sido fechada). Ela será aberta no editor visual, em vez de no editor de código. Dê um
clique duplo no primeiro botão da barra de ferramentas. Será então mostrado o editor de
código, com o cursor posicionado no método que trata o evento actionPerformed do
botão. (Os trechos em azul-claro são gerados pelo próprio editor visual e não poderão ser
modificados no editor de código.) Complete o código para o evento conforme a Listagem 2.
O objetivo é apenas instanciar o diálogo de edição de tarefas e exibi-lo de forma modal4[4].
Você já poderá executar a aplicação com Run|Run Main Project e verificar o
comportamento do exemplo; ou então selecionar Build|Build Main Project para gerar o
pacote jar executável para a aplicação, e iniciar a aplicação na linha de comando da
maneira usual (onde estamos supondo que o diretório Todo/dist contem o jar da aplicação):
$ java -jar Todo/dist/Todo.jar

4[4] Um diálogo exibido de forma modal impede que se interaja com sua janela "mãe",
até que seja fechado pelo usuário.

8
É importante que uma aplicação Swing seja testada desde o início com look-and-
feels alternativos, para garantir que ela é realmente uma aplicação multiplataforma. Por
exemplo, para utilizar o look-and-feel GTK do Linux, a linha de comando seria:
$ java -Dswing.defaultlaf=com.sun.java.swing.plaf.gtk.GTKLookAndFeel -jar
Todo/dist/Todo.jar
A Figura 17 apresenta o aspecto das duas janelas da aplicação com este look-and-
feel.

Conclusões
Este artigo demonstrou como usar as facilidades oferecidas pelo NetBeans para
desenvolver aplicações Java com aparência profissional, baseadas nos componentes visuais
do Swing. Como se viu, é importante atentar para detalhes, naturalmente, seguir as boas
práticas da plataforma Java.
Artigos futuros irão demonstrar como utilizar as janelas que foram prototipadas
nesta aplicação em uma aplicação orientada a objetos, considerando acesso a bancos de
dados e uso eficiente de threads. Veremos também como integrar e usar componentes de
terceiros no NetBeans.

Links
netbeans.org
Site oficial do NetBeans
netbeans.org/kb/articles/form_getstart40.html
GUI Building in NetBeans IDE 4.0
java.sun.com/docs/books/tutorial/uiswing
Trilha sobre Swing no Java Tutorial da Sun
cld.blog-city.com/read/1149708.htm
Swing Pointer, uma coleção de recursos para o desenvolvedor Swing

Alguns conceitos de Swing


Para os que estão começando com a programação visual em Java, são apresentados
aqui alguns conceitos do Swing, que são usados ou citados ao longo do texto.
Componentes, em um sentido amplo, são objetos visuais (ex.: JCheckBox,
JButton, JSeparator), ou objeto não-visuais (como GridBagLayout) que podem interagir
com objetos visuais por meio dos padrões JavaBeans.
Um container é qualquer objeto que possa conter outros objetos visuais. Todo
container tem um gerenciador de layout que organiza o posicionamento e
dimensionamento dos componentes dentro do container. Exemplos: JPanel e JDialog.
Todo componente possui um tamanho mínimo, que é a menor dimensão
(altura, largura) capaz de exibir todo o seu conteúdo (texto, ícone ou ambos). Em alguns
(poucos) casos, este tamanho é derivado de outras propriedades do componente; por
exemplo, em um JTextArea podem ser especificadas colunas e linhas da sua área de texto
visível.

NetBeans 3.6, 4.0 e 4.1


O NetBeans é um dos projetos mais antigos de IDEs livre com recursos visuais, mas
não era inicialmente com uma interface com o usuário bem-projetada. Era sim um exemplo
de como a plataforma Java e a tecnologia de componentes JavaBeans poderia viabilizar a
construção de aplicações desktop complexas.
Como mostrados em outros artigos desta coluna, na versão 3.6 iniciou-se um ciclo
de mudanças profundas no NetBeans, de modo a modernizá-lo e deixá-lo mais coerente
com modernas práticas de desenvolvimento de software em Java. A primeira mudança, no
3.6, foi a criação de uma nova interface com o usuário, baseada em visualizações que
podem ser ancoradas em qualquer parte da tela, organizadas em abas, e abandonando o
antigo modelo MDI (como o usado no Gerenciador de Programas do antigo Windows 3.0 e
em partes do Microsoft Office).

9
A versão 4.0 inaugurou um novo modelo de projetos, baseado no Ant, que permite
customizar com facilidade o processo de construção e empacotamento de aplicações.
Basicamente, o desenvolvedor pode acrescentar novas etapas ao processo, por exemplo,
geração de código via XDoclet ou pré-compilação de páginas JSP com o Jasper do Tomcat –
tudo sem necessitar de instalar um plug-in especialmente construído para o NetBeans.
Na recém-lançada versão 4.1 a novidade foram recursos voltados para o
desenvolvimento J2EE, em especial suporte a EJBs e a web services, não havendo grandes
mudanças na interface com o usuário ou no suporte ao desenvolvimento Swing, exceto pela
presença de novos templates e assistentes.
Da versão 3.6 até a versão 4.1 há poucas mudanças no editor visual, a maioria delas
apenas estéticas. A única mudança realmente importante foi o acréscimo, na versão 4.0, de
um customizador para o GridBagLayout, utilizado neste artigo para configurar o diálogo de
edição de propriedades.
Gerenciadores de layout e o NetBeans
A maior dificuldade do iniciante em Swing é lidar com os gerenciadores de layout
predefinidos. Isso é sentido especialmente por desenvolvedores habituados a ambientes
RAD para Windows. O motivo é que nestes ambientes se costuma posicionar os
componentes de modo fixo (em pixels) nos formulários, enquanto que no Swing o
posicionamento é determinado por um gerenciador de layout.
Por isso foi preparado este quadro, que relaciona os usos mais comuns dos principais
gerenciadores de layout do J2SE (e um específico do NetBeans), além das facilidades
oferecidas pelo IDE para a customização visual de componentes com esses gerenciadores.

FlowLayout
O gerenciador de layout de “fluxo” apenas posiciona os componentes em fila, um
após o outro, cada qual com suas dimensões mínimas. O FlowLayout imita o fluxo de texto
em uma folha de papel e, como um parágrafo em um processador de textos, também pode
alinhar os componentes à direita, à esquerda, ou centralizados dentro do container. O uso
mais comum deste layout é para preencher uma linha com a maior quantidade possível de
componentes, por exemplo em barras de ferramentas ou de status.
No NetBeans: Novos componentes são sempre adicionados ao final do fluxo, mas
eles podem ser reposicionados pelo mouse. Um quadrado pontilhado indica a nova posição
do componente durante esta operação.

BorderLayout
O BorderLayout posiciona os compomentes nas “bordas” do container, deixando a
maior parte da sua área disponível para o componente inserido no centro. Cada borda é
identificada por um ponto cardeal (NORTH, SOUTH, EAST, WEST). Apenas um componente
será visível em cada borda, expandido na altura ou largura para ocupar toda a borda do
container, porém assumindo o valor mínimo na outra dimensão.
Note que esta disposição reflete o padrão na maioria das aplicações desktop, como
processadores de texto ou programas de desenho: uma barra de ferramentas ao norte, uma
barra de status ao sul, opcionalmente outras barras de ferramentas ao leste e oeste, e uma
área de edição ao centro.
No NetBeans: O componente é posicionado na borda da área de desenho que for
clicada quando é feita sua adição ao container, e permite que um componente seja
arrastado para outra posição (outra borda ou para o centro) desde que esta posição esteja
vazia. Caso um componente seja arrastado para uma posição já ocupada, o BorderLayout
irá se perder (e consequentemente também o NetBeans), e a correção terá que ser feita na
visão de propriedades e/ou no inspetor.

GridLayout

10
O GridLayout organiza os componentes em uma "grade" ou tabela com tamanho
(linhas e colunas) pré-fixadas no momento da sua criação. Todas as células possuem o
mesmo tamanho, e são expandidas para ocupar a área total disponível no container. Caso
haja menos componentes do que células, o espaço das células vazias é distribuído
igualmente entre os componentes; mas podem ficar células vazias nas últimas colunas da
última linha.
O GridLayout é adequado quando se deseja que um grupo de componentes (como
um grupo de botões) tenha dimensões uniformes, como na caixa de ferramentas de um
programa de desenho ou o par de botões “Ok” e “Cancela” de um diálogo.
No NetBeans: Novos componentes são sempre acrescentados na próxima célula
vazia da grade, mas podem ser arrastados com o mouse para uma posição (célula)
diferente.

GridBagLayout
Com nome estranho ("saco de grades"), o GridBagLayout é o mais poderoso e mais
flexível dos gerenciadores fornecidos com o J2SE. Ele imita em linhas gerais o
funcionamento de uma tabela HTML, em que um componente pode ocupar várias células,
ou seja, se estender por várias colunas e linhas. Os componentes podem ser expandidos
para ocupar toda a área das suas células, ou serem alinhados em qualquer posição do
conjunto de células. Linhas e colunas assumem as dimensões do maior componente, mas é
necessário que tenham todas o mesmo tamanho. E algumas células podem ser configuradas
para ocuparem toda a área disponível no container.
Podemos afirmar com segurança que qualquer disposição de componentes pode ser
configurada em um GridBagLayout. Por outro lado, a quantidade de constraints
(propriedades e restrições de layout) possível para cada componente também deu a este
gerenciador a fama de ser difícil de programar.
No NetBeans:
O editor visual do NetBeans não tinha suporte ao GridBagLayout antes da versão
4.0, o que obrigava o desenvolvedor a configurar cada uma das dezenas de constraints,
manualmente para cada componente. Mas a nova versão fornece um customizador para o
GridBagLayout, numa janela externa ao editor visual. Nela é representada a grade
definida para o layout, assim como a área ocupada por cada componente. Botões de setas
permitem ajustar facilmente cada propriedade, e os componentes podem ser arrastados
entre as células da grade.
Observe o código de cores no customizador: azul significa que a célula é ocupada por
um componente; cinza indica uma célula vazia; vermelho indica uma linha ou coluna sem
componentes; e amarelo mostra espaçamento (insets) interno à célula.
O customizador do GridBagLayout é realmente fácil de usar, e em pouco tempo você
estará acostumado com ele. Pena que, pelo customizador, não seja possível modificar
propriedades além dos constraints de cada componente, nem adicionar novos componentes.
Então será necessário entrar e sair do customizador várias vezes durante o desenho de um
formulário.

Null Layout
Na verdade o Null Layout não é um gerenciador de layout, mas sim a ausência de
qualquer gerenciador (equivale a definir a propriedade layout do container com o valor
null). O resultado é que no IDE componentes podem ser posicionados à vontade na área de
desenho; eles permanecerão na posição onde foram colocados e com as dimensões
indicadas pelo desenvolvedor. Criar telas com o "layout nulo" às vezes chamado de usar o
"posicionamento absoluto".
No NetBeans: O NetBeans pode parecer “pobre” no seu suporte ao layout nulo, pois
não fornece as ferramentas de alinhar e centralizar componentes, ou para ajustar as
dimensões de grupos de componentes. Por outro lado, o uso desse layout não é
recomendado, por ser incompatível com o uso de look-and-feels customizados e ir contra à

11
filosofia de portabilidade do Java. Então faz sentido que o NetBeans desestimule o uso desta
opção ao não fornecer facilidades específicas.

Absolute Layout
O AbsoluteLayout não é um gerenciador padrão do Java, mas uma adição do
NetBeans; seu uso requer que o pacote modules/ext/AbsoluteLayout.jar seja copiado para a
estação do usuário e configurado no classpath. Ele funciona como um Null Layout, com a
diferença de que as dimensões do container são calculadas corretamente, permitindo o uso
do método pack() (com o Null Layout, o resultado são dimensões [0, 0]). Ele apresenta
todas as desvantagens do Null Layout, portanto também não é recomendado para uso
geral.

GridBagConstraints
As propriedades que determinam a posição e dimensões de um componente dentro
de um GridBagLayout são reunidas em um objeto chamado GridBagConstraints. Cada
componente adicionado a um container cujo gerenciador de layout seja um GridBagLayout
possui seu próprio objeto GridBagConstraints. Neste quadro fornecemos uma breve
explicação do significado de cada uma dessas propriedades.
Note que identificamos as propriedades da classe GridBagConstraints do modo
como aparecem no customizador do GridBagLayout. Estes nomes não são os mesmos que
serão encontrados na documentação javadoc da classe, onde é seguido um estilo mais
“telegráfico”, na sintaxe de Java. Por exemplo, a propriedade "Grid Width" do customizador
é na verdade gridWidth; e "Internal Padding X" é ipadx.
Grid X e Grid Y – indicam a posição do componente dentro da tabela ou grade
utilizada pelo GridBagLayout para posicionar os componentes.
Grid Width e Grid Height – determinam a quantidade de células da tabela que
serão ocupadas pelo componente, respectivamente na largura e altura.
Fill – indica se o componente irá ocupar seu tamanho mínimo, deixando vazio o
restante das células ocupadas; ou se ele será expandido para ocupar todo o espaço alocado
para a célula. A expansão pode ser apenas na vertical (valor Vertical), apenas na
horizontal (Horizontal), ou em ambos os sentidos (Both).
Internal Padding X e Internal Padding Y – indicam espaço acrescentado ao
próprio componente, aumentando o seu tamanho mínimo, em vez de acrescentado à célula
que o contém.
Anchor – indica o alinhamento do componente em relação à suas células, caso ele
não preencha toda a área alocada a elas. Seus valores possíveis são baseados nos pontos
cardeais, como North ou SouthEast.
Weight X e Weight Y – Valores maiores do que zero indicam que a célula será
expandida para além do seu tamanho mínimo, ocupando o espaço na largura ou altura que
sobrar no container. Estas propriedades costumam ser utilizadas apenas quando o container
pode ser redimensionado pelo usuário.
Insets – indicam espaçamentos a serem inseridos nas quatro bordas da célula,
afastando o componente destas bordas. Dois componentes em células adjacentes e com
espaçamentos zerados serão exibidos "grudados" um no outro.

Dicas do editor visual


Aqui estão reunidas algumas dicas que podem tornar mais produtivo o uso do editor
visual do NetBeans para a construção de interfaces Swing. A maioria explora o uso do botão
direito do mouse na área de desenho ou no inspetor como alternativa à visão de
propriedades.
Quais propriedades foram alteradas?

12
A visão de propriedades coloca em negrito os nomes das propriedades que estão
com valores diferentes do padrão, de modo que fica fácil verificar o que foi customizado em
um componente, por exemplo, quando se deseja deixar um outro componente com a
mesma aparência. Para retornar propriedades modificadas aos seus valores padrão, abra o
customizador da propriedade e clique no botão Reset to Defaults.

Alterando o texto de um componente


Componentes que exibem textos (como JLabel, JButton e JMenuItem) oferecem
em seu menu de contexto a opção Edit Text para edição rápida do texto do componente
diretamente na área de desenho.

Alterando o layout de um container


O gerenciador de layout de um container também pode ser modificado diretamente
pelo menu de contexto.

Renomeando um componente
É importante dar a todos os componentes nomes intuitivos, porque esses nomes são
utilizados nas propriedades do container (por exemplo, JFrame, JDialog ou JPanel) que
os contém. O menu de contexto do componente fornece a opção de renomear o
componente, mas por algum bug ela nem sempre funciona se for utilizada na área de
desenho do editor. Por outro lado, ela sempre funciona se utilizada na visão do inspetor.

Novas linhas ou colunas em um GridBagLayout


No customizador do GridBagLayout, pode-se criar novas linhas e colunas
simplesmente arrastando-se o componente para além da parte inferior da grade, ou para

13
além da fronteira direita da grade. No processo pode-se criar também várias linhas e
colunas vazias, que poderão depois ser ocupadas por novos componentes.
Caso você queira inserir novos componentes no início (ou no meio) da grade, inicie
arrastando os componentes mais à direita (ou mais embaixo) para criar novas linhas; e
depois arraste os demais componentes para as células criadas, até que fiquem células
vazias nas posições desejadas.

Figura 1. Esboço da aplicação Todo

Figura 2. Criação da janela ListaTarefas

14
Figura 3. Editor visual do NetBeans 4.0 (esquerda) e do 4.1 (direira). Notem a posição
diferente do inspetor e a configuração do palete de componentes.

Figura 4. Protótipo parcial da janela principal (ListaTarefas); observe o agrupamento dos


botões (4, 2, 1) na barra de ferramentas

Figura 5. Hierarquia de objetos da janela principal

15
Figura 6. Abas do customizador para o TableModel do JTable na janela principal

Figura 7. Menu Arquivo na pré-visualização do editor visual

Figura 8. Estrutura completa de menus da janela principal, visualizada no inspetor

16
Figura 9. Escolhendo um ícone para um item de menu ou toolbar

Figura 10. Barra de ferramentas do protótipo depois de configurada com os ícones

Figura 11. Configurando mnemônicos e aceleradores no NetBeans (propriedades


correspondentes destacadas em amarelo)

Figura 12. Os menus da aplicação completamente configurados

Figura 13. Diálogo de edição de tarefas, “rascunhado” com o Null Layout

17
Figura 14. Customizador de layout do diálogo de edição de tarefas, após a conversão para
um GridBagLayout

Figura 15. Forma final do diálogo de edição de tarefas

Figura 16. Customizador do GridBagLayout do diálogo de edição de tarefas

18
Figura 17. Aplicação Todo com o look-and-feel GTK do Linux

Listagem 1. Modificações na classe principal da aplicação para iniciar a janela da listagem


de tarefas
package todo;

import javax.swing.*;
import todo.visao.*;

public class Main {


public Main() {}

public static void main(String[] args) {


JFrame w = new ListaTarefas();
w.pack();
w.setVisible(true);
}
}

Listagem 2. Código vinculado ao evento actionPerformed no primeiro botão da barra de


ferramentas, para exibir o formulário de edição de tarefas
import javax.swing.*;
// ...
private void botaoAdicionarActionPerformed(java.awt.event.ActionEvent evt) {
JDialog d = new EditaTarefa(this, true);
d.pack();
d.setVisible(true);
}

19
Parte 2: JTable, MVC Aplicado e Tratamento de Eventos

Saiba como customizar componentes JTable, organizar o tratamento de eventos,


estruturar uma aplicação visual para facilitar extensões e manutenções.
Nesta edição, damos continuidade à construção da aplicação iniciada na edição
anterior, apresentando conceitos fundamentais de desenvolvimento Java e recursos da série
4.x do IDE livre NetBeans. A primeira parte foi focada na programação visual e em como o
NetBeans pode ser utilizado para prototipar uma interface com usuário baseada no Swing,
além de mostrar características dos principais gerenciadores de layout.
Nesta segunda parte, passamos ao editor de código. Vamos customizar o visual da
tabela que exibe as tarefas, para indicar com cores diferentes tarefas completadas,
atrasadas ou em estado de alerta. Também iremos tratar dos eventos gerados pelos
componentes da interface, evoluir a arquitetura e saber pelos componentes da interface,
evoluir a arquitetura e saber como evitar que o tratamento de eventos transforme seu
código orientado e objetos em “código espaguete”.
Arquitetura MVC em aplicações Gráficas
Durante a prototipação da interface gráfica, buscamos ficar o máximo possível
dentro do editor visual do NetBeans. O objetivo era apenas criar uma “casca” visual para a
aplicação que pudesse ser avaliada e discutida com os usuários. Agora vamos começar a
colocar lógica por trás dessa casca, permitindo avaliar e testar a real funcionalidade da
aplicação.
É comum, no desenvolvimento de aplicações visuais, acabar gerando código
desorganizado, onde uma modificação em qualquer parte gera efeitos colaterais nos locais
mais inesperados; ou onde a simples adição de uma informação extra exige uma cascata de
mudanças em várias partes da aplicação.
Para evitar isso, vamos adotar uma arquitetura muito popular em aplicações
interativas, a arquitetura MVC (Movel-View-Controller, Modelo-Visão-Controlador). A MVC
foi usada ou descrita em vários artigos na Java Magazine (em maior parte no contexto de
aplicações web). Nela, cada classe tem um papel bem definido: tratar da exibição das
informações (Visão); responder a ações do usuário (Controlador); ou cuidar da consistência
e persistência dos dados (Modelo). Classes com papéis diferentes são praticamente
independentes entre si, e assim é possível modificá-las sem medo de gerar “efeitos
colaterais”. O uso da arquitetura MVC também torna fácil identificar onde fazer cada
mudança.
Para tornar o uso da arquitetura bem explícito, iremos organizar as classes Java em
três pacotes: todo.visao, todo.controle e todo.modelo.
No pacote visao estão classes gráficas (como janelas ou componentes
personalizados), ou classes necessárias para o seu funcionamento. Essas classes não
tomam decisões a respeito de como uma operação deve ser realizada – este é o papel das
classes do pacote controle, as quais efetivamente respondem aos eventos do usuário
(como cliques num item de menu) e decidem qual operação realizar. Já as classes do pacote
modelo representam os dados da aplicação, e contêm a inteligência necessária para
realizar ações sobre esses dados.

Figura 1. Dependências entre componentes no modelo MVC

20
A Figura 1 ilustra o relacionamento entre os pacotes, usando um diagrama UML (o
desenho de pasta representa um pacote e agrupa várias classes)1. Observe que o
Controlador fica no “meio do caminho” entre a Visão e o Modelo2. Essa separação traz um
benefício adicional: ela permite que uma mesma Visão seja reutilizada com vários Modelos
contendo as mesmas informações – mas obtendo essas informações de fontes diferentes
(ex.: um Modelo baseado em banco de dados e outro acessando arquivos XML). Pelo seu
lado, o Modelo fica independente da interface com o usuário (que faz parte da Visão): as
classes do Modelo podem, por exemplo, ser utilizadas depois, numa aplicação web ou num
MIDlet J2ME.
Inteligência em objetos
No início da Orientação a Objetos, era comum defender-se a idéia de que um objeto
deveria conter toda a “inteligência”relacionada a ele. Mas com o passar do tempo verificou-
se que essa estratégia poderia levar a objetos “gordos”, concentrando mutas
funcionalidades, e com manutenção difícil e baixo desempenho. Hoje, no entanto, se
considera uma alternativa válida criar também objetos “burros”, que apenas trafegam
informações de um “objeto inteligente” para outro, deixando-os mais independentes entre
si.
No nosso caso, os “objetos burros” serão Value Objects (VOs)( que são JavaBeans,
com atributos e seus métodos get/set, e possivelmente alguma funcionalidade localizada).
E os objetos inteligentes são os objetos de Visão, Modelo e Controle.
A única classe VO de que necessitamos agrupa todas as informações de uma tarefa,
então este será o seu nome. Na nossa aplicação, teremos classes de Visão que sabem
representar graficamente uma tarefa, e classes de Modelo que sabem como recuperar e
salvar tarefas do bando de dados (ou de um collection etc.).
Para simplificar, a classe Tarefa ( e outros VOs que surgirem) podem ser deixados no
mesmo pacote das classes de Modelo. Afinal, são as operações implementadas no Modelo
que determinam quais informações deverão estar nos VOs.

Arquitetura da aplicação
Nossas classes de Visão – ListaTarefas (um JFrame) e EditaTarefa (um JDialog)
– foram prototipadas no artigo anterior. Agora estamos preocupados em definir a interface
externa destas classes, isto é, o que irão expor para o Controlador. Precisamos definir os
eventos, que representam ações realizadas pelo usuário, e os métodos para manipular
informações encapsuladas em VOs.
Teremos apenas uma classe no Controlador, chamada ConsultaEditaTarefas.
Em aplicações mais complexas, poderá haver várias classes controladoras.
Uma estratégia para começar é criar um controlador para cada conjunto de
operações consultar-editar-apagar da aplicação.
Nosso exemplo também terá apenas uma classe no Modelo, chamada
GerenciadorTarefas. Esta classe é um DAO (Data Access Object) e será responsável por
ler e atualizar registros no banco de dados.
Aplicações mais complexas terão classes de Modelo que encapsulam processos de negócios
em vez de apenas entidades de informação.
E irão para o Controlador apenas estas classes de mais alto nível, reservando os DAOs para
uso interno.
Na Figura 2 temos um modelo UML contendo as classes que iremos desenvolver.
Note como todas as classes no diagrama têm dependências em relação ao VO Tarefa. (Mas
como este não tem inteligência significativa, ele é com freqüência omitido em diagramas de
classes, e na avaliação de dependências entre classes da aplicação).

Começando a construção

21
Nossa idéia é prosseguir construindo a aplicação “de cima para baixo”. Iniciamos
pela interface com o usuário (Visão) e vamos descendo até chegar à logica de banco de
dados (Modelo). Em cada etapa será feito o mínimo de trabalho necessário para que seja
possível testar uma nova funcionalidade. Assim, nossos objetos de modelo serão por algum
tempo versões temporárias, mantendo os dados em memória sem acessar o bando de
dados.
A classe Main (não representada na Figura 2) permanece como sendo a classe
principal da aplicação, e do projeto no NetBeans. Seu papel é instanciar e conectar as
classes de Visão, Modelo e Controle.
A Listagem 1 apresenta a classe Tarefa e a Listagem 2, a classe Main. Note que
Tarefa fornece alguns métodos utilitários relacionados com a manipulação das datas de
conclusão e prazo de alerta. Parece ir contra a recomendação de que um VO não deve ter
inteligência, mas estes métodos não dependem de nada externo ao próprio objeto, então
este é o seu lugar.
A Listagem 3 apresenta a classe GerenciadorTarefas, que implementa um único
método: listaTarefas(). Neste ponto, o método constrói um java.util.List contendo
objetos Tarefas pré-fabricados para que seja possível testar o código da Visão e do
Controlador.
Você pode gerar os métodos get/set automaticamente no NetBeans. Depois
de definir os atributos da classe Tarefa, clique com o botão direito no editor
de código (ou na visão de projeto do NetBeans) e escolha
Refactor|Encapsulate Fields.
A Listagem 4 mostra a primeira versão da classe controladora
ConsultaEditaTarefas. Tudo o que ela faz no momento é passar para a Visão
(ListaTarefas) a lista de objetos Tarefa retornada pelo Modelo (GerenciadorTarefas).
Note que as classes de Visão devem ter métodos para receber as tarefas, e é
responsabilidade da Visão repassar os dados das tarefas para componentes visuais internos,
como caixas de texto e componente JTable.

Figura 2. Principais classes da aplicação Lista de Tarefas


Modelo da aplicação e models do Swing
Na maioria dos toolkits gráficos, os objetos visuais de tabela e de lista manipulam
apenas arrays de strings; para preenchê-los, os dados devem ser convertidos em strings e
inseridos linha a linha (ou célula a célula). Esta abordagem tem dois problemas: aumento
do consumo de memória, pois os dados são duplicados na tabela; e maior tempo gasto para
inserir um conjunto extenso de dados.
No Swing, entretanto, os dados para exibição num componente de tabela (JTable)
devem se passados num classe que implementa a interface TableModel. Esta classe fica
responsável por fornecer para a tabela os dados das células visíveis, e em informar quando
os dados forem modificados. Com isso, não são acrescentadas ou alteradas linhas na tabela
em si; essas operações são realizadas no TableModel associado à tabela.
O Swing fornece a classe DefaultTableModel, que armazena dados em vetores de
vetores (java.util.Vector), emulando a abordagem dos outros toolkits. Isso permite
começar rapidamente, mas o recomendado mesmo é criar o seu próprio TableModel, que
acessa diretamente os VOs. Dessa forma não há duplicação de dados e se obtêm ganhos de

22
performance, pois a tabela pede ao seu modelo apenas os dados que irá efetivamente
utilizar. Todos os componentes do Swing seguem a abordagem descrita: em vez de o
próprio componente armazenar as informações que exibe, essa responsabilidade e delegada
para um objeto model do Swing. (O uso do nome “model” nas classes e interfaces utilizadas
pelos componentes do Swing não é coincidência. Para uma discussão mais aprofundada
sobre o assunto, consulte o quadro “Arquitetura MVC e o Swing”.)
Listagem 1. VO Tarefa
package todo.modelo;

import java.util.Date;
import Java.util.Calendar;

public class Tarefa {


private int id;
private String descricao;
private int prioridade;
private Date dataConclusao;
private boolean gerarAlerta;
private int diasAlerta;
private String observacoes;
private boolean concluida;

private Calendar getDataHojeNormalizada()


Calendar hoje - Calendar.getlnstanceC);
hoje.set(Calendar.HOUR_OF_DAY. 0);
hoje.set(Calendar.MINUTE. 0);
hoje.set(Calendar.SECOND. 0);
hoje.set(Calendar.MILLISECOND, 0);
return hoje;
}

public boolean isAtrasada() {


Date conclusao = getDataConclusao();
if (conclusao == null)
return false;
else {
return conclusao.compareTo(
getDataHojeNormalizada().getTime()) < 0;
}
}

public boolean isAlertaAtivo() {


Date conclusao = getDataConclusao();
if (!isGerarAletra() || conclusao == null)
return false;
else {
Calendar diaConclusao = Calendar.getlnstance();
diaConclusao.setTime(getDataConclusao());
int dias = getDataHojeNormalizada().get(Calendar.DAY_OF_YEAR)
- diaConclusao.get(Calendar.DAY_OF_YEAR);
return dias <= getDiasAlerta();
}
}

public Tarefa() {
setConcluida(false);
setGerarAlerta(false);
}

23
// ... métodos get/set
}

Listagem 2. Classe principal da aplicação

package todo;

import javax.swing.*;
import todo.visao.ListaTarefas;
import todo.controle.ConsultaEditaTarefas;
import todo.modelo.GerenciadorTarefas;

public class Main {


public Main() {}

public static void main(String[] args) {


ListaTarefas visao = new ListaTarefas();
GerenciadorTarefas modelo = new GerenciadorTarefas();
ConsultaEditaTarefas controle = new ConsultaEditaTarefas(
visao, modelo);
visao.pack() ;
visao.setVisible(true);
}
}

Listagem 3. Classe “fake” de modelo, GerenciadorTarefa

package todo.modelo;

import java.util.*;
import java.text.*;

public class GerenciadorTarefas {


List tarefas = new ArrayList();

public GerenciadorTarefas() {
DateFormat df = DateFormat.getDatelnstance(
DateFormat.SHORT);
Tarefa umaTarefa = new Tarefa();
umaTarefa.setDescricao(“Entregar coluna para a Java Magazine”);
try {
umaTarefa.setDataConclusao(df.parse(“05/06/2005”));
}
catch(Exception e) { // não faz nada }

umaTarefa.setPrioridade(1);
umaTarefa.setObservacoes(“Testar exemplo no NB 4.0 e 4.1”);
umaTarefa.setGerarAlerta(true);
umaTarefa.setDiasAlerta(15);
umaTarefa.setConcluida(false);
tarefas.add(umaTarefa);

//… cria e adiciona mais instâncias de Tarefa …


}

public List listaTarefas() {

24
return tarefas;
}

private boolean stringVazia(String str) {


return str == null || str.trim().length() == 0;
}

private void validaTarefa(Tarefa tarefa) |


throws ValidacaoException {
if (stringVazia(tarefa.getDescricao()))
throw new ValidacaoException(
“Deve ser fornecida uma descrição para a tarefa”);
}

public void adicionaTarefa(Tarefa tarefa)


throws ValidacaoException
validaTarefa(tarefa);
tarefas.add(tarefa);
}

public void editaTarefa(Tarefa tarefa)


throws ValidacaoException (
validaTarefa(tarefa);
}

public void removeTarefa(Tarefa tarefa)


tarefas.remove(tarefa);
}
}

Listagem 4. Classe controladora ConsultaEditaTarefas

package todo.controle;

import todo.visao.*;
import todo.modelo.*;

public class ConsultaEditaTarefas {


private ListaTarefas visao;
private GerenciadorTarefas modelo;

public ConsultaEditaTarefas(ListaTarefas visao, GerenciadorTarefas modelo) {


this.visao = visao;
this.modelo = modelo;
listaTarefas();
}

public void listaTarefas() {


visao.setListaTarefas(modelo.listaTarefas));
}
}
As classes model do Swing devem ser tratadas como sendo internas à Visão,
permanecendo invisíveis ao restante da aplicação. A Listagem 5 apresenta a classe
TarefasTableModel, que permite a exibição dos dados de uma List em um JTable. A
Listagem 6 mostra as modificações feitas na classe ListaTarefas para utilizar este
TableModel. (O método getValoresTarefa() adicionado à nossa classe model será
necessário depois para customizar a formatação da tabela.)

25
Sempre que criar no NetBeans classes que estendem outras classes ou
implementar interfaces você pode usar o comando Tool|Override Methods
para gerar a declaração dos métodos a serem redefinidos ou
implementados.
Chegamos ao ponto em que é possível executar a aplicação e testar o seu
funcionamento. O conjunto de classes criadas até o momento é ilustrado na Figura 3, que
é uma captura da visão de projetos do NetBeans. O resultado esperado é o apresentado na
Figura 4, onde o JTable exibe os dados dos VOs pré-fabricados retornados pelo
GerenciadorTarefas.

Figura 3. Classes da aplicação após a inserção de um TableModel customizado

Listagem 5. TableModel para exibição de um List

package todo.visao;
import java.util.*;
import java.text.*;
import javax.swing.table.*;
import todo.modelo.*;

public class TarefasTableModel extends AbstractTableModel {


private List tarefas;
private DateFormat df = DateFormat.getDatelnstance(
DateFormat.SHORT);

public TarefasTableModel(List tarefas) {


this.tarefas = tarefas;
public Object getValueAt(int rowlndex. int columnIndex) {
Tarefa umaTarefa = tarefas.get(rowIndex);
switch(columnIndex) {
case 0: return umaTarefa.getPrioridade();
case 1: return umaTarefa.getDescricao();
case 2: return umaTarefa.isGerarAlerta():
case 3: return umaTarefa.getDataConclusao();
}
return null;
}
public int getRowCount() {
return tarefas.size();
}

public int getColumnCount() {


return 4;
}

26
public Tarefa getValoresTarefa(int rowIndex) {
return tarefas.get(rowIndex);
}
}

Listagem 6. Modificações em ListaTarefas para utilizar a TableModel

package todo.visao;
// ... imports

public class ListaTarefas extends javax.swing.JFrame {


public void setListaTarefas(List tarefas) {
this.tarefas.setModel(new TarefasTableModel(tarefas));
}

// … código gerado pelo editor visual do NetBeans

Customizando as colunas da tabela


Visualmente, a janela mostra na Figura 4 foi um retrocesso em relação ao protótipo
construído no artigo anterior, pois não apresenta mais os títulos das colunas. Isso
aconteceu porque, o usar um TableModel customizado, toda a configuração feita no editor
visual do NetBeans sobre um DefaultTableModel passa a ser ignorada durante a
execução da aplicação. Algumas configurações de colunas poderiam ser inseridas no próprio
TableModel; entretanto, outras, como a largura das colunas, exigem a criação de um
ColumnModel, que agrega por sua vez uma coleção de objetos TableColumn (cada
TableColumn indica o título da coluna, sua largura, se ela pode ser redimensionada etc.).
Optamos então por colocar todas as configurações no ColumnModel, em vez de dividi-la
entre as duas classes.
Queremos que nosso ColumnModel customizado, ao qual chamaremos de
TarefasCollumnModel (Listagem 7) exiba as colunas com títulos “Prioridade”, “Alerta?” e
“Data de Conclusão”, com larguras pré-fixadas e grandes o suficiente para exibição do seu
conteúdo. Queremos também que toda a largura restante na tabela seja ocupada pela
coluna de descrição.
O segredo é configurar as larguras das colunas de acordo com a fonte de caracteres
configurada para tabela (a fonte default vai depender do look-and-feel padrão do Swing em
seu sistema operacional). Infelizmente um ColumnModel não tem acesso ao seu JTable,
de modo que foi necessário passar o objeto FontMetrics da fonte no construtor para o
ColumnModel. Usamos a largura dos caracteres “0” e “M” como referência para a largura
dos campos, e tivemos o cuidado de garantir que as colunas coubessem seus títulos.
A Listagem 8 apresenta as modificações necessárias na classe ListaTarefas para
utilizar o novo ColumnModel; a Figura 5 mostra o resultado da execução da aplicação
modificada.

27
Ainda falta ajustar a formatação individual de cada célula, o que irá exigir a criação
de uma terceira classe auxiliar para o componente JTable.

Figura 4. Aplicação utilizando o novo TableModel

Figura 5. Visual com colunas customizadas

Listagem 7. Customização das colunas da tabela

package todo.visao;
import java.awt.*;
import javax.swing.table.*;

public class TarefasColumnModel extends DefaultTableColumnModel {


private TableColumn criaColuna(int columnIndex, int largura,
FontMetrics fm, boolean resizeable, String titulo)

{
int larguraTitulo = fm.stringWidth(titulo +” “);
if (largura < larguraTitulo)
largura = larguraTitulo;
TableColumn col = new TableColumn(columnIndex);
col.setCellRenderer(null);
col.setHeaderRenderer (null) ;
col.setHeaderValue(titulo);
col.setPreferredWidth(largura);
if (!resizeable) {
col.setMaxWidth(largura);
col.setMinWidth(largura);
}
col.setResizable(resizeable);
return col;
}
public TarefasColumnModel(FontMetrics fm) {
int digito = fm.stringWidth(“0”);
int letra - fm.stringWidth(“M”);
addColumn(criaColuna(0, 3 * digito, fm, false, “Prioridade”));
addColumn(criaColuna(1, 20 * letra, fm, true, “Descrição”));
addColumn(criaColuna(2, 3 * letra, fm, false, “Alarme?”));
addColumn(criaColuna(3, 10 * digito, fm, false, “Conclusão”));

28
}
}
Listagem 8. Vinculando o TarefasColumnModel ao JTable em ListaTarefas
package todo.visao;
// ... imports

public class ListaTarefas extends javax.swing.JFrame {


public void setListaTarefas(List tarefas) {
this.tarefas.setModel(new TarefasTableModel(tarefas));
public ListaTarefas() {
initComponents();
tarefas.setAutoCreateColumnsFromModel(false);
FontMetrics fm = tarefas.getFontMetrics(tarefas,getFont());
tarefas.setColumnModel(new TarefasColumnModel(fm));
}

// ... código gerado pelo editor visual do NetBeans


}
Renderizando células
O JTable é um componente genérico para exibição de dados numa grade, que pode
ser adaptado a duas situações distintas:
quando os dados são uniformes dentro de cada coluna (típico de aplicações de
banco de dados).
quando cada célula numa mesma coluna pode ser um tipo diferente (como numa
planilha eletrônica).
Nossa aplicação se enquadra na primeira situação. Em ambas as situações, a
formatação dos valores individuais será feita por um TableCellRenderer. Classes que
implementam esta interface podem ser vinculadas a uma coluna da tabela por meio do
ColumnModel (primeira situação) ou a um tipo específico de dados (segunda situação)3.
Optamos por definir um único renderizador de célula para as quatro, o TarefasCellRender
(Listagem 9). Nosso renderizador é simples, pois estende o DefaultTableCellRenderer
fornecido pelo Swing. Para utilizá-lo, basta mudar, em TarefasColumnModel, a linha:

col.setCellRenderer(null);
para:
col.setCellRenderer(new TarefasCellRenderer());
Quando a célula for desenhada, será acionado o TableCellRenderer configurado
para o tipo ou para a coluna. Esse renderizador deve retornar um JComponent que será
responsável pela formatação do valor, a nossa implementação, assim como a padrão do
Swing, utiliza um único JLabel para o desenho de todas as células. Desta forma um JTable
com milhares de células não irá criar o mesmo números de objetos, sendo eficiente em
consumo de memória e CPU.
Nosso objetivo é configurar a aparência das linhas como um todo e não apenas de
células individuais. Por isso é necessário primeiro referenciar o JTable que chama o
renderizado; depois acessamos o TableModel e obtemos a Tarefa correspondente à linha
da célula sendo desenhada.
O método corTarefa() utiliza os métodos isAtrasada(), isAlertaAtivo(),e
isConcluida() de Tarefa para selecionar uma cor de fundo diferente para cada linha. O
código de cores utilizado é o seguinte:
vermelho:tarefa atrasada, isto é, que já passou da sua data de conclusão

29
amarelo: tarefa em alerta, isto é, que foi configurada para gerar alertas alguns dias
antes da sua data de conclusão
azul: tarefa concluída
branco: tarefa que não está em nenhum dos outros estados.
Caso a célula esteja selecionada, é utilizada a cor cinza escura para o fundo da célula e o
código de cores é utilizado para a cor de texto.
Listagem 9. Renderizador para as células de uma Tarefa
package todo.visao;
// ... imports

private class TarefasCellRenderer extends DefaultYableCellRenderer{

public TarefasCellRenderer() { super();}

private Color corTarefa(Tarefa tarefa) {


if (tarefa.isConcluida())
return Color.CYAN;
else {
Date conclusao = tarefa.getDataConclusao();
Date hoje = new Date();
if (conclusao = null)
return Color.WHITE;
else if (tarefa.isAtrasada())
return Color. PINK;
else if (tarefa.isAlertaAtivo())
return Color.YELLOW;
else
return Color.WHITE;
}
}

private Object formata(Object value) {


if (value instanceof Date) {
Date data = (Date)value;
DateFormat df = DateFormat.getDateInstance();
return df.format(data);
}
else if (value instanceof Boolean)
return (Boolean)value ? “S” : “N”;
else
return value;
}

public Component getTableCellRendererComponent(


javax.swing.JTable table,
Object value, boolean isSelected, boolean hasFocus,
int row, int column)
{
value = formata(value);
JLabel label = (Jlabel)super.getTableCellRendererComponent(
table, value, isSelected, hasFocus, row, column);
if (column ! = 1)
label .setHorizontalAlignment(JLabel .CENTER);
Tarefa tarefa = ((TarefasTableModel)table.getModel())
.getValoresTarefa(row);
if (isSelected) {

30
label.setForeground(corTarefa(tarefa));
label.setBackground(Color.GRAY);
}
else {
label.setForeground(Color.BLACK);
label.setBackground(corTarefa(tarefa));
}
return label;
}
}

É recomendado que toda customização de cores num componente Swing


mude ao mesmo tempo a cor de texto e a de fundo, em ambos os estados
(normal e selecionado). Caso contrário, poderá haver uma combinação
ilegível, devido às cores definidas pelo tema dos sistema operacional, ou do
look-and-feel escolhido pelo usuário.

Figura 6. Aparência com TableCellRenderer customizado


A mesma classe TarefasCellRenderer é utilizada para renderizar todas as colunas.
Por isso foi criado o método formata(), que utiliza os valores “S” e “N” para colunas
booleanas e um DateFormat para colunas do tipo Date.
O resultado final pode ser visto na Figura 6. A Figura 7 apresenta a situação do
projeto com as duas classes adicionadas, o modelo de colunas e o renderizador de células.
Agora a listagem de tarefas está em sua forma final, podemos dar interatividade à
aplicação.

Eventos internos e externos


O próximo passo será permitir que sejam adicionadas e editadas tarefas – mas sem
ainda salvá-las num banco de dados. Para implementar esta funcionalidade, é importante
entender que nem todos os eventos devem ser tratados da mesma forma na aplicação.

Figura 7. Classes da aplicação ao fim da customização do JTable

31
Podemos classificar os eventos da interface com o usuário em duas categorias:
Eventos internos – São consumidos pelas classes de Visão e não é necessário
repassá-los ao Controlador. Eventos internos afetam apenas a aparência e o estado
das classes de Visão; eles não levam à execução de métodos do Modelo. Por
exemplo, ao selecionar uma tarefa na lista, devem ser habilitados os botões de
editar, remover e marcar como concluída. Quando não houver tarefa selecionada,
esses botões devem ser desabilitado, tudo sem passar pelo Controlador ou Modelo.
Eventos externos – Correspondem às operações realizadas pelo usuário na
aplicação, devendo, portanto, ser repassados às classes de Controlador. Estas, por
sua vez, respondem chamando métodos das classes de Modelo e depois comandando
a atualização dos dados exibidos na Visão. Por exemplo, a adição de uma nova
tarefa envolve a chamada ao método na classe do Modelo que realiza esta operação,
seguindo pela entrega de uma novas lista de tarefas para a classe de Visão
selecionada.
Para implementar os eventos internos, podemos utilizar os recursos do editor visual
do NetBeans. No IDE, um clique duplo em qualquer componente cria um método para o
tratamento do seu evento padrão (geralmente o evento “Action”) e abre o editor de código
para que possa programar o corpo do método. Já os eventos externos devem ser
encaminhados ao Controlador.
Eventos internos: seleção de linhas na tabela
Vamos iniciar pelos eventos internos, que são mais simples por envolverem código
em apenas uma classe. Vamos controlar o estado dos botões e menus da classe de Visão
ListaTarefas.
A operação de edição será habilitada apenas quando houver uma única tarefa
selecionada; já a marcação e a remoção podem atuar sobre múltiplas tarefas selecionadas,
portanto serão habilitadas mesmo havendo uma seleção múltipla. Já os botões de adicionar
tarefa, de exibir tarefas concluídas, e os de ordenação podem estar sempre habilitados.
Não será possível criar o código para o tratamento dos eventos de seleção através do
editor visual do NetBeans. O motivo é que os eventos não são gerados pelo componente
JTable, mas pelo objeto ListSelectionModel vinculado a ele – e este objeto não é
acessível pelo inspetor no editor visual. Então temos que escrever o código do listener para
o evento de seleção, incluindo a lógica que habilita ou desabilita os botões apropriados, e
não esquecendo de alternar também o estado dos itens de menus correspondentes. A
Listagem 10 apresenta as mudanças necessárias na classe ListaTarefas.
É fácil identificar outros eventos internos das duas classes de Visão. Por exemplo, em
EditaTarefa, a marcação do checkbox “GerarAlerta” deve habilitar o campo de texto
“diasAlerta”. Ou então, o botão “Cancela”, que simplesmente fecha (dispose()) o JDialog,
sem gerar nenhum outro evento para o controlador. Deixamos a codificação destes eventos
simples como exercício para o leitor.
Neste ponto, compile e execute a aplicação e verifique o seu funcionamento antes de
avançar.

Listagem 10. Modificações em ListaTarefas para habilitar e desabilitar os botões e menus


conforme a seleção na tabela
package todo.visao;
// ... imports

public class ListaTarefas extends javax.swing.JFrame {


private void habilitaEdicao(boolean habilitado) {
botaoEditar.setEnabled(habilitado);
menuEditar.setEnabled(habilitado);
}

32
// habilitaMarcacao() e habilitaRemocao()
// seguem o mesmo modelo de habilitaEdicao()

private ListSelectionListener selectionListener =


new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
habilitaEdicao(tarefas.getSelectedRowCount() ==1);
habilitaMarcacao(tarefas.getSelectedRowCount() >= 1);
habilitaRemocao(tarefas.getSelectedRowCount() >= 1);
}
};
public void setListaTarefas(List tarefas) {
this.tarefas.setModel(new TarefasTableModel(tarefas));
}

public ListaTarefas() {
initComponents();
tarefas.setAutoCreateColumnsFromModel(false);
FontMetrics fm = tarefas.getFontMetrics(tarefas.getFont());
tarefas.setColumnModel(new TarefasColumnModel(fm));
tarefas.getSelectionModel().addListSelectionListener(
selectionListener);
habilitaEdicao(false);
habilitaRemocao(false);
}

// ... código gerado pelo editor visual do NetBeans


}

Eventos externos: adicionando e editando tarefas


Para não poluir as classes de Visão com dezenas de métodos no estilo
“addEditarTarefaListener()”, “addAdicionarTarefaListener” etc. decidimos expor em cada
classe de Visão apenas o evento ActionEvent à sua classe controladora. A classe
ActionEvent inclui a propriedade actionCommand, que espelha a propriedade de mesmo
nome do componentes que gerou o evento. Dessa forma, o controlador pode saber
exatamente quando a operação foi requisitada pelo usuário, sem, ter que definir listeners
adicionais.
A Listagem 11 apresenta a classe ConsultaEditaTarefas modificada para tratar
apenas dos eventos de adicionar e editar tarefa. A Listagem 12 mostra as modificações
nas classes ListaTarefa para gerar o evento (lembre-se de configurar os
actionCommands no editor visual). A Listagem 13 apresenta as modificações no diálogo
EditaTerefas, de modo que os botões de Salvar e de Remover também gerem eventos
ActionEvent para o controlador.
Como o código de gerenciamento dos listeners e de geração de evento é o mesmo
para ambas as classes, ele foi movido para uma classe utilitária ActionSupport,
apresentada na Listagem 14. Observe que é o Controlador que sabe se o diálogo foi
instanciado para adição de uma nova tarefa, ou para a edição de uma nova tarefa existente.
Mas o diálogo necessita receber esta informação para desabilitar o botão de remover em
caso de uma tarefa nova.
Foram omitidas as listas das classes ModeloException e ValidacaoException,
ambas no pacote todo.modelo. Estas classes simplesmente sinalizam erros ocorridos
internamente nas classes de modelo; representam erros de “alto nível” da aplicação, em
vez de erros de “baixo nível” como IOException ou NumberFormatException. Note que

33
as áreas de mensagens das duas classes de Visão são utilizadas para exibir a mensagem de
erro fornecida pelas exceções vindas do Modelo.

Listagem 11. Classe controladora modificada para tratar da adição e edição de tarefas
package todo.controle;

//... imports

public class ConsultaEditaTarefas implements ActionListener


{
// ...atributos omitidos

public ConsultaEditaTarefas(ListaTarefas visao.


GerenciadorTarefas modelo)
{
this.visao = visao;
this.modelo = modelo;
visao.addActionListener(this);
listaTarefas();
}
public void listaTarefas() {
visao.setListaTarefas(modelo.listaTarefas());
public void actionPerformed(java.awt.event.ActionEvent e)
{
try {
if (e.getActionCommand().equals(“novaTarefa”)) {
editaTarefa(true);
}
else if (e.getActionCommand().equals(“editarTarefa”))
{
editaTarefa(false);
}
else if (e.getActionCommand().equals(“salvarTarefa”))
{
salvaTarefa();
}
else
visao.setMensagem(“Não implementado: [" + e.getActionCommand() + “J”,
true);
}
catch (Exception ex) {
visao.setMensagem(ex.getMessage(), true);
}
}

private void editaTarefa(boolean novaTarefa) {


editaTarefaDialog = new EditaTarefa(visao.true);
editaTarefaDialog.setNovaTarefa(novaTarefa);
if (novaTarefa)
editaTarefaDialog.setTarefa(new Tarefa( ));
else
editaTarefaDialog.setTarefa(visao.
getTarefaSelecionada());
editaTarefaDialog.addActionListener(this);
editaTarefaDialog.setVisible(true);
}

34
private void salvaTarefa() {
Tarefa tarefa = editaTarefaDialog.getTarefa();
try {
if (editaTarefaDialog.isNovaTarefa())
modelo.adicionaTarefa(tarefa);
else
modelo.editaTarefa(tarefa);
editaTarefaDialog.dispose();
editaTarefaDialog = null;
listaTarefas();
}
catch (ModeloException e) {
editaTarefaDialog.setMensagem(e.getMessage(), true);
}
}
}

Listagem 12. Modificações em ListaTarefa para gerar o evento Action para o Controlador

package todo.visao;
// ... imports

public class ListaTarefas extends javax.swing.JFrame {


private ActionSupport actionSupport =
new ActionSupport(this);
public void addActionListener(ActionListener listener) {
actionSupport.addActionListener(listener);
}

public void removeActionListener(ActionListener listener)


{
actionSupport.removeActionListener(listener);
}

public void setMensagem(String msg, boolean isErro) {


status.setText(msg);
if (isErro)
status.setForeground(Color.RED);
else
status.setForeground(Color.DARK_GRAY);
}

public void setListaTarefas(List tarefas) {


this.tarefas.setModel(new TarefasTableModel(tarefas));
}

public ListaTarefas() {
initComponents() ;
tarefas.setAutoCreateColumnsFromModel(false);
FontMetrics fm = tarefas.getFontMetrics(
tarefas. getFont());
tarefas.setColumnModel(new TarefasColumnModel(fm));
menuAdicionar.addActionListener(actionSupport);
menuEditar.addActionListener(actionSupport);
botaoAdicionar.addActionListener(
actionSupport) ;
botaoEditar.addActionListener(
actionSupport) ;

35
}
// ... Código gerado pelo NetBeans
}
O mesmo controlador responde aos eventos, tanto do JFrame ListaTarefas quanto
do JDialog EditaTarefa. Isto faz sentido porque o JDialog é apenas parte de um processo
iniciado pelo JFrame, e não uma classe de Visão independente. Assim também podemos
manter o diálogo aberto em caso de erros de validação dos dados digitados pelo usuário.
No final das contas, temos no código do controlador um grande switch, onde é feita
a decisão de qual método da classe de Modelo chamar baseado no valor do
actionCommand do evento. Numa aplicação maior, esse código poderia ser
parametrizado, como sugere o quadro “Frameworks MVC para aplicações gráficas”.
Mais uma vez estamos em condições de rodar a aplicação e testar a funcionalidade
recém-acrescentada. A forma final do projeto está na Figura 8. Estamos quase com a
aplicação pronta, exceto pelo fato de que as edições são perdidas quando ela é encerrada. A
solução para essa e outras questões será o assunto da próxima parte.

Figura 8. Classes da aplicação após o tratamento dos eventos de adicionar e editar tarefas.

Listagem 13. Modificações em EditaTarefa para gerar o evento Action para o controlador
package todo.visao;
// ... imports

public class EditaTarefa extends javax.swing.JDialog{

private Tarefa tarefa;


private boolean novaTarefa;

public void setNovaTarefa(boolean novaTarefa) {


this.novaTarefa - novaTarefa;
remover.setEnabled(!novaTarefa);
if (isNovaTarefa())
setMensagem(“Forneça os dados para a nova tarefa”. false);
else
setMensagem(“Alterando os dados da tarefa”. false);
}

public boolean isNovaTarefa() {


return novaTarefa;

36
public void setMensagem(String msg. boolean isErro) {
// ... igual ao correspondente em ListaTarefa
}

public void setTarefa(Tarefa tarefa) {


this.tarefa = tarefa;
descricao.setText(tarefa.getDescricao());
prioridade.setValue(tarefa.getPrioridade());
// ... demais atributos omitidos
}

public Tarefa getTarefa() {


tarefa.setDescricao(descricao.getText());
tarefa.setPrioridade(((Number)prioridade.getValue()),
intValue());
// ... demais atributos omitidos
return tarefa;
}

// actionSupport(), addActionListener e
// removeActionListener() iguais aos de ListaTarefas ...

public EditaTarefa(java.awn.Frame parent, boolean modal) {


super(parent, modal);
initComponents();
salvar.addActionListener(actionSupport);
remover.addActionListener(actionSupport);
}

// … código gerado pelo editor visual do NetBeans


}

Listagem 14. Classe utilitária ActionSupport

package todo.visao;
// .. imports

public class ActionSupport implements ActionListener {


private Window window;

public ActionSupport(Window window) {


this.window = window;
}
private List listeners = new ArrayList<
ActionListener>();

public void addActionListener(ActionListener listener) {


listeners.add(listener);
}

public void removeActionListener(ActionListener listener) {


listeners.remove (listeners) ;
}

public void fireActionEvent(ActionEvent e) {


Iterator it = listeners.iterator();
while (it.hasNext()) {

37
ActionListener listener = it.next();
listener.actionPerformed(new ActionEvent(window,
ActionEvent.ACTION_PERFORMED, e.getActionCommand()));
}
}

public void actionPerformed(ActionEvent e) {


fireActionEvent(e);
}
}

Conclusões
Neste artigo vimos como configurar um componente JTable do Swing, que possui
flexibilidade de customização sem igual em outros toolkits gráficos. Vimos também que a
plena exploração das capacidades desse componente exige a criação de novas classes em
vez de simplesmente definir propriedades no editor visual. E mostramos como tratar
eventos gerados pelos componentes gráficos, sem cair no “código espaguete” - mantendo a
separação de responsabilidades proposta pela arquitetura MVC.
O próximo artigo da série irá demostrar como filtrar o conteúdo da lista de tarefas e
como armazenar as tarefas num banco de dados, além de apresentar um componente para
a seleção visual de datas.
________________________________________________________
[1] – Os diagramas UML neste artigo foram criados com a aplicação Java.
[2] – Nesta artigo estamos usando Visão, Modelo e Controle praticamente como sinônimos
para os pacotes de mesmo nome; em aplicações mais complexas, esses pacotes seriam
quebrados em vários.livre ArgoUML (argouml.org).
[3] – Neste caso, que não é ilustrado pela aplicação de exemplo, o vínculo é configurado
por meio de um atributo do próprio JTable, um Map, onde as chaves são objetos Class
(como String.class, Data.class) e os valores são os objetos TableCellRenderer criados pelo
desenvolvedor.

Arquitetura MVC e Swing


O Swing foi arquitetado de acordo com o modelo MVC. Cada classe ou componente
visual que manipulamos (por exemplo um JButton) é na verdade a agregação de um
Controlador, que obtém dados de um Modelo, com uma Visão que é fornecida pelo look-
and-feel atual. Esta visão é chamada de “UI Delegate”, indicando que é ela que realmente
desenha o componente no vídeo. A Figura Q1 ilustra o uso da arquitetura MVC no Swing.
(Note que neste diagrama não estão representados nomes de classes ou interfaces reais,
mas apenas uma descrição da arquitetura que é aplicada individualmente a cada tipo de
componente.)
Nos casos mais simples, o programador não precisa fornecer a classe model para um
componente Swing, pois o próprio componente instancia sua classe model padrão (ex.:
DefaultTableModel) e oferece métodos utilitários para armazenar valores nessa instância.
Assim o programador fica com a impressão de estar lidando com uma única classe virtual, e
essa impressão é reforçada num editor visual como o do NetBeans.
Alguns tipos de models do Swing são compartilhados entre vários tipos diferentes
componentes. Por exemplo, uma barra de rolagem e um spinner podem utilizar o mesmo
modelo (que indica os limites superior e inferior, e os incrementos para o valor
armazenado). É possível também que um componente esteja associado ao mesmo tempo a
vários models diferentes, por exemplo um que representa os dados propriamente ditos e
outro que indica os elementos selecionados pelo usuário. É o que acontece com o JTable,
que tem um TableModel um ListSelectionModel e um ColumnModel.

38
Um erro comum, entretanto, é acreditar que as classes model do Swing devem ser
usadas como classes de Modelo de aplicação MVC. Não é o caso, pois estamos lidando com
níveis de abstração diferentes (a aplicação como um todo e o toolkit gráfico específico).
Caso as classes de Modelo da aplicação sejam construídas segundo os models do Swing elas
ficarão dependentes da Visão, ou seja, haverá uma independência grande e indesejada do
Modelo com a Visão.

Figura Q1. Visão geral da arquitetura MVC, conforme utilizada pelos componentes do
Swing

Frameworks MVC para aplicações gráficas


O leitor com experiencia em frameworks MVC para aplicações web, como o Struts,
irá logo imaginar que o grande switch no Controlador do nosso exemplo poderia ser
substituído por um arquivo de configuração. Esta é uma das idéias básicas em frameworks
par aplicações gráficas, como o NetBeans Platform (netbeans.org/products/platform). (O
NetBeans Platform é uma separação do código do Controlador MVC do NetBeans do restante
do IDE, de modo que ele possa ser utilizado em outros tipos de aplicações.)
Um exemplo popular de aplicação construída desta forma é o MC4J (mc4j.sf.net), um
console para gerenciamento de aplicações que suportam o padrão JMX. Vale a pena
conhecer e estudar o código dessa ferramenta open source.
Infelizmente, frameworks MVC para aplicações gráficas ainda não atingiram o
mesmo nível de popularidade que têm em aplicações web. Talvez isso seja por conta da
facilidade em construir aplicações simples num editor visual, sem se preocupar com a
estruturação do código, o que pode fazer com que considerem pequeno o ganho em
produtividade se fosse adotado um framework. Mas isso parece estar finalmente mudando,
com o crescimento de projetos relacionados, do Eclipse.org e da comunidade JavaDesktop
no portal java.net.

Links
http://java.sun.com/products/jfc/tsc/articles/architecture
Documento que descreve a arwquitetura do Swing e sua aplicação do padrãoMVC.
http://netbeans.org
Site oficial do Netbeans

39
Parte 3: Banco de Dados e Preferências dos Usuários

Nesta parte concluímos a aplicação, implementando suporte a preferências e


acesso a banco de dados, e fazendo ajustes finais

Neste artigo completamos aplicação de Lista de Tarefas (“Todo”), que iniciamos


nesta coluna na Edição 25 – um exemplo completo do desenvolvimento de uma aplicação
gráfica baseada no Swing, utilizando os recursos do IDE livre NetBeans.
O primeiro artigo desta série demonstrou como usar os recursos do editor visual do
NetBeans para prototipar a interface como o usuário. O segundo mostrou como customizar
a exibição de dados em JTable do Swing e como organizar o tratamento de eventos na
aplicação, de acordo com a arquitetura Model-View-Controller.
Este último artigo mostra como filtrar as informações exibidas na tabela e como ler e
gravar as informações em um banco de dados relacional.
Para manter a aplicação simples de instalar e distribuir, será utilizado o banco de
dados HSQLDB (apresentado na Edição 7). Dessa forma, a aplicação final será formada por
apenas dois pacotes jar: o Todo.jar gerado pelo NetBeans, contendo as classes da
aplicação; e o hsqldb.jar do HSQLDB.
O quadro “Configurando o projeto no NetBeans” descreve como configurar o IDE
para uso do HSQLDB durante o desenvolvimento da aplicação. E o leitor que preferir utilizar
um banco de dados diferente pode consultar o quadro “Experimentando com outros
bancos”.

Arquitetura da aplicação
No artigo anterior, chegamos a uma aplicação bastante funcional, que permitia
edição e consulta de tarefas armazenadas em memoria, mas sem ainda persistir os dados
em disco. Utilizamos a arquitetura MVC, pela qual as classes da aplicação devem atuar
apenas num dos seguintes papéis: gerenciamento dos dados (Modelo), visualização dos
dados (Visão), ou resposta às ações do usuário (Controle). Criamos um pacote para cada
parte: o diagrama de classes UML da Figura 1 representa as principais classes desses
pacotes.

Figura 1. Principais classes da aplicação de exemplo, desenvolvidas nas duas partes


desta série. As classes em azul-claro são neste artigo, e a classe GerenciadorTarefas é
praticamente reescrita.

No pacote todo.modelo, temos a classe Tarefa, construída com um VO (Value


Object, um “repositório de dados” com pouca ou nenhuma inteligência própria). Há também
a classe GerenciadorTarefas, cuja versão inicial tinha o objetivo de fazer uma “simulação”
da persistência mantendo os dados em memória, pois o foco estava nas classes de visão e
controle. Nesta edição vamos expandir a classe GerenciadorTarefas para conter o código
de persistência num banco de dados. Também será criada uma nova classe de modelo,

40
chamada Parametros, contendo os dados de configuração da aplicação – por exemplo, o
diretório onde são salvos os arquivos do bando de dados.
O pacote todo.visao contém duas janelas que foram prototipadas na primeira parte,
ListaTarefas (um JFrame) e EditaTarefas (um JDialog), além de várias classes
auxiliares. A Figura 2 reapresenta as duas janelas, para que o leitor que não tenha idéia da
aparência e funcionalidade da aplicação.
O pacote todo.controle contém uma única classe, ConsultaEditaTarefas, que
responde às ações do usuário para criar, editar ou listar tarefas. Essas classe delega as
ações em si para a classe de modelo GerenciadorTarefas e comanda a atualização das
classes de visão, quando necessário. Neste artigo será criada outra classe controladora,
chamada CriaAbreListaTarefas, responsável por criar novos bancos de dados existentes
em outras localizações. Ela também receberá as funcionalidades de “miscelânea” da
aplicação, por exemplo a exibição da caixa “Sobre”.

Figura 2. Janelas da aplicação Lista de Tarefas

Os três pacotes contêm ainda uma série de classes auxiliares não descritas aqui, por
exemplo, exceções customizadas ou modelos para o JTable do Swing. Elas não afetam a
arquitetura da aplicação e na maioria dos casos são utilizadas por classes fora dos
respectivos pacotes.
A Figura 3 apresenta as classes da aplicação, na visão de projetos do NetBeans. O
quadro “Todas as classes do exemplo” apresenta uma breve descrição do papel de cada
uma.

Figura 3. Todas as classes da aplicação final, na visão de projeto do NetBeans.

41
Acesso ao banco de dados
A nova versão da classe GerenciadorTarefas foi construída como um DAO. A idéia
é que as demais classes da aplicação não tenham conhecimento do uso de banco de dados
relacionais ou de outra tecnologia de armazenamento e recuperação de informações – elas
apenas chamam os métodos da classe DAO para ler e gravar objetos. Nosso DAO fornece
métodos como listaTarefas() e editaTarefa(), que serão chamados pelo controlador
apropriado (no caso, ConsultaEditaTarefas) de acordo com a operação solicitada pelo
usuário. A Listagem 1 fornece o código completo dessa classe, que será detalhado a
seguir.
A nova classe de modelo irá interagir com a classe Parametros para obter as
configurações de acesso ao banco de dados, que serão utilizadas em conecta() para criar
uma conexão ao HSQLDB. A conexão é mantida como variável de instância (atributo) da
classe DAO. É fornecido também o método desconecta(), para que a aplicação possa
fechar a conexão quando for encerrada.
O próprio HSQLDB cria automaticamente uma base de dados vazio no momento da
conexão, caso o arquivo referenciado na URL JDBC não exista. Como padrão, estamos
utilizando a base dB/todo (que corresponde ao arquivo dB/todo.script) no diretório pessoal
do usuário, que será o $HOME em sistemas Linux ou a pasta correspondente em sistemas
Windows. Para simplificar a utilização, o próprio DAO irá criar as tabelas na base de dados,
caso elas não existam. Isso é feito pelos métodos privativos existemTabelas() e
criaTabelas().
Visando simplificar a escrita dos métodos de consulta e alteração de dados
propriamente ditos, são fornecidos os métodos auxiliares finaliza(), executa(),
prepara(), consulta() e altera(), que encapsulam seqüências comuns chamadas JDBC.
Todos eles, exceto os dois últimos, poderiam ser movidos para uma superclasse abstrata
numa aplicação com vários DAOs. Os métodos consulta() e altera() seriam praticamente
iguais, variando apenas quanto aos nomes dos campos, em outras classes DAO de uma
aplicação maior.
O método finaliza() tem importância especial, pois temos que garantir que os
recursos do banco de dados sejam liberados o quanto antes, mesmo que ocorram exceções
durante o acesso. Todos os métodos de acesso ou alteração devem conter uma cláusula
finally que chama finaliza() (veja por exemplo o método executa()).
Os métodos específicos de acesso e alteração de dados – adicionaTarefa(),
editatarefa(), marcaComoConcluida(), removeTarefa(), listaTarefas() e
listaTarefasComAlarme() – são todos escritos chamando a finaliza() é garantia em caso
de erros.
Por fim, o DAO fornece o método validaTarefa(), que é chamado pelo controlador
antes de se inserir ou modificar uma tarefa. Isso permite que a operação seja abortada, e o
usuário informado de que houve erros na digitação dos atributos da tarefa.
Observe que os métodos da classe de todo.modelo, nunca retornam exceções de
baixo nível ao controlador, por exemplo SQLException ou IOException. São retornadas
subclasses de ModeloException, como a BancoDeDadosException. Estas classes, por
sua vez, encapsulam a exceção de baixo nível como “causa” (acessível pelo método
getCause() de Exception), de modo que a informação esteja disponível durante a
depuração da aplicação. Isso foi feito para que o controlador fique completamente isolado
de qualquer conhecimento relativo ao banco de dados ou outro mecanismo de persistência.
O mesmo estilo de programação poderia ser utilizado caso fosse empregada outra forma de
persistência, por exemplo arquivos XML.

42
Listagem 1. Classe de modelo GerenciadorTarefas

package todo.modelo;

import java.io.*;
import java.sql .*;
import java.text.*;
import java.util.List;
import java.util.ArrayList;

public class GerenciadorTarefas {


private Parametros params;
private Connection con;
private Statement stmt;
private ResultSet rs;

public GerenciadorTarefas(Parametros params) throws


BancoDeDadosException
{
this.params = params;
conecta();
}

public void reconecta(String database) throws


BancoDeDadosException
{
desconecta();
params.setDatabase(database);
conecta();
}

private void conecta() throws BancoDeDadosException {


try {
Class.forName(params.getJdbcDriver());
con = DriverManager.getConnection(params.getJdbcUrl().
"sa", "");
if (!existemTabelasC)
criaTabelas() ;
}
catch (BancoDeDadosException e) {
throw new BancoDeDadosException(
"Não foi possível criar as tabelas no banco de dados",
e.getCause());
}
catch (ClassNotFoundException e) {
throw new BancoDeDadosException(
"Não foi possível carregar o driver do banco de da
dos" , e);
}
catch (SQLException e) {
throw new BancoDeDadosException(
Não foi possível conectar ao banco de dados" , e);
}
}
private boolean existemTabelas() {
try {
String sql = "SELECT COUNT(*) FROM todo";
stmt = con.createStatement();
rs = stmt.executeQuery(sql);

43
return true;
}
catch (SQLException e) {
return false;
}
finally {
finaliza();
}
}

private void criaTabelas() throws BancoDeDadosException {


executa ("CREATE TABLE todo (" +
"id IDENTITY. " +
"descricao VARCHAR(100), " +
"prioridade INTEGER, " +
"concluida BOOLEAN, " +
"dataConclusao DATE, " +
"alerta BOOLEAN, " +
"diasAlerta INTEGER, " +
"observacoes VARCHAR(250) " +
")");
}

public void desconecta()


try {
if (con != null)
con.close();
con = null ;
}
catch (SOLException e) {/* ignora a exceção */}
}
private void finaliza() {
try {
if (rs != null)
rs.close() ;
rs = null;
if (stmt != null)
stmt.close();
stmt = null;
}
catch (SOLException e) {/* ignora a exceção*/}
}

private void executa(String sql) throws BancoDeDadosException{


try {
stmt = con.createStatement();
stmt.executeUpdate(sql );
}
catch (SQLException e) {
throw new BancoDeDadosException(
"Não foi possível alterar o banco de dados", e);
}
finally (finaliza();}
}

private PreparedStatement prepara(String sql)


throws SQLException
}
try {

44
PreparedStatement pst = con.prepareStatement(sql);
stmt = pst;
return pst;
}
finally {finaliza();}
}

private List<Tarefa> consulta(String where, String orderBy)


throws BancoDeDadosException
{
List<Tarefa> resultado = new ArrayList<Tarefa>();
try {
String sql = "SELECT id, descricao, prioridade, concluida, " +
"dataConclusao, alerta, diasAlerta, observacoes FROM todo “;
if (where != null)
sql += "WHERE" + where + " ";
if (orderBy != null)
sql += "ORDER BY " + orderBy;

stmt = con.createStatement();
rs = stmt. executeOuery(sql);
while (rs.next()) {
Tarefa tarefa = new Tarefa();
tarefa.setId(rs.getInt(1));
tarefa.setDescricao(rs.getString(2));
tarefa.setPrioridade(rs.getInt(3));
tarefa.setConcluida(rs.getBoolean(4));
tarefa.setDataConclusao(rs.getDate(5));
tarefa.setGerarAletraCrs.getBoolean(6));
tarefa.setDiasAlerta(rs.getInt(7));
tarefa.setObservacoes(rs.getString(8));
resultado.add(tarefa);
}
}
catch (SOLException e) {
throw new BancoDeDadosException{
"Não foi possível consultar o banco de dados", e);
}
finally {finaliza(); }
return resultado;
}

private void altera(String sql, Tarefa tarefa)


throws BancoDeDadosException
{
try {
PreparedStatement pst = con.prepareStatement(sql);
stmt = pst;
pst.setString(1, tarefa.getDescricao());
pst.setlnt(2, tarefa.getPrioridade());
pst.setBoolean(3, tarefa.isConcluida());

if (tarefa.getDataConclusao() == null) {
pst. setDate(4, null);
}
else {
pst.setDate(4,
new Date(tarefa.getDataConclusao().getTime()));
}

45
pst.setBoolean(5, tarefa.isGerarAletra());
pst.setlnt(6, tarefa.getDiasAlerta());
pst.setString(7, tarefa.getObservacoes());
pst.executeUpdate();
}
catch (SQLException e) {
throw new BancoDeDadosException(
"Não foi possível alterar o banco de dados", e);
}
finally { finaliza(); }
}
public List<Tarefa> listaTarefas(boolean prioridadeOuData)
throws BancoDeDadosException
{
return consultaCnull, prioridadeOuData ?
"prioridade, dataConclusao, descricao"
"dataConclusao, prioridade, descricao");
}
public List<Tarefa> listaTarefasComAlarme()
throws ModeloException
{
return consulta("alerta = true AND"
+ "datediff( 'dd' ,curtime().dataConclusao)<=diasAlerta".
"dataConclusao, prioridade. descricao");
}
public void adicionaTarefa(Tarefa tarefa)
throws ValidacaoException, BancoDeDadosException
{
validaTarefa(tarefa);
String sql = "INSERT INTO todo (" +
"descricao, prioridade, concluida, dataConclusao, alerta," +
"diasAlerta, observacoes) VALUES (?, ?, ?, ?, ?, ? n";
altera(sql, tarefa);
}

public void editaTarefa(Tarefa tarefa)


throws ValidacaoException, BancoDeDadosException
{
String sql - "UPDATE todo SET" +
"descricao - ?, prioridade = ?, concluida = ?,
dataConclusao - ?, " +
"alerta = ?, diasAlerta = ?, observacoes = ? " +
"WHERE id - " + tarefa.getld();
altera(sql, tarefa);
}
public void marcaComoConcluida(int id, boolean concluida)
throws BancoDeDadosException
{
executa (“UPDATE todo SET concluida = “ + concluida + “ “ +
“Where id = “ + id);
}
public void removeTarefa(int id) throws BancoDeDadosException{
executa("DELETE FROM todo WHERE id = " + id);
}
private boolean stringVazia(String str) {
return str == null l l str.trim().length() == 0;
}
private void validaTarefa(Tarefa tarefa)

46
throws ValidacaoException
{
if (stringVazia(tarefa.getDescricao()))
throw new ValidacaoException(
"Deve ser fornecida uma descri,ao para a tarefa");
}
}

Banco de dados e a arquitetura MVC


Programadores acostumados a seguir uma lógica seqüencial nos métodos de
tratamento de eventos de ambientes RAD, podem ficar um pouco perdidos com a divisão de
responsabilidades imposta pela arquitetura MVC. Para tornar as coisas mais claras, a Figura
4 apresenta um diagrama de seqüência UML para a operação de “adicionar tarefa” da
aplicação.
O processo é disparado pelo usuário que clica no botão da barra de ferramentas (ou
no item de menu correspondente) da visão ListaTarefas. Isso gera um evento, que é
capturado pelo controlador ConsultaEditaTarefas. O controlador então exibe o dialogo de
edição, que é outra visão (EditaTarefas), e o controle retorna para o usuário.

Figura 4. Diagramas de seqüência para a criação de uma nova tarefa.

Depois de digitar os dados, o usuário clica no botão Salvar, que provoca o envio de
um novo evento, também capturado pelo controlador. Em resposta a este segundo evento,
o controlador chama o método apropriado da classe DAO, que pode ser adicionaTarefa()
ou editaTarefa(). Se houver alguma exceção, o controlador exibe a mensagem de erros na
área reservada para isso no próprio diálogo de edição. Se não houver nenhuma exceção, ele
fecha o diálogo e atualiza a visão.
Caso o usuário cancele o diálogo de edição, não será gerado nenhum evento para o
controlador, e o diálogo será simplesmente descartado.
Algumas operações, como remover tarefas e marcar tarefas como concluídas atuam
sobre uma seleção contendo múltiplas tarefas. Neste caso, o controlador simplesmente
chama o método removerTarefa() ou maracrComoConcluida() do modelo num loop
for. Para mais detalhes, veja a Listagem 2. Nela está o código do controlador
ConsultaEditaTarefas, que tem poucas alterações com relação à versão do artigo
anterior.

47
Listagem 2. Classe controladora ConsultaEditaTarefas

package todo.controle;

import java.util.*;
import java.text.DateFormat;
import java.awt.Cursor;
import java.awt.event.*;
import javax.swing.*;
import todo.visao.*;
import todo.modelo.*;

public class ConsultaEditaTarefas implements ActionListener {


private ListaTarefas visao;
private ConsultaEditaTarefas controle;
private GerenciadorTarefas modelo;

public ConsultaEditaTarefas(ListaTarefas visao,


GerenciadorTarefas modelo) {
this.visao = visao;
this.modelo = modelo;
visao.addActionListener(this);
try {
listaTarefas();
}
catch (BancoDeDadosException e) {
visao.setMensagem(e.getMessage(), true);
}
}
public void listaTarefas() throws BancoDeDadosException {
visao.setListaTarefas(modelo.listaTarefas(true));
}

public void actionPerformed(java.awt.event.ActionEvent e) {


visao.setCursor(new Cursor(Cursor.WAIT_CURSOR));
try {
if (false)
; // faz nada
else if (e.getActionCommand().equals("novaTarefa")) {
editaTarefa(true);
}
else if (e.getActionCommand().equals("editarTarefa")) {
editaTarefa(false);
}
else if (e.getActionCommand().equals("salvarTarefa")) {
salvaTarefa() ;
}
else if (e.getActionCommand().equals("marcarTarefas")) {
marcaTarefas() ;
}
else if (e.getActionCommand().equals("removerTarefas")) {
removeTarefas();
}
else if (e.getActionCommand().equals("verAlertas")) {
exibeAlertas() ;
}
}

48
catch (Exception ex) {
visao.setMensagem(ex.getMessage(), true);
}
visao.setCursor(null);
}

private void editaTarefa(boolean novaTarefa) {


editaTarefaDialog = new EditaTarefa(visao, true);
editaTarefaDialog.setNovaTarefa(novaTarefa);
if (novaTarefa)
editaTarefaDialog.setTarefa(new Tarefa());
else
editaTarefaDialog.setTarefa(visao.getTarefaSelecionada());
editaTarefaDialog.addActionListener(this);
editaTarefaDialog.setVisible(true);
}
private void salvaTarefa() {
Tarefa tarefa = editaTarefaDialog.getTarefa();
try {
if (editaTarefaDialog.isNovaTarefa())
modelo.adicionaTarefa(tarefa);
else
modelo.editaTarefa(tarefa);
editaTarefaDialog.dispose();
editaTarefaDialog = null;
listaTarefas ();
}
catch (ModeloException e) {
editaTarefaDialog.setMensagem(e.getMessage(), true);
}
}

private void marcaTarefas() {


Tarefa[] tarefas = visao.getTarefasSelecionadas();
try {
for (Tarefa tarefa : tarefas) {
modelo.marcaComoConcluida(
tarefa.getld(),!tarefa.isConcluida());
}
listaTarefas();
}
catch (ModeloException e) {
editaTarefaDialog.setMensagem(e.getMessage(), true);
}
}

private void removeTarefas() {


Tarefa[] tarefas = visao.getTarefasSelecionadas();
int removidas = 0;
try {
for (Tarefa tarefa : tarefas) {
int resposta = JOptionPane.showConfirmDialog(visao,
"Tem certeza de que deseja remover"
+ " a tarefa\n[" + tarefa.getDescricao() + "J ?",
"Remover Tarefas",
JOptionPane.YES_NO_OPTION);
if (resposta == JOptionPane.YES_OPTION) {
modelo.removeTarefa(tarefa.getld());
removidas++;

49
}
}
if (removidas > 0)
listaTarefas();
}
catch (ModeloException e) {
editaTarefaDialog.setMensagem(e.getMessage(), true);
}
}

public void exibeAlertas() {


try {
List<Tarefa> tarefas = modelo.listaTarefasComAlarme();
for(Tarefa tarefa : tarefas) {
DateFormat df = DateFormat.getDatelnstance();
JOptionPane.showMessageDialog(visao,
"A seguinte tarefa está a menos de "
+ tarefa.getDiasAlerta()
+ " dia(s) da sua data de conclusão:\n"
+ df.format(tarefa.getDataConclusao()) + "\n"
+ "[" + tarefa.getDescricao() + "J",
"Alerta", JOptionPane.INFORMATION_MESSAGE);
}
if (tarefas.size() == 0) {
visao.setMensagem(
"Não há nenhuma tarefa em alerta no momento.",false);
}
}
catch (ModeloException e) {
editaTarefaDialog.setMensagem(e.getMessage(), true);
}
}
}

Filtragem de informações na tabela


Agora que nossa aplicação mantém a lista de tarefas entre uma execução e outra, a
utilização se torna muito mais interessante, mas o leitor poderá sentir a falta de recursos
para filtrar as tarefas. O protótipo criado na primeira parte prevê um único filtro para a lista
de tarefas, que é exibir (ou não) as tarefas marcadas como concluídas, e que caso sejam
exibidas serão apresentadas em azul-claro. Além deste filtro, foi prevista a ordenação das
tarefas por prioridade ou por data de conclusão, ambas em ordem crescente.
Há duas estratégias básicas para fazer a filtragem e a ordenação: pela própria visão,
sem afetar o controlador ou o modelo; ou pelo modelo, de modo a aproveitar os recursos do
banco de dados.
Filtrar na visão simplifica o modelo (com o custo de deixar a visão mais complexa),
mas faz sentido conceitualmente, pois estamos falando apenas de como as informações são
apresentadas para o usuário, e não de regras de validação ou consistência de dados. Já
filtrar no modelo, pode ser necessário caso o volume de dados seja grande, ou os critérios
de filtragem, muito complexos. Também pode ser mais fácil de programar, pois o SQL
permite escrever código de consulta e alteração de dados de forma mais sucinta do que em
Java.
No nosso caso da Lista de Tarefas, espera-se que nunca haja um volume de dados
muito grande; por isso a lista é mantida inteiramente em memória (dentro da classe
visao.TarefasTableModel). Nossas operações de filtragem e ordenação são facilmente
escritas em Java, utilizando a API padrão de coleções, e o usuário tem retorno quase
instantâneo – coisa difícil de se obter com um banco de dados.

50
Adotamos então a estratégia de fazer a filtragem na visão. Desse modo os eventos
do menu Opções são capturados e tratados pela própria classe de visão ListaTarefas, em
vez de serem repassados para o controlador.
O código que chama as operações de ordenação e filtragem é simples e poderia ter
sido escrito diretamente no método de tratamento de eventos gerado pelo NetBeans (com
um clique duplo sobre os botões e itens de menus) – mas como temos a mesma operação
sendo realizada por menus e botões, é mais conveniente criar métodos auxiliares. Assim
garantimos que o status do menu e da barra de ferramentas esteja consistente e evitamos
repetição de código, o que é sempre uma boa idéia, por menor que seja o código duplicado.
Criamos os métodos ordenaPorPrioridade() e mostraConcluidas(), chamados pelos
métodos de tratamento de eventos botaoOrdenarActionPerformed(),
botaoMostrarActionPerformed(), menuMostrarActionPerformed(),
menuOrdenarPrioridadesActionPerformed() e menuOrdenarDatasActionPerformed(), que são
gerados pelo NetBeans para os eventos dos itens de menu.
A ordenação e filtragem em si é realizada pela classe TarefasTableModel, que já
era a responsável por fornecer e formatar os dados para o JTable contendo as tarefas
listadas. As modificações em TarefasTableModel em relação ao artigo anterior são
indicadas em negrito na Listagem 3.

Listagem 3. Modificações na classe TarefasTableModel

package todo.visao;

import java.util.*;
import java.text.*;
import javax.swing.table.*;
import todo.modelo.*;

public class TarefasTableModel extends AbstractTableModel {


private List<Tarefa> tarefas;
private List<Tarefa> tarefasFiltradas;
private boolean mostrarConcluidas = true;
private boolean ordenarPorPrioridade = true;
private DateFormat df = DateFormat.getDatelnstance(
DateFormat.SHORT);

public TarefasTableModel (List<Tarefa> tarefas) {


this.tarefas = tarefas;
filtraTarefas() ;
}

public Object getValueAt(int rowlndex, int columnlndex) {


Tarefa umaTarefa = tarefasFiltradas.get(rowlndex);
switch(columnlndex) {
case 0: return umaTarefa.getPrioridade();
case 1: return umaTarefa.getDescricao();
case 2: return umaTarefa.isGerarAletra();
case 3: return umaTarefa.getDataConclusao();
}
return null;
}

public int getRowCount() {


return tarefasFiltradas.size();
}

public int getColumnCount() {

51
return 4;
}

public Tarefa getValoresTarefa(int rowlndex) {


if (rowlndex > tarefasFiltradas.size() )
return null;
else
return tarefasFiltradas.get(rowlndex);
}

public boolean isMostrarConcluidas() {


return mostarConcluidas;
}
public void setMostrarConcluidas(boolean mostrarConcluidas) {
this.mostrarConcluidas = mostrarConcluidas;
filtraTarefas();
}

public boolean isOrdenarPorPrioridade() {


return ordenarPorPrioridade;
}

public void setOrdenarPorPrioridade(


boolean ordenarPorPrioridade)
{
this.ordenarPorPrioridade = ordenarPorPrioridade;
filtrarTarefas();
}

private void filtraTarefas() {


tarefasFiltradas = new ArrayList<Tarefa>();
for (Tarefa tarefa : tarefas) {
if (!isMostrarConcluidas() && tarefa.isConcluida())
continue;

tarefasFiltradas.add(tarefa);
}
if (! isOrdenarPorPrioridade())
Collections.sort(tarefasFiltradas,
new Comparator<Tarefa)() {
public int compare(Tarefa t1, Tarefa t2) {
if (t1.getDataConclusao().equals(t2.getDataConclusao())) {
if (t1.getPrioridade() == t2.getPrioridade())
return t1.getDescricao().compareTo(t2.getDescricao());
else
return t1.getPrioridade()>t2.getPrioridade() 1:-1;
}
else
return t1.getDataConclusao().compareTo(
t2.getDataConclusao());
}
});
fireTableDataChanged();
}
}

52
Criando e abrindo listas
Estamos com quase toda a funcionalidade da nossa aplicação pronta. Faltam apenas
dois itens do menu Arquivo: um cria uma nova lista de tarefas armazenada em outro
arquivo que não o ${user.home}/dB/todo; outro abre listas de tarefas armazenadas em
outros arquivos.
Este é um caso claro de preferências dos usuários. Uma aplicação mais complexa
certamente iria armazenar outras preferências (deixamos como exercício armazenar as
seleções do menu Opções).
Como as preferências também devem ser informações persistentes, criamos uma
classe de modelo para lidar com elas. Daí a classe Parametros já citada. Ela fornece
métodos get e set para preferências individuais e é construída utilizando a Preferences API
(para mais sobre essa API, veja o artigo de Paloma Sol na Edição 10).
A classe Parametros é bastante simples, como vemos na Listagem 4. Observe que
a API de preferências exige que a aplicação indique os valores padrão para cada
preferência, pois apenas valores diferentes serão efetivamente armazenados. Para isso são
definidas várias constantes no início da classe. Um caso especial é o valor para a
preferência chamada database, gerado dinamicamente a partir do diretório pessoal do
usuário.
Chegamos ao ponto em que é necessário acrescentar um segundo controlador à
aplicação, a classe CriaAbreListaTareafas. Sua função é lidar justamente com as opções
do menu Arquivo, visto que elas não têm relação direta com o ciclo criar-editar-remover-
consultar para as tarefas. Visando simplificar a aplicação, este controlador também lida com
outro item de menu, ainda não coberto, o Ajuda/Sobre.
Não há necessidade de criar classes de visao auxiliares para estas operações, pois as
APIs JOptionPane e JFileChooser são sufientes para esta aplicação. Seria possível definir
visualmente no NetBeans as caixas “Sobre” e “Abrir arquivo”, mas no nosso caso é
desnecessário, pois apenas configuramos propriedades como o título dos diálogos padrão,
sem acrescentar novos componentes. Acaba portanto sendo bem mais simples chamar
diretamente as APIs. A Figura 5 apresenta a aparência dos diálogos “Sobre” e “Abrir Lista
de Tarefas”. O diálogo “Criar Lista de Tarefas” é similar a este último.

Figura 5. Caixa Sobre e diálogos de Abrir e Nova Lista de Tarefas.


O código do novo controlador é mostrado na Listagem 5. Para abrir e salvar tarefas
foi usado o componente JFileChooser do Swing. Este componente utiliza um objeto
FileFilter para determinar quais arquivos devem ser exibidos; por isso declaramos e
instanciamos uma classe aninhada, armazenando o objeto no atributo hsqlDatabases da
classe CriaAbreListaTarefas:
private FileFilter hsqlDatabases = newFileFilter(){...}

Esse objeto da classe aninhada fornece o filtro de arquivos a ser passado para os
diálogos de abrir e salvar listas de tarefas, garantindo que sejam exibidos apenas pastas e
arquivos cujo nome contenham a extensão .script do HSQLDB.

53
Listagem 4. Classe de modelo Parametros

package todo.modelo;

import java.io.File;
import java.util.prefs.*;

public class Parametros {


private static final String jdbcDriver = "org.hsqldb.jdbcDriver”;
private stafic final String defaultUrl = "jdbc:hsqldb:";
private static final String defaultDatabase = "db/todo";

private Preferences prefs =


Preferences.userNodeForPackage(Parametros.class);

public String getDatabase() {


//return prefs.get ("database", System.getProperty(
//"user.dir")
return prefs.get("database", System.getProperty("user.home”)
+ File.separator + defaultDatabase);
}

public void setDatabase(String database) {


prefs.put("database", database);
}

public String getJdbcUrl() {


return defaultUrl + getDatabase();
}

public String getJdbcDriver() {


return jdbcDriver;
}
}

Listagem 5. Classe de controladora CriaAbreListaTarefas

package todo.controle;

import java.io.File;
import java.awt.Cursor;
import java.awt.event.*;
import java.io.IOException;
import javax.swing.*;
import javax.swing.filechooser.*;

import todo.modelo.*;
import todo.visao.ListaTarefas;

public class CriaAbreListaTarefas implements ActionListener {


private ListaTarefas visao;
private GerenciadorTarefas modele;
private Parametros params;

public CriaAbreListaTarefasCListaTarefas visao,


GerenciadorTarefas modelo, Parametros params)
{
this.visao = visao;
this.modelo = modelo;

54
this.params = params;
visao.addActionListener(this);
visao.addWindowListener(fechaBanco);
}

private WindowListener fechaBanco = new WindowAdapter() {


public void windowClosed(WindowEvent e) {
modelo.desconecta();
}
public void windowClosing(WindowEvent e) {
modelo.desconecta();
}
};

public void actionPerformed(java.awt.event.ActionEvent e) {


visao.setCursor(new Cursor(Cursor.WAIT_CURSOR));
try {
if (false)
; // faz nada
else if (e.getActionCommand().equals("criarListaTarefas")) {
novaListaTarefas();
}
else if (e.getActionCommand().equals("abrirListaTarefas")) {
abreListaTarefas();
}
else if (e.getActionCommand().equals("sobre"))
sobre() ;
}
}
catch (Exception ex) {
visao.setMensagem(ex.getMessage(). true);
}
visao.setCursor(null);
}

private FileFilter hsqlDatabases = new FileFilter() {


public String getDescription() {
return "Listas de Tarefas - hsqldb (*.script)";
}
public boolean accept(File f) {
if (f.isDirectory())
return true;
else if (f.getName().endsWith(".script"))
return true;
else
return false;
}
};

private String criaOuAbreBanco(


File database) throws BancoDeDadosException {
String arq = database.getAbsolutePath();
if (arq.startsWith("file:"))
arq = arq.substring(5);
if (arq.endsWith(".script"))
arq = arq.substring(O, arq.length() - 7);

modelo.reconecta(arq);
visao.setListaTarefas(modelo.listaTarefas(true));

55
return arq;
}

public void abreListaTarefas(String bd) throws


BancoDeDadosException
{
try {
String arq = criaOuAbreBanco(new File(bd));
visao.setMensagem("Aberta lista de tarefas; "+arq.false);
}
catch (BancoDeDadosException e) {
visao.setMensagem(
"Nao foi possivel abrir a Lista de Tarefas", true);
}
}

private void novaListaTarefas() {


JFileChooser dlg = new JFileChooser();
dlg.setDialogTitle("Nova Lista de Tarefas");
dlg.setFileFilter(hsqlDatabases);
File dir = new File(params.getDatabase()).getparentFile();
dlg.setCurrentDirectory(dir);
if (dlg.showSaveDialog(visao)==JFileChooser.APPROVE_OPTION){
try {
String arq = criaOuAbreBanco(dlg.getSelectedFile());
visao.setMensagem("Criada lista de tarefas; "
+ arq, false);
}
catch (BancoDeDadosException e)
visao.setMensagemC
"Não foi possível criar a Lista de Tarefas". true);
}
}
}

private void abreListaTarefas() {


JFileChooser dig = new JFileChooser();
dlg.setDialogTitle("Abrir Lista de Tarefas");
dlg.setFileFilter(hsqlDatabases);
File dir = new File(params.getDatabase()).getParentFile();
dlg.setCurrentDirectory(dir);
if (dlg.showOpenDialog(visao)==JFileChooser.APPROVE_OPTION){
try {
String arq = criaOuAbreBanco(dlg.getSelectedFile());
visao.setMensagem("Aberta lista de tarefas: "
+ arq, false);
}
catch (BancoDeDadosException e) {
visao.setMensagem(
"Nao foi possivel abrir a Lista de Tarefas", true);
}
}
}

private void sobre() {


JOptionPane.showMessageDialog(visao,
"Todo - Lista de Tarefas\nVersao 1.0\n\n"
+ "Revista Java Magazine 27",
"Sobre Todo",

56
JOptionPane.INFORMATION_MESSAGE);
}
}

Alertas e linha de comando


Finalmente chegamos à última funcionalidade a ser implementada: a exibição de
alertas para tarefas próximas das datas de conclusão. Esta operação é representada pelo
ícone da barra de ferramentas, e também é executada no início da aplicação para chamar a
atenção do usuário.
A relação de tarefas com alertas ativos é gerada por listaTarefasComAlertas() do
DAO GerenciadorTarefas, e é exibida por exibeAlertas() do controlador
ConsultaEditaTarefas. Novamente por simplicidade, optamos por utilizar um
JOptionPane do Swing, em vez de criar outra classe de visão. Um exemplo desta caixa é
apresentado na Figura 6.

Figura 6. Alerta avisando sobre uma tarefa próxima da sua data de conclusão

A Listagem 5 exibe a classe todo.Main alterada para prever a funcionalidade dos


alertas. Ela instancia as classes de Modelo, Controlador e Visão e os conecta por meio dos
seus construtores. Em seguida esta classe comanda a verificação dos alertas, utilizando o
método já apresentado no controlador ConsultaEditaTarefas.
A classe Main também verifica se algum argumento foi fornecido na linha de
comando. Sendo fornecidos vários argumentos, a aplicação exibe uma mensagem de erro
no console. Caso tenha sido fornecido um único argumento, ele é considerado como o nome
do arquivo contendo uma lista de tarefas e é passado para o controlador
CriaAbreListaTarefas, que comanda a abertura do arquivo.
Não sendo fornecido nenhum argumento de linha de comando, é utilizado o bando de
dados padrão do usuário, de acordo com suas preferências.
Vale destacar a importância do suporte à linha de comando em uma aplicação
gráfica. Na nossa aplicação, por exemplo, isso permite que seja criado um atalho/lançador
na área de trabalho do usuário para abrir diretamente uma lista de tarefas especifica; ou
que uma lista de tarefas seja aberta com um clique duplo, no gerenciador de arquivos do
seu SO.

Empacotando para os usuários


Invocado o comando Build/Clean and Build Main Project no NetBeans, podemos gerar
o pacote dist/Todo.jar, um jar executável para distribuição. Entretanto, como adicionamos o
suporte a banco de dados, este pacote não será mais suficiente para a execução da
aplicação.
Uma estratégia de distribuição muito utilizada nesse caso é fornecer um único
arquivo .zip contendo o pacote jar executável e os jars das bibliotecas utilizadas (ou seja, o
hsqldb.jar). Com isso, ao descompactar o .zip de distribuição da aplicação, todos os jars
ficarão nem mesmo diretório.
Não será necessário criar um executável nativo, ou um script (.bat, .cmd, .sh etc.)
para configurar o classpath da aplicação; basta incluir dentro do jar executável uma
referência aos pacotes de bibliotecas necessários.

57
Isso é feito modificando-se o arquivo de manifesto do pacote jar. Para configurar o
manifesto, entre na visão de arquivos do NetBeans e localize, na raiz do projeto, o arquivo
MANIFEST.MF (veja a Figura 7).

Figura 7. Arquivo de manifesto do jar executável na visão de arquivos do projeto do


NetBeans
Abra este arquivo, que será exibido no editor de texto simples do NetBeans, e
acrescente a linha:
Class-path:hsqldb.jar
Então reconstrua (menu Build) o pacote da aplicação, e copie tanto esse pacote
quanto o hsqldb.jar para uma pasta qualquer do sistema. Em seguida, execute a aplicação
no prompt de comandos:
java-jar/caminho/para/aplicacao/Todo.jar
Veja como agora a aplicação é iniciada corretamente. O mesmo comando pode ser
utilizado num atalho/lançador na área de trabalho do usuário.
Conclusões
Encerramos aqui nossa série sobre desenvolvimento Java/Swing, com o NetBeans.
Outras pequenas modificações feitas na versão final da aplicação, disponível para download,
são comentadas no quadro “Ajustes diversos”, onde também são fornecidas algumas dicas,
por exemplo como detectar um clique duplo.
Se você está com a sensação de que falamos muito das APIs do Java e pouco do
NetBeans em si, na verdade esta é uma das grandes vantagens do Java sobre outras
plataformas: o desenvolvedor não fica dependente do IDE utilizado. Exceto pela
prototipação visual dos formulários, todo o projeto poderia ser modificado, recompilado e
testado em outros IDEs, como o Eclipse. Ou mesmo sem nenhum IDE, apenas usando as
ferramentas de linha de comando do JSDK e um editor de textos.
Por outro lado, demonstramos nesta série como o NetBeans oferece recursos
suficientes para que o desenvolvedor considere o seu uso em substituição a outros IDEs
Java no mercado. Não apenas as facilidades visuais, mas também o suporte ao Ant, a
estrutura de projetos, recursos de refatoração de código, depurador visual e auto-completar
código, além da interface flexível colocam o NetBeans em pé de igualdade com os melhores
produtos.

LINKS:
netbeans.org
Site oficial do NetBeans

hsqldb.sf.net
Site oficial do banco de dados HSQLDB

javamagazine.com.br/downloads/jm27/jm27-apcompleta-p3.zip

58
Fernando Lozano: (fernando@lozano.eti.br,www.lozano.eti.br) é consultor
independente, atuando há mais de dez anos em projetos de integração de redes,
desenvolvimento de sistemas e tuning de banco de dados. É também conselheiro do Linux
Professional Institute do Brasil e autor do livro “Java em GNU/Linux”, além de líder da
comunidade Linux no portal Java.net.
Configurando o projeto no NetBeans
O bando de dados HSQLDB pode funcionar embutido num aplicação Java, sem criar
novos threads nem abrir portas TCP. É uma excelente alternativa para aplicações de
automação de escritório, tanto que o HSQLDB foi incorporado à versão 2.0 do OpenOffice
em seu novo componente de banco de dados.
Neste artigo, foi utilizada a versão 1.8.0.1 do HSQLDB, que pode ser obtida
livremente de hsqldb.sf.net. Para instalá-la, descompacte o .zip e localize o arquivo
lib/hsqldb.jar. Este jar é tudo o que precisamos para a aplicação, já que a aplicação irá criar
automaticamente o banco de dados e as tabelas necessárias.
De volta ao NetBeans, clique com o botão direito no aplicação, na visão de projetos;
selecione a opção Properties e depois o elemento Libraries em Categories. Em seguida
clique em Add JAR/Folder e escolha o arquivo hsqldb.jar, para acrescentá-lo ao classpath do
projeto no NetBeans (o resultado é apresentado na Figura Q1). Depois clique em Ok para
fechar o diálogo de propriedades.
Caso deseje inspecionar o banco de dados da aplicação, você pode utilizar o
navegador JDBC incluso no NetBeans. (Para mais informações sobre esse recurso, consulte
o artigo “O Novo NetBeans”, na Edição 24.)

Figura Q1. Adicionando a biblioteca do HSQLDB ao classpath da aplicação

Experimentando com outros bancos


As características da aplicação Lista de Tarefas tornam o HSQLDB sensivelmente
mais adequado do que um banco de dados cliente/servidor, como o MySQL, Oracle, DB2
etc. Entretanto, a arquitetura de aplicação proposta não tem nenhuma dependência com o
HSQLDB em particular.
Caso o leito deseje experimentar com outros bancos de dados, será necessário
modificar as configurações relativas à conexão JDBC (classe todo.modelo.Paramentros) e
talvez, dependendo do banco, modificar o comando SQL CREATE TABLE em
todo.modelo.GerenciadorTarefas e incluir comandos adicionais (ou mudar a sintaxe),
para a criação do banco de dados vazio.
Se for adotado um banco cliente/servidor, não fará sentido fornecer as
funcionalidades de “Abrir lista de tarefas” e “Criar lista de tarefas”, que pressupõem hum
banco de dados baseado em arquivos locais. Em vez disso, poderá ser fornecido um diálogo
simples para edição dos parâmetros de conexão. Mas essas facilidades permanecerão

59
válidas para outros bancos de dados baseados em arquivos, como o Derby ou McKoi: será
necessário apenas mudar os filtros de arquivos para extensões apropriadas).

Todas as classes do exemplo

todo.Main
Fornece o método main() para iniciar a aplicação.

todo.controle.ConsultaEditaTarefas
Para edição e consulta a tarefas, é o principal controlador da aplicação.

todo.controle.CriaAbreListaTarefas
Para criar e abrir um arquivo (do HSQLDB) de uma lista de tarefas, e coordenar outras
operações gerais.

todo.modelo.BandoDeDadosException
Indica um erro qualquer de banco de dados.

todo.modelo.GerenciadorTarefas
É a classe de modelo principal da aplicação, seguindo o pattern DAO (Data Access Objetc);
tem a funcionalidade de recuperar e armazenar tabelas no banco de dados.

todo.modelo.ModeloException
Superclasse para todas as classes ES exceção do pacote todo.modelo.

todo.modelo.Parametros
Classe de modelo para parâmetros da aplicação, recuperados via a API Preferences do J2SE.

todo.modelo.Tarefa
Classe que segue o pattern VO (Value Object), encapsulando todos os atributos de uma
tarefa.

todo.modelo.ValidacaoException
Indica um erro de validação na digitação dos dados de uma tarefa.

todo.visao.ActionSupport
Superclasse abstrata das classes de visão, fornece métodos utilitários para a geração de
eventos actionPerformed para classes de controle, seguindo o modelo de eventos do
Swing/AWT.

todo.visao.EditaTarefa
Diálogo (JDialog) para a entrada dos dados de uma tarefa, seja na sua adição ou edição.

todo.visao.ListaTarefas
Janela principal (JFrame) da aplicação, exibe uma lista de tarefas em um componente
JTable.

todo.visao.TarefasCellRenderer
Customiza um JTable para exibir as informações de uma tarefa em cores diferentes, de
acordo com o status de concluída ou com alerta.

todo.visao.TarefasColumnModel
Customiza um JTable para exibir quatro colunas (Prioridade, Descrição, Alarme e Data de
Conclusão). As colunas têm tamanho fixo, com exceção de Descrição, que ocupa toda a
largura restante.

todo.visao.TarefasTableModel

60
Customiza um JTable para exibir as informações a partir de uma lista de objetos Tarefa
(List<Tarefa>). Evita conversões desnecessárias dos atributos para um vetor de strings,
economizando memória.

Ajustes diversos
Em relação à aplicação prototipada na primeira parte e tornada interativa na
segunda, foram feitos uma série de ajustes. É normal que, à medida que uma aplicação
chegue próxima a sua conclusão, sejam identificados algumas pequenas questões relativas
à interface com o usuário, habilitação de componentes, ou posicionamento de janelas.
Ajustes desse tipo estão espalhados pela aplicação e não são apresentados em listagens
para poupar espaço. Mas os fontes completos da aplicação final – e das versões
intermediarias – estão disponíveis para download no site da Java Magazine.
Segue uma breve relação dos justes realizados:
• As duas visões ListaTarefas e EditaTarefas foram centralizadas, uma em relação à
tela e a outra em relação à sua janela mãe, por meio do método
setLocationRelativeTo(), chamado no construtor.
• O método actionPerformed() dos controladores chama setCursor() para ativar o
cursor de “ampulhetas” (ou o cursor padrão de espera na sua plataforma) e
restaurar o cursor padrão depois de tratar um evento (ou em caso de exceções).
Assim operações potencialmente demoradas, como conectar ao banco de dados ou
listar vários registros, terão feedback visual para o usuário.
• O JTable e JTextField para edição da quantidade de dias para emissão de alertas
na edição de uma tarefa não estavam sendo habilitados/desabilitados junto com o
JCheckbox correspondente. Problema resolvido.
• O controlador CriaAbreListaTarefas foi registrado com um WindowListener da
classe de visão ListaTarefas. Desse modo, ele garante o fechamento do banco de
dados ao final da aplicação.
• Foi capturado o evento MouseClick do JTable, em ListaTarefas. Se o atributo
clickCount do evento.

Onde está o RAD?


O leitor habituado a um ambiente RAD (rapid Application Development) como o VB
ou Delphi pode estar um pouco frustrado por não termos utilizado recursos visuais para o
código de banco de dados. O fato é que a biblioteca padrão de classes do J2SE não inclui
componentes no estilo DataControl, DBGrid etc., que aumentam inicialmente a
produtividade, mas estimulam a programação de “código espaguete” misturando lógica de
acesso a dados, visualização de dados e regras de negocio dentro da mesma classe de
formulário (janela), e acabam prejudicando a manutenção da aplicação.
Embora existam componentes sililares no mercado, que podem ser acrescentados ao
NetBeans (ou outros IDEs) a prática recomendada em aplicações Java é ou construir classes
DAO para acesso a dados via JDBC; ou então utilizar um framework de mapeamento
objeto-relacional com o Hibernate. Em ambos os casos, é mantida a separação de
responsabilidades entre as várias classes da aplicação, e o código é escrito num estilo
completamente orientado a objetos em vez de parcialmente procedural.
Para sermos justos, temos que lembrar que nada impede que as mesmas práticas de
projetos sejam empregadas no Delphi e outros ambientes RAD orientados a objetos, mas
infelizmente a cultura predominante neles é de não explorar classes e objetos na
arquitetura da aplicação.

61

Você também pode gostar