Escolar Documentos
Profissional Documentos
Cultura Documentos
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.
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.
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.
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.
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
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.
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.
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.
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.
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.
15
Figura 6. Abas do customizador para o TableModel do JTable na janela principal
16
Figura 9. Escolhendo um ícone para um item de menu ou toolbar
17
Figura 14. Customizador de layout do diálogo de edição de tarefas, após a conversão para
um GridBagLayout
18
Figura 17. Aplicação Todo com o look-and-feel GTK do Linux
import javax.swing.*;
import todo.visao.*;
19
Parte 2: JTable, MVC Aplicado e Tratamento de Eventos
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.
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 Tarefa() {
setConcluida(false);
setGerarAlerta(false);
}
23
// ... métodos get/set
}
package todo;
import javax.swing.*;
import todo.visao.ListaTarefas;
import todo.controle.ConsultaEditaTarefas;
import todo.modelo.GerenciadorTarefas;
package todo.modelo;
import java.util.*;
import java.text.*;
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);
24
return tarefas;
}
package todo.controle;
import todo.visao.*;
import todo.modelo.*;
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.
package todo.visao;
import java.util.*;
import java.text.*;
import javax.swing.table.*;
import todo.modelo.*;
26
public Tarefa getValoresTarefa(int rowIndex) {
return tarefas.get(rowIndex);
}
}
package todo.visao;
// ... imports
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.
package todo.visao;
import java.awt.*;
import javax.swing.table.*;
{
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
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
30
label.setForeground(corTarefa(tarefa));
label.setBackground(Color.GRAY);
}
else {
label.setForeground(Color.BLACK);
label.setBackground(corTarefa(tarefa));
}
return label;
}
}
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.
32
// habilitaMarcacao() e habilitaRemocao()
// seguem o mesmo modelo de habilitaEdicao()
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);
}
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
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 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
36
public void setMensagem(String msg. boolean isErro) {
// ... igual ao correspondente em ListaTarefa
}
// actionSupport(), addActionListener e
// removeActionListener() iguais aos de ListaTarefas ...
package todo.visao;
// .. imports
37
ActionListener listener = it.next();
listener.actionPerformed(new ActionEvent(window,
ActionEvent.ACTION_PERFORMED, e.getActionCommand()));
}
}
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.
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
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
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.
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”.
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.
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;
43
return true;
}
catch (SQLException e) {
return false;
}
finally {
finaliza();
}
}
44
PreparedStatement pst = con.prepareStatement(sql);
stmt = pst;
return pst;
}
finally {finaliza();}
}
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;
}
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);
}
46
throws ValidacaoException
{
if (stringVazia(tarefa.getDescricao()))
throw new ValidacaoException(
"Deve ser fornecida uma descri,ao para a 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.*;
48
catch (Exception ex) {
visao.setMensagem(ex.getMessage(), true);
}
visao.setCursor(null);
}
49
}
}
if (removidas > 0)
listaTarefas();
}
catch (ModeloException e) {
editaTarefaDialog.setMensagem(e.getMessage(), true);
}
}
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.
package todo.visao;
import java.util.*;
import java.text.*;
import javax.swing.table.*;
import todo.modelo.*;
51
return 4;
}
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.
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.*;
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;
54
this.params = params;
visao.addActionListener(this);
visao.addWindowListener(fechaBanco);
}
modelo.reconecta(arq);
visao.setListaTarefas(modelo.listaTarefas(true));
55
return arq;
}
56
JOptionPane.INFORMATION_MESSAGE);
}
}
Figura 6. Alerta avisando sobre uma tarefa próxima da sua data de conclusão
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).
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.)
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).
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.
61