Você está na página 1de 61

Um Sistema Java Completo - Criando Aplicaes Grficas com o NetBeans

Parte 1: Prototipao da interface com o usurio Aprenda a criar aplicaes com aparncia profissional, desenvolvendo um exemplo completo no NetBeans, comeando 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 verso 4.0 foi apresentada na edio anterior, com foco no novo modelo de projetos, que oferece flexibilidade para os projetos mais complexos. Nesta srie de artigos, o enfoque ser demonstrar como construir aplicaes grficas com a biblioteca padro 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 esforo de adaptao filosofia de trabalho diferente do Java visual. Isso inclui a forma de desenhar formulrios usando gerenciadores de layout; o tratamento de eventos baseado em listeners; e a forma de utilizao de componentes mais sofisticados (como tabelas e rvores), que exigem customizao baseada na extenso de classes ou implementao de interfaces. Nesta parte apresentamos o desenvolvimento da interface grfica de uma aplicao de "Lista de Tarefas", ilustrando boas prticas do desenvolvimento Swing e mostrando como o NetBeans pode auxiliar na implementao dessas prticas. Se voc est comeando com o desenvolvimento Swing, no deixe de consultar os quadros Conceitos essenciais do Swing e "Gerenciadores de Layout e o NetBeans". E leitores atentos ao lanamento recente do NetBeans 4.1 podem consultar o quadro NetBeans 3.6, 4.0 e 4.1 para informaes sobre as diferenas entre as verses. No contexto deste artigo, as verses 4.1 e 4.0 so praticamente idnticas, e pouco mudou desde a verso 3.6 no que se refere ao desenvolvimento visual. A aplicao de exemplo A Lista de Tarefas ou todo list um componente comum de aplicaes de produtividade pessoal, que permite cadastrar tarefas com prioridades e datas de concluso associadas. A interface grfica inclui uma janela principal, que lista as tarefas ordenadas por prioridade ou por data de concluso, e um dilogo para edio ou incluso de uma tarefa. A janela principal tpica de aplicaes desktop, contendo uma barras de menus e de ferramentas, e uma rea central para a visualizao das tarefas; essa janela deve tambm ser capaz de adaptar-se resoluo de vdeo do sistema ou ao tamanho determinado pelo usurio. J o dilogo um tpico formulrio de entrada de dados, com caixas de edio, checkboxes e outros controles posicionados em uma ordem natural de preenchimento, com os usuais botes Ok e Cancelar na parte inferior. O termo "formulrio" utilizado de forma genrica para referenciar janelas, dilogos e painis, ou seja, qualquer classe que possa ser editada visualmente pela adio de componentes visuais. Outras janelas da aplicao, como uma caixa Sobre, e um dilogo para exibio de alertas sobre tarefas prximas de suas datas de concluso sero construdas nas prximas partes desta srie. Antes de iniciar a construo de uma interface visual, sempre bom desenhar um esboo contendo os principais componentes e sua disposio nas janelas. Papel e caneta ou um programa de desenho so geralmente melhores para isso do que o seu IDE favorito, pois permitem que as idias fluam sem serem viciadas pela estrutura e componentes padres do IDE. A idia aqui fazer brainstorming sobre como deve ser a interface com o usurio, e no obter uma definio precisa da aparncia de cada formulrio. Tambm h o benefcio de se poder focar nos componentes realmente essenciais para o usurio, antes de

entrar em detalhes de implementao, como cones ou atalhos. Veja na Figura 1 um esboo das duas janelas aplicao de exemplo deste artigo. Design: por favor, ajustem as cores para deixar o fundo azulado o mais branco possvel: Arquitetura da aplicao um padro em desenvolvimento orientado a objetos utilizar a arquitetura MVC como base de uma aplicao interativa. Dessa forma, o cdigo da nossa aplicao ser organizado em classes de modelo, viso e controlador, utilizando para tal uma estrutura de pacotes. Neste artigo ser realizada apenas a primeira etapa do desenvolvimento da aplicao, que a prototipao da interface com o usurio, utilizando os recursos de desenho de interfaces Swing do NetBeans. Vamos limitar o cdigo ao mnimo que possibilite a navegao e exibio de informaes; assim poderemos validar a usabilidade e a adequao da interface s necessidades da aplicao. Nas prximas edies, alm de novos formulrios, vamos criar as classes de negcios, contendo a lgica de validao e persistncia. Criao do projeto Para a aplicao de exemplo, vamos usar o modelo Java Application do NetBeans (selecione File|New Project, escolha a categoria General e depois Java Application). Fornea Todo como nome do projeto e aceite os padres, criando assim a classe todo.Main. Deixaremos esta classe vazia; mas adiante ela ser modificada para instanciar a janela principal da aplicao. Crie tambm os pacotes todo.modelo, todo.visao e todo.controle. Em uma aplicao de produo, o prefixo para os nomes dos pacotes da aplicao 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 so exibidas vrias vises (ou visualizaes) 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 customizao. esquerda, logo abaixo da rea que exibe as vises de projeto, arquivos e ambiente de execuo (Projects, File e Runtime) temos o inspetor (Inspector), que apresenta a estrutura hierrquica de objetos visuais. O inspetor muito til quando um componente est sobreposto por outro, ou quando no est visvel 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. possvel ainda selecionar vrios componentes na rea de desenho (ou no inspetor) e modificar propriedades em todos eles ao mesmo tempo na janela de propriedades. H algumas diferenas no posicionamento e configuraes padres dessas partes do IDE entre as verses 4.1 e 4.0 do NetBeans, entretanto possvel arrastar qualquer viso para outra posio na janela principal do IDE, alm de customizar a aparncia da paleta. Pessoalmente prefiro uma mistura dos padres das duas verses, configurando a paleta para uma aparncia mais compacta (como na verso 4.0) e colocando o inspetor no lado esquerdo (como no NetBeans 4.1), de modo a deixar mais espao para a janela de propriedades. Para reposicionar o inspetor, basta arrast-lo pela sua barra de ttulo; para deixar a paleta mais compacta, clique nela com o boto direito e escolha Hide component names.

Outra dica de customizao do NetBeans colocar a viso de sada (output) no modo de "auto-esconder", para que ela no reduza o espao disponvel para o inspetor e a paleta; ela fica como apenas um boto na parte inferior da janela do IDE. Note que a Figura 3 j apresenta a viso de sada nesta configurao. Basta clicar no cone da janela de sada, quando ela for ativada pela prxima compilao ou execuo do projeto. O nosso esboo indica a presena 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 adio 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 colocao do JToolbar na posio "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 no 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 boto. Repita o procedimento algumas vezes, para inserir os botes de adicionar, editar, excluir e marcar tarefas. Utilize smbolos como + e "*" para o texto dos componentes (altere a propriedade text), enquanto no criamos os cones apropriados para cada um. Selecione o JToogleButton (prximo ao JButton na paleta, com cone igual) e acrescente dois botes desse tipo ao JToolbar. Eles correspondem s opes de ordenao das tarefas por prioridade ou por data de concluso, e para exibir ou no as tarefas j concludas. Por fim, acrescente mais um JButton para a operao de visualizao de alertas. Continue utilizando smbolos como ! no texto dos botes, como substitutos provisrios para os cones que ainda no foram acrescentados. Para obter o agrupamento e separao visual entre os grupos de botes de uma JToolbar no 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 espao em branco. Para criar uma barra de status, insira um JLabel ( ) na parte inferior da janela principal, de modo que ele seja colocado na posio "sul" do BorderLayout. Finalmente, insira um JScrollPane ( ) na parte central do JFrame, e dentro dele insira um JTable ( ). Sempre que o contedo 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 aplicao j comea a se parecer com o nosso esboo 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 funo mais do que decorativa. Isso facilita a identificao dos componentes no inspetor e tambm 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 prototipao de uma interface grfica, seja ela desktop ou web, importante desde o incio inserir dados que sejam o mais prximo possvel da realidade. Caso contrrio, no ser possvel decidir, por exemplo, se o tamanho e a legibilidade dos componentes so adequados ou se a disposio na tela intuitiva. Dessa forma, no recomendvel utilizar textos falsos (como Nonono ou "Xxxxx"), pois eles no refletem a informao que ser vista pelo usurio final.

No caso da janela principal, isso significa no apenas configurar as colunas e os ttulos da tabela, mas tambm inserir algumas linhas de dados. Selecione o JTable (no inspetor ou na rea de desenho), depois selecione sua propriedade model e clique no boto de reticncias ( ) ao seu lado. Isso exibe o "customizador" da propriedade, que um mecanismo padro do Java para que um componente visual oferea suporte personalizado configurao de suas propriedades, independentemente do IDE utilizado. Alguns IDEs podem optar por oferecer seus prprios customizadores como alternativa (ou em adio) aos fornecidos pelos prprios componentes. O customizador padro do NetBeans para uma propriedade do tipo TableModel (mostrado na Figura 6) apresenta duas abas. Na primeira, so definidas as colunas da tabela, incluindo o nome e o tipo. Na segunda, possvel inserir um conjunto de dados iniciais para a tabela. Fornea estes dados conforme a figura. Se voc est habituado a IDEs como Delphi ou Visual Basic, poder achar a configurao de tabelas do NetBeans um tanto restrita. Mas veremos na prxima edio que a maioria dessas configuraes na verdade feita por cdigo Java, em classes criadas como parte da aplicao. Veremos ainda que as tabelas do Swing fornecem flexibilidade bastante superior oferecida pelos seus correspondentes em ambientes visuais no-Java. Editando menus A edio de menus e itens de menus no NetBeans no feita na rea de desenho, mas sim no inspetor. Apenas o primeiro nvel na barra de menus pode ser configurado e visualizado na prpria 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 boto direito no JMenuBar e selecione a opo Add JMenu; depois repita mais duas vezes a operao, de modo a terminar com quatro menus; Altere a propriedade text dos menus para "Arquivo", "Editar", "Opes" e "Ajuda". At aqui as alteraes podem ser observadas na rea de desenho do editor visual do NetBeans. Em seguida clique com o boto direito no JMenu "Arquivo" recm-criado e escolha Add>JMenuItem O item adicionado no 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 operao para criar o item Abrir lista de tarefas, depois adicione um JSeparator e mais um JMenuItem, para a opo Sair. Ao selecionar um item de menu ou boto 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 cdigo, permitindo editar o cdigo associado ao evento de ao do boto ou item. Se isso acontecer, observe no topo do editor de cdigo dois botes: Source e Design. Um clique em Design retorna ao editor visual; e a qualquer momento pode-se clicar em Source para ver o cdigo Java correspondente ao formulrio. Para visualizar a aparncia dos itens recm-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 cdigo de tratamento de eventos (que ainda no acrescentamos de qualquer modo). Assim possvel interagir com os menus e visualizar seus itens e submenus. A Figura 7 mostra um dos menus nessa pr-visualizao. 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 posio dentro do inspetor. cones e barras de ferramentas

Um item de menu ou boto de barra de ferramentas contendo apenas texto considerado pobre para os padres atuais de interfaces grficas: espera-se que ao menos os itens mais importantes tenham cones associados, alm de teclas de atalho. Todas essas opes 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) no incluem um conjunto padro de cones prontos para uso. A Sun fornece um conjunto de cones especialmente adaptados ao look-and-feel Metal padro 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 pgina, e podem ser redistribudos livremente com sua aplicao. Preferi, no entanto, copiar os cones do Workbench do Eclipse, que so mais modernos em vrios aspectos e tambm podem ser redistribudos livremente. Os cones esto todos na pasta src/icones nos fontes para download deste artigo (embora apenas uma pequena deles sejam utilizados pela aplicao que estamos construindo). A segunda dificuldade na forma de referenciar os cones, de forma independente do diretrio de instalao no computador do usurio final. Caso contrrio voc vai precisar fornecer tambm um programa de instalao, assim como lidar com questes como letras de drives (que s existem no Windows) e separadores de diretrios. A soluo recomendada colocar os cones junto s classes da aplicao, de modo que possam ser localizados por uma busca pelo classpath, da mesma forma que so localizados os arquivos .class. pela JVM. Arquivos de dados encontrados desta maneira so chamados de recursos da aplicao. Voc pode ento copiar a pasta src/icones do pacote de download para o mesmo local em seu projeto no NetBeans. Por estarem na mesma pasta do cdigo Java da aplicao, os recursos (no caso os cones) sero automaticamente copiados para a pasta build/classes durante a compilao do projeto. Assim estaro junto aos bytecodes das classes da aplicao, como desejamos. O prximo passo configurar os cones nos itens de menu. Para cada item, selecione sua propriedade icon e clique no boto do seu customizador ( ). No dilogo exibido (Figura 9) selecione a opo 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 aplicao, por exemplo /icones/delete_obj.gif. Caso o NetBeans no exiba os cones recm-copiados no customizador da propriedade icon, entre nas propriedades do projeto (clique com o boto direito sobre o cone do projeto, na viso de projeto) e adicione uma pasta qualquer ao seu classpath de compilao (item Libraries, aba Build nas propriedades do projeto). Ao se confirmar a alternao nas propriedades, o IDE ir re-escanear o classpath e assim notar a presena dos cones. Depois no deixe de voltar s propriedades do projeto e remover esta pasta adicional. Quando o projeto for empacotado para distribuio (com Build|Build Main Project) os arquivos de cones sero copiados para o pacote jar executvel, juntamente com os bytecodes da aplicao. A aplicao poder assim ser instalada em qualquer pasta no computador do usurio final. Da mesma forma que foi feito para os menus, podemos agora configurar os cones para os botes da barra de ferramentas. A Figura 10 apresenta a barra customizada com os cones. Tome o cuidado de escolher para os botes os mesmos cones dos itens de menu equivalentes. Aceleradores e atalhos Aplicaes visuais bem-escritas definem mnemnicos para todos os seus itens de menus, e tambm aceleradores para os itens utilizados com mais freqncia pelo usurio.

Um mnemnico permite navegar pelos menus utilizando apenas o teclado, o que torna a operao da aplicao mais gil para usurios freqentes e mais confortvel em dispositivos como quiosques1[1]. Mnemnicos tambm so um dos principais itens de acessibilidade2[2] de uma aplicao. Um mnemnico em geral a primeira letra do item do menu, conjugada com a tecla Alt. Alm de itens de menus, outros componentes como botes e labels podem ter mnemnicos, permitindo a navegao pelo teclado em um formulrio de entrada de dados. Mais adiante veremos como fazer isso, quando detalharmos a construo do dilogo de edio de tarefas. J um acelerador permite a execuo direta de alguma operao importante, sem a necessidade de navegar at ela nos menus. Um exemplo o popular Ctrl+X para o comando Editar|Recortar. Mnemnicos e aceleradores so definidos em propriedades dos itens de menus e demais controles; ento fcil configur-los no NetBeans. A Figura 11 apresenta essa configurao para o item Editar|Adicionar tarefa da aplicao de exemplo. E a Figura 12 mostra os menus da aplicao completos, j com cones, mnemnicos e aceleradores definidos. Observe que as letras correspondentes aos mnemnicos so exibidas sublinhadas em cada item de menu, enquanto que os aceleradores definidos so exibidos ao lado do texto do item correspondente. J que falamos de facilidade de uso, aproveitamos para um comentrio sobre as barras de ferramentas. Conceitualmente, elas seriam atalhos de mouse, ento se uma operao importante o suficiente para estar na barra de ferramentas, ela tambm deve ter um atalho de teclado associado. Alm disso, todo elemento numa barra de ferramentas deve corresponder a algum item de menu, ou ento a alguma opo num dilogo. Em outras palavras, atalhos e barras de ferramentas nunca devem ser a nica forma de se realizar uma tarefa. Voltando aplicao: para que as barras e menus fiquem completos, eles necessitam apenas da configurao de tooltips, que podem ser configurados alterando a propriedade toolTipText de cada componente. A janela de edio de tarefas Para criar a segunda janela da aplicao, clique com o boto 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 possvel posicionar os componentes de forma livre dentro do dilogo. 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 mudana para o GridBagLayout vai parecer que a janela no mudou, mas ao abrir o customizador do gerenciador de layout (clicando com o boto direito no JDialog e selecionando Customize Layout), veremos que o conjunto de constraints gerados pela converso menos que timo (Figura 14). H vrias colunas e linhas adicionais e os espaamentos desiguais em cada clula prejudicam o alinhamento dos componentes. Apesar disso, ainda no encontrei um desenvolvedor que, depois de algum tempo com o NetBeans, prefira utilizar o GridBagLayout desde o incio, porque ser mais trabalhoso configurar manualmente todas as propriedades a cada componente adicionado. 1[1] Como os quiosques que informam sobre localizaes de lojas em shopping centers ou caixas eletrnicos de bancos 2[2] Uma aplicao considerada acessvel se foi construda levando em considerao a facilidade de uso por pessoas com deficincias visuais ou motoras 3[3] As propriedades que vemos agrupadas na categoria Layout em um componente so na verdade as propriedades do objeto de constraints do gerenciador de layout, cuja classe especfica para cada gerenciador.

O posicionamento dos JLabels e caixas de texto (incluindo os dois JSpinner), no oferece dificuldades. Lembre-se tambm de colocar o JTextArea dentro de um JScrollPane. Os botes de Salvar, Cancelar e Remover devero ser inseridos todos dentro de um painel; este painel que ser posicionado dentro do dilogo (mais detalhes adiante). Observe ainda os dois JSeparator, colocados antes e depois da rea de texto de observaes, e o JLabel a parte superior do dilogo, que ser utilizado como uma rea ara exibio de mensagens de erros. Daqui em diante entramos em mais detalhes sobre como configurar o dilogo para chegar aparncia final, conforme a Figura 15. Todos os ajustes sero 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 espaamento no formulrio Foi inserido um espaamento de cinco pixels entre cada componente e entre os componentes e as bordas do dilogo, para que no paream grudados. Observe as reas em amarelo na Figura 16; elas indicam onde foram adicionados espaamentos. Aproveite o fato de que possvel selecionar mltiplos componentes utilizando a tecla Ctrl + mouse, para configurar espaamentos uniformes. Os labels de Descrio, Prioridade e Data de concluso, assim como o checkbox Gerar alerta foram ancorados (modificando Anchor) direita (posio "leste"); os controles correspondentes (um JTextField, dois JSpinner e um JFormattedTextField) foram alinhados esquerda (posio "oeste"). Ambos os grupos receberam espaamento esquerda e abaixo, exceto pela primeira linha (Descrio), que ganhou tambm espaamento acima (caso contrrio, ela ficaria colada ao label de mensagem). Para JScrollPane e os dois separadores, foi acrescentado espaamento esquerda, direita e abaixo, e eles foram configurados para ocuparem trs clulas de largura (alterando Grid Width). A caixa de texto de descrio ocupa duas clulas, assim como a de data de concluso. As caixas de prioridade e de dias de antecedncia para o alerta ocupam apenas uma clula de largura. O campo de observaes Queremos que o dilogo de edio de atividades seja redimensionvel, e que o campo de observaes ocupe todo o espao 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 contm 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 botes Um JButton normalmente assume a dimenso mnima que permita exibir seu texto, gerando telas deselegantes, onde o boto de "Ok" muito menor do que o de "Cancelar", por exemplo. A aparncia fica melhor quando botes relacionados tm as mesmas dimenses. A maneira mais fcil de fazer isso inserindo os botes dentro do seu prprio painel, e configurando o layout deste painel para um GridLayout. O painel ento posicionado na parte inferior do dilogo, posicionado na parte de baixo do GridBagLayout, com duas clulas de largura. esquerda do painel de botes est o checkbox que registra se a tarefa foi ou no completada. A ncora do painel colocada na posio "leste", de modo que o conjunto de botes fique alinhado direita. Label de mensagens

Para o label de mensagens, posicionado no topo do dilogo, queremos um visual diferente do padro. Ele deve ficar claramente diferenciado no formulrio de edio, dada sua funo de exibir mensagens informativas. Obtemos o efeito de faixa mudando a cor de fundo do label, e deixado seus espaamentos (Insets) zerados, de modo que ele fique colado aos cantos do dilogo; entretanto, no queremos que o texto fique colado. A soluo definir uma borda: alteramos a propriedade border do label para EmptyBorder e configuramos esta borda com espaamentos de 5 pixels em cada direo. 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 mudana na cor de fundo s ser visvel se for modificada a propriedade opaque do componente, pois o padro que um JLabel tenha o fundo transparente, incorporando a cor de fundo do seu container. J em relao fonte, foi mantido o padro do Swing (Dialog); apenas retiramos o negrito da fonte padro. Na maioria das vezes deve-se evitar a customizao de fontes e cores em formulrios de entrada de dados, pois isso pode tornar a aplicao deselegante ou ilegvel caso o usurio opte por um look-and-feel customizado, ou por um tema de cores diferente para o lookand-feel padro. Caso seja necessrio 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. Mnemnicos no formulrio Para garantir a agilidade na digitao e a acessibilidade do formulrio, devem ser definidos mnemnicos 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 "botes" (JButton, JToogleButton, JCheckBox e JRadioButton) so configurados pelas suas propriedades nmemonic especficas. Use a Figura 15 como referncia para definir as teclas de mnemnico para cada componente. Testando o prottipo At este ponto, as janelas do prottipo foram testadas apenas pela pr-visualizao do editor visual do NetBeans, que nem sempre fiel ao comportamento das classes Java. Vamos ento inserir o mnimo de cdigo para que as duas telas possam ser iniciadas como parte de uma aplicao, e verificar se o resultado funciona corretamente. Localize a classe todo.Main na viso de projeto e d um clique duplo para abri-la no editor de cdigo. Edite o cdigo conforme a Listagem 1: dessa forma, a aplicao iniciar instanciando uma janela ListaTarefas e tornando-a visvel. 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 cdigo. D um clique duplo no primeiro boto da barra de ferramentas. Ser ento mostrado o editor de cdigo, com o cursor posicionado no mtodo que trata o evento actionPerformed do boto. (Os trechos em azul-claro so gerados pelo prprio editor visual e no podero ser modificados no editor de cdigo.) Complete o cdigo para o evento conforme a Listagem 2. O objetivo apenas instanciar o dilogo de edio de tarefas e exibi-lo de forma modal4[4]. Voc j poder executar a aplicao com Run|Run Main Project e verificar o comportamento do exemplo; ou ento selecionar Build|Build Main Project para gerar o pacote jar executvel para a aplicao, e iniciar a aplicao na linha de comando da maneira usual (onde estamos supondo que o diretrio Todo/dist contem o jar da aplicao): $ java -jar Todo/dist/Todo.jar 4[4] Um dilogo exibido de forma modal impede que se interaja com sua janela "me", at que seja fechado pelo usurio.

importante que uma aplicao Swing seja testada desde o incio com look-andfeels alternativos, para garantir que ela realmente uma aplicao 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 Todo/dist/Todo.jar feel. Concluses Este artigo demonstrou como usar as facilidades oferecidas pelo NetBeans para desenvolver aplicaes Java com aparncia profissional, baseadas nos componentes visuais do Swing. Como se viu, importante atentar para detalhes, naturalmente, seguir as boas prticas da plataforma Java. Artigos futuros iro demonstrar como utilizar as janelas que foram prototipadas nesta aplicao em uma aplicao orientada a objetos, considerando acesso a bancos de dados e uso eficiente de threads. Veremos tambm 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 coleo de recursos para o desenvolvedor Swing

-jar

A Figura 17 apresenta o aspecto das duas janelas da aplicao com este look-and-

Alguns conceitos de Swing Para os que esto comeando com a programao visual em Java, so apresentados aqui alguns conceitos do Swing, que so usados ou citados ao longo do texto. Componentes, em um sentido amplo, so objetos visuais (ex.: JCheckBox, JButton, JSeparator), ou objeto no-visuais (como GridBagLayout) que podem interagir com objetos visuais por meio dos padres 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 mnimo, que a menor dimenso (altura, largura) capaz de exibir todo o seu contedo (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 visvel. NetBeans 3.6, 4.0 e 4.1 O NetBeans um dos projetos mais antigos de IDEs livre com recursos visuais, mas no era inicialmente com uma interface com o usurio bem-projetada. Era sim um exemplo de como a plataforma Java e a tecnologia de componentes JavaBeans poderia viabilizar a construo de aplicaes desktop complexas. Como mostrados em outros artigos desta coluna, na verso 3.6 iniciou-se um ciclo de mudanas profundas no NetBeans, de modo a moderniz-lo e deix-lo mais coerente com modernas prticas de desenvolvimento de software em Java. A primeira mudana, no 3.6, foi a criao de uma nova interface com o usurio, baseada em visualizaes 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).

A verso 4.0 inaugurou um novo modelo de projetos, baseado no Ant, que permite customizar com facilidade o processo de construo e empacotamento de aplicaes. Basicamente, o desenvolvedor pode acrescentar novas etapas ao processo, por exemplo, gerao de cdigo via XDoclet ou pr-compilao de pginas JSP com o Jasper do Tomcat tudo sem necessitar de instalar um plug-in especialmente construdo para o NetBeans. Na recm-lanada verso 4.1 a novidade foram recursos voltados para o desenvolvimento J2EE, em especial suporte a EJBs e a web services, no havendo grandes mudanas na interface com o usurio ou no suporte ao desenvolvimento Swing, exceto pela presena de novos templates e assistentes. Da verso 3.6 at a verso 4.1 h poucas mudanas no editor visual, a maioria delas apenas estticas. A nica mudana realmente importante foi o acrscimo, na verso 4.0, de um customizador para o GridBagLayout, utilizado neste artigo para configurar o dilogo de edio 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 formulrios, 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 especfico do NetBeans), alm das facilidades oferecidas pelo IDE para a customizao visual de componentes com esses gerenciadores. FlowLayout O gerenciador de layout de fluxo apenas posiciona os componentes em fila, um aps o outro, cada qual com suas dimenses mnimas. O FlowLayout imita o fluxo de texto em uma folha de papel e, como um pargrafo em um processador de textos, tambm 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 possvel de componentes, por exemplo em barras de ferramentas ou de status. No NetBeans: Novos componentes so sempre adicionados ao final do fluxo, mas eles podem ser reposicionados pelo mouse. Um quadrado pontilhado indica a nova posio do componente durante esta operao. BorderLayout O BorderLayout posiciona os compomentes nas bordas do container, deixando a maior parte da sua rea disponvel para o componente inserido no centro. Cada borda identificada por um ponto cardeal (NORTH, SOUTH, EAST, WEST). Apenas um componente ser visvel em cada borda, expandido na altura ou largura para ocupar toda a borda do container, porm assumindo o valor mnimo na outra dimenso. Note que esta disposio reflete o padro na maioria das aplicaes 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 edio ao centro. No NetBeans: O componente posicionado na borda da rea de desenho que for clicada quando feita sua adio ao container, e permite que um componente seja arrastado para outra posio (outra borda ou para o centro) desde que esta posio esteja vazia. Caso um componente seja arrastado para uma posio j ocupada, o BorderLayout ir se perder (e consequentemente tambm o NetBeans), e a correo ter que ser feita na viso 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 criao. Todas as clulas possuem o mesmo tamanho, e so expandidas para ocupar a rea total disponvel no container. Caso haja menos componentes do que clulas, o espao das clulas vazias distribudo igualmente entre os componentes; mas podem ficar clulas vazias nas ltimas colunas da ltima linha. O GridLayout adequado quando se deseja que um grupo de componentes (como um grupo de botes) tenha dimenses uniformes, como na caixa de ferramentas de um programa de desenho ou o par de botes Ok e Cancela de um dilogo. No NetBeans: Novos componentes so sempre acrescentados na prxima clula vazia da grade, mas podem ser arrastados com o mouse para uma posio (clula) diferente. GridBagLayout Com nome estranho ("saco de grades"), o GridBagLayout o mais poderoso e mais flexvel dos gerenciadores fornecidos com o J2SE. Ele imita em linhas gerais o funcionamento de uma tabela HTML, em que um componente pode ocupar vrias clulas, ou seja, se estender por vrias colunas e linhas. Os componentes podem ser expandidos para ocupar toda a rea das suas clulas, ou serem alinhados em qualquer posio do conjunto de clulas. Linhas e colunas assumem as dimenses do maior componente, mas necessrio que tenham todas o mesmo tamanho. E algumas clulas podem ser configuradas para ocuparem toda a rea disponvel no container. Podemos afirmar com segurana que qualquer disposio de componentes pode ser configurada em um GridBagLayout. Por outro lado, a quantidade de constraints (propriedades e restries de layout) possvel para cada componente tambm deu a este gerenciador a fama de ser difcil de programar. No NetBeans: O editor visual do NetBeans no tinha suporte ao GridBagLayout antes da verso 4.0, o que obrigava o desenvolvedor a configurar cada uma das dezenas de constraints, manualmente para cada componente. Mas a nova verso 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. Botes de setas permitem ajustar facilmente cada propriedade, e os componentes podem ser arrastados entre as clulas da grade. Observe o cdigo de cores no customizador: azul significa que a clula ocupada por um componente; cinza indica uma clula vazia; vermelho indica uma linha ou coluna sem componentes; e amarelo mostra espaamento (insets) interno clula. O customizador do GridBagLayout realmente fcil de usar, e em pouco tempo voc estar acostumado com ele. Pena que, pelo customizador, no seja possvel modificar propriedades alm dos constraints de cada componente, nem adicionar novos componentes. Ento ser necessrio entrar e sair do customizador vrias vezes durante o desenho de um formulrio. Null Layout Na verdade o Null Layout no um gerenciador de layout, mas sim a ausncia 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 permanecero na posio onde foram colocados e com as dimenses 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 no fornece as ferramentas de alinhar e centralizar componentes, ou para ajustar as dimenses de grupos de componentes. Por outro lado, o uso desse layout no recomendado, por ser incompatvel com o uso de look-and-feels customizados e ir contra

11

filosofia de portabilidade do Java. Ento faz sentido que o NetBeans desestimule o uso desta opo ao no fornecer facilidades especficas. Absolute Layout O AbsoluteLayout no um gerenciador padro do Java, mas uma adio do NetBeans; seu uso requer que o pacote modules/ext/AbsoluteLayout.jar seja copiado para a estao do usurio e configurado no classpath. Ele funciona como um Null Layout, com a diferena de que as dimenses do container so calculadas corretamente, permitindo o uso do mtodo pack() (com o Null Layout, o resultado so dimenses [0, 0]). Ele apresenta todas as desvantagens do Null Layout, portanto tambm no recomendado para uso geral. GridBagConstraints As propriedades que determinam a posio e dimenses de um componente dentro de um GridBagLayout so reunidas em um objeto chamado GridBagConstraints. Cada componente adicionado a um container cujo gerenciador de layout seja um GridBagLayout possui seu prprio objeto GridBagConstraints. Neste quadro fornecemos uma breve explicao 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 no so os mesmos que sero encontrados na documentao javadoc da classe, onde seguido um estilo mais telegrfico, 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 posio do componente dentro da tabela ou grade utilizada pelo GridBagLayout para posicionar os componentes. Grid Width e Grid Height determinam a quantidade de clulas da tabela que sero ocupadas pelo componente, respectivamente na largura e altura. Fill indica se o componente ir ocupar seu tamanho mnimo, deixando vazio o restante das clulas ocupadas; ou se ele ser expandido para ocupar todo o espao alocado para a clula. A expanso 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 espao acrescentado ao prprio componente, aumentando o seu tamanho mnimo, em vez de acrescentado clula que o contm. Anchor indica o alinhamento do componente em relao suas clulas, caso ele no preencha toda a rea alocada a elas. Seus valores possveis so baseados nos pontos cardeais, como North ou SouthEast. Weight X e Weight Y Valores maiores do que zero indicam que a clula ser expandida para alm do seu tamanho mnimo, ocupando o espao na largura ou altura que sobrar no container. Estas propriedades costumam ser utilizadas apenas quando o container pode ser redimensionado pelo usurio. Insets indicam espaamentos a serem inseridos nas quatro bordas da clula, afastando o componente destas bordas. Dois componentes em clulas adjacentes e com espaamentos zerados sero exibidos "grudados" um no outro. Dicas do editor visual Aqui esto reunidas algumas dicas que podem tornar mais produtivo o uso do editor visual do NetBeans para a construo de interfaces Swing. A maioria explora o uso do boto direito do mouse na rea de desenho ou no inspetor como alternativa viso de propriedades. Quais propriedades foram alteradas?

12

A viso de propriedades coloca em negrito os nomes das propriedades que esto com valores diferentes do padro, de modo que fica fcil verificar o que foi customizado em um componente, por exemplo, quando se deseja deixar um outro componente com a mesma aparncia. Para retornar propriedades modificadas aos seus valores padro, abra o customizador da propriedade e clique no boto 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 opo Edit Text para edio rpida do texto do componente diretamente na rea de desenho.

Alterando o layout de um container O gerenciador de layout de um container tambm pode ser modificado diretamente pelo menu de contexto.

Renomeando um componente importante dar a todos os componentes nomes intuitivos, porque esses nomes so utilizados nas propriedades do container (por exemplo, JFrame, JDialog ou JPanel) que os contm. O menu de contexto do componente fornece a opo 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 viso 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 alm da parte inferior da grade, ou para

13

alm da fronteira direita da grade. No processo pode-se criar tambm vrias linhas e colunas vazias, que podero depois ser ocupadas por novos componentes. Caso voc queira inserir novos componentes no incio (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 clulas criadas, at que fiquem clulas vazias nas posies desejadas.

Figura 1. Esboo da aplicao Todo

Figura 2. Criao da janela ListaTarefas

14

Figura 3. Editor visual do NetBeans 4.0 (esquerda) e do 4.1 (direira). Notem a posio diferente do inspetor e a configurao do palete de componentes.

Figura 4. Prottipo parcial da janela principal (ListaTarefas); observe o agrupamento dos botes (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-visualizao 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 prottipo depois de configurada com os cones

Figura 11. Configurando mnemnicos e aceleradores no NetBeans (propriedades correspondentes destacadas em amarelo)

Figura 12. Os menus da aplicao completamente configurados

Figura 13. Dilogo de edio de tarefas, rascunhado com o Null Layout

17

Figura 14. Customizador de layout do dilogo de edio de tarefas, aps a converso para um GridBagLayout

Figura 15. Forma final do dilogo de edio de tarefas

Figura 16. Customizador do GridBagLayout do dilogo de edio de tarefas

18

Figura 17. Aplicao Todo com o look-and-feel GTK do Linux Listagem 1. Modificaes na classe principal da aplicao 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. Cdigo vinculado ao evento actionPerformed no primeiro boto da barra de ferramentas, para exibir o formulrio de edio 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 aplicao visual para facilitar extenses e manutenes. Nesta edio, damos continuidade construo da aplicao iniciada na edio anterior, apresentando conceitos fundamentais de desenvolvimento Java e recursos da srie 4.x do IDE livre NetBeans. A primeira parte foi focada na programao visual e em como o NetBeans pode ser utilizado para prototipar uma interface com usurio baseada no Swing, alm de mostrar caractersticas dos principais gerenciadores de layout. Nesta segunda parte, passamos ao editor de cdigo. Vamos customizar o visual da tabela que exibe as tarefas, para indicar com cores diferentes tarefas completadas, atrasadas ou em estado de alerta. Tambm 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 cdigo orientado e objetos em cdigo espaguete. Arquitetura MVC em aplicaes Grficas Durante a prototipao da interface grfica, buscamos ficar o mximo possvel dentro do editor visual do NetBeans. O objetivo era apenas criar uma casca visual para a aplicao que pudesse ser avaliada e discutida com os usurios. Agora vamos comear a colocar lgica por trs dessa casca, permitindo avaliar e testar a real funcionalidade da aplicao. comum, no desenvolvimento de aplicaes visuais, acabar gerando cdigo desorganizado, onde uma modificao em qualquer parte gera efeitos colaterais nos locais mais inesperados; ou onde a simples adio de uma informao extra exige uma cascata de mudanas em vrias partes da aplicao. Para evitar isso, vamos adotar uma arquitetura muito popular em aplicaes interativas, a arquitetura MVC (Movel-View-Controller, Modelo-Viso-Controlador). A MVC foi usada ou descrita em vrios artigos na Java Magazine (em maior parte no contexto de aplicaes web). Nela, cada classe tem um papel bem definido: tratar da exibio das informaes (Viso); responder a aes do usurio (Controlador); ou cuidar da consistncia e persistncia dos dados (Modelo). Classes com papis diferentes so praticamente independentes entre si, e assim possvel modific-las sem medo de gerar efeitos colaterais. O uso da arquitetura MVC tambm torna fcil identificar onde fazer cada mudana. Para tornar o uso da arquitetura bem explcito, iremos organizar as classes Java em trs pacotes: todo.visao, todo.controle e todo.modelo. No pacote visao esto classes grficas (como janelas ou componentes personalizados), ou classes necessrias para o seu funcionamento. Essas classes no tomam decises a respeito de como uma operao deve ser realizada este o papel das classes do pacote controle, as quais efetivamente respondem aos eventos do usurio (como cliques num item de menu) e decidem qual operao realizar. J as classes do pacote modelo representam os dados da aplicao, e contm a inteligncia necessria para realizar aes sobre esses dados.

Figura 1. Dependncias 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 vrias classes)1. Observe que o Controlador fica no meio do caminho entre a Viso e o Modelo2. Essa separao traz um benefcio adicional: ela permite que uma mesma Viso seja reutilizada com vrios Modelos contendo as mesmas informaes mas obtendo essas informaes 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 usurio (que faz parte da Viso): as classes do Modelo podem, por exemplo, ser utilizadas depois, numa aplicao web ou num MIDlet J2ME. Inteligncia em objetos No incio da Orientao a Objetos, era comum defender-se a idia de que um objeto deveria conter toda a intelignciarelacionada a ele. Mas com o passar do tempo verificouse que essa estratgia poderia levar a objetos gordos, concentrando mutas funcionalidades, e com manuteno difcil e baixo desempenho. Hoje, no entanto, se considera uma alternativa vlida criar tambm objetos burros, que apenas trafegam informaes de um objeto inteligente para outro, deixando-os mais independentes entre si. No nosso caso, os objetos burros sero Value Objects (VOs)( que so JavaBeans, com atributos e seus mtodos get/set, e possivelmente alguma funcionalidade localizada). E os objetos inteligentes so os objetos de Viso, Modelo e Controle. A nica classe VO de que necessitamos agrupa todas as informaes de uma tarefa, ento este ser o seu nome. Na nossa aplicao, teremos classes de Viso 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, so as operaes implementadas no Modelo que determinam quais informaes devero estar nos VOs.

Arquitetura da aplicao Nossas classes de Viso 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 iro expor para o Controlador. Precisamos definir os eventos, que representam aes realizadas pelo usurio, e os mtodos para manipular informaes encapsuladas em VOs. Teremos apenas uma classe no Controlador, chamada ConsultaEditaTarefas. Em aplicaes mais complexas, poder haver vrias classes controladoras. Uma estratgia para comear criar um controlador para cada conjunto de operaes consultar-editar-apagar da aplicao. Nosso exemplo tambm ter apenas uma classe no Modelo, chamada GerenciadorTarefas. Esta classe um DAO (Data Access Object) e ser responsvel por ler e atualizar registros no banco de dados. Aplicaes mais complexas tero classes de Modelo que encapsulam processos de negcios em vez de apenas entidades de informao. E iro para o Controlador apenas estas classes de mais alto nvel, 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 tm dependncias em relao ao VO Tarefa. (Mas como este no tem inteligncia significativa, ele com freqncia omitido em diagramas de classes, e na avaliao de dependncias entre classes da aplicao).

Comeando a construo

21

Nossa idia prosseguir construindo a aplicao de cima para baixo. Iniciamos pela interface com o usurio (Viso) e vamos descendo at chegar logica de banco de dados (Modelo). Em cada etapa ser feito o mnimo de trabalho necessrio para que seja possvel testar uma nova funcionalidade. Assim, nossos objetos de modelo sero por algum tempo verses temporrias, mantendo os dados em memria sem acessar o bando de dados. A classe Main (no representada na Figura 2) permanece como sendo a classe principal da aplicao, e do projeto no NetBeans. Seu papel instanciar e conectar as classes de Viso, Modelo e Controle. A Listagem 1 apresenta a classe Tarefa e a Listagem 2, a classe Main. Note que Tarefa fornece alguns mtodos utilitrios relacionados com a manipulao das datas de concluso e prazo de alerta. Parece ir contra a recomendao de que um VO no deve ter inteligncia, mas estes mtodos no dependem de nada externo ao prprio objeto, ento este o seu lugar. A Listagem 3 apresenta a classe GerenciadorTarefas, que implementa um nico mtodo: listaTarefas(). Neste ponto, o mtodo constri um java.util.List contendo objetos Tarefas pr-fabricados para que seja possvel testar o cdigo da Viso e do Controlador. Voc pode gerar os mtodos get/set automaticamente no NetBeans. Depois de definir os atributos da classe Tarefa, clique com o boto direito no editor de cdigo (ou na viso de projeto do NetBeans) e escolha Refactor|Encapsulate Fields. A Listagem 4 mostra a primeira verso da classe controladora ConsultaEditaTarefas. Tudo o que ela faz no momento passar para a Viso (ListaTarefas) a lista de objetos Tarefa retornada pelo Modelo (GerenciadorTarefas). Note que as classes de Viso devem ter mtodos para receber as tarefas, e responsabilidade da Viso repassar os dados das tarefas para componentes visuais internos, como caixas de texto e componente JTable.

Figura 2. Principais classes da aplicao Lista de Tarefas Modelo da aplicao e models do Swing Na maioria dos toolkits grficos, 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 clula a clula). Esta abordagem tem dois problemas: aumento do consumo de memria, pois os dados so duplicados na tabela; e maior tempo gasto para inserir um conjunto extenso de dados. No Swing, entretanto, os dados para exibio num componente de tabela (JTable) devem se passados num classe que implementa a interface TableModel. Esta classe fica responsvel por fornecer para a tabela os dados das clulas visveis, e em informar quando os dados forem modificados. Com isso, no so acrescentadas ou alteradas linhas na tabela em si; essas operaes so 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 comear rapidamente, mas o recomendado mesmo criar o seu prprio TableModel, que acessa diretamente os VOs. Dessa forma no h duplicao de dados e se obtm 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 prprio componente armazenar as informaes 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 no coincidncia. Para uma discusso 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

// ... mtodos get/set } Listagem 2. Classe principal da aplicao package todo; import import import import javax.swing.*; todo.visao.ListaTarefas; todo.controle.ConsultaEditaTarefas; 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) { // no 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 instncias 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 descrio 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 Viso, permanecendo invisveis ao restante da aplicao. A Listagem 5 apresenta a classe TarefasTableModel, que permite a exibio dos dados de uma List em um JTable. A Listagem 6 mostra as modificaes feitas na classe ListaTarefas para utilizar este TableModel. (O mtodo getValoresTarefa() adicionado nossa classe model ser necessrio depois para customizar a formatao 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 declarao dos mtodos a serem redefinidos ou implementados. Chegamos ao ponto em que possvel executar a aplicao e testar o seu funcionamento. O conjunto de classes criadas at o momento ilustrado na Figura 3, que uma captura da viso 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 aplicao aps a insero de um TableModel customizado

Listagem 5. TableModel para exibio de um List

package todo.visao; import import import import java.util.*; java.text.*; javax.swing.table.*; 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. Modificaes 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)); } // cdigo gerado pelo editor visual do NetBeans } Customizando as colunas da tabela Visualmente, a janela mostra na Figura 4 foi um retrocesso em relao ao prottipo construdo no artigo anterior, pois no apresenta mais os ttulos das colunas. Isso aconteceu porque, o usar um TableModel customizado, toda a configurao feita no editor visual do NetBeans sobre um DefaultTableModel passa a ser ignorada durante a execuo da aplicao. Algumas configuraes de colunas poderiam ser inseridas no prprio TableModel; entretanto, outras, como a largura das colunas, exigem a criao de um ColumnModel, que agrega por sua vez uma coleo de objetos TableColumn (cada TableColumn indica o ttulo da coluna, sua largura, se ela pode ser redimensionada etc.). Optamos ento por colocar todas as configuraes 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 ttulos Prioridade, Alerta? e Data de Concluso, com larguras pr-fixadas e grandes o suficiente para exibio do seu contedo. Queremos tambm que toda a largura restante na tabela seja ocupada pela coluna de descrio. 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 padro do Swing em seu sistema operacional). Infelizmente um ColumnModel no tem acesso ao seu JTable, de modo que foi necessrio passar o objeto FontMetrics da fonte no construtor para o ColumnModel. Usamos a largura dos caracteres 0 e M como referncia para a largura dos campos, e tivemos o cuidado de garantir que as colunas coubessem seus ttulos. A Listagem 8 apresenta as modificaes necessrias na classe ListaTarefas para utilizar o novo ColumnModel; a Figura 5 mostra o resultado da execuo da aplicao modificada.

27

Ainda falta ajustar a formatao individual de cada clula, o que ir exigir a criao de uma terceira classe auxiliar para o componente JTable.

Figura 4. Aplicao utilizando o novo TableModel

Figura 5. Visual com colunas customizadas

Listagem 7. Customizao 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, Descrio)); addColumn(criaColuna(2, 3 * letra, fm, false, Alarme?)); addColumn(criaColuna(3, 10 * digito, fm, false, Concluso));

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)); }

// ... cdigo gerado pelo editor visual do NetBeans } Renderizando clulas O JTable um componente genrico para exibio de dados numa grade, que pode ser adaptado a duas situaes distintas: quando os dados so uniformes dentro de cada coluna (tpico de aplicaes de banco de dados). quando cada clula numa mesma coluna pode ser um tipo diferente (como numa planilha eletrnica). Nossa aplicao se enquadra na primeira situao. Em ambas as situaes, a formatao 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 situao) ou a um tipo especfico de dados (segunda situao)3. Optamos por definir um nico renderizador de clula 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 clula for desenhada, ser acionado o TableCellRenderer configurado para o tipo ou para a coluna. Esse renderizador deve retornar um JComponent que ser responsvel pela formatao do valor, a nossa implementao, assim como a padro do Swing, utiliza um nico JLabel para o desenho de todas as clulas. Desta forma um JTable com milhares de clulas no ir criar o mesmo nmeros de objetos, sendo eficiente em consumo de memria e CPU. Nosso objetivo configurar a aparncia das linhas como um todo e no apenas de clulas individuais. Por isso necessrio primeiro referenciar o JTable que chama o renderizado; depois acessamos o TableModel e obtemos a Tarefa correspondente linha da clula sendo desenhada. O mtodo corTarefa() utiliza os mtodos isAtrasada(), isAlertaAtivo(),e isConcluida() de Tarefa para selecionar uma cor de fundo diferente para cada linha. O cdigo de cores utilizado o seguinte: vermelho:tarefa atrasada, isto , que j passou da sua data de concluso

29

amarelo: tarefa em alerta, isto , que foi configurada para gerar alertas alguns dias antes da sua data de concluso azul: tarefa concluda branco: tarefa que no est em nenhum dos outros estados. Caso a clula esteja selecionada, utilizada a cor cinza escura para o fundo da clula e o cdigo de cores utilizado para a cor de texto. Listagem 9. Renderizador para as clulas 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 customizao 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 contrrio, poder haver uma combinao ilegvel, devido s cores definidas pelo tema dos sistema operacional, ou do look-and-feel escolhido pelo usurio.

Figura 6. Aparncia com TableCellRenderer customizado A mesma classe TarefasCellRenderer utilizada para renderizar todas as colunas. Por isso foi criado o mtodo 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 situao do projeto com as duas classes adicionadas, o modelo de colunas e o renderizador de clulas. Agora a listagem de tarefas est em sua forma final, podemos dar interatividade aplicao.

Eventos internos e externos O prximo 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 aplicao.

Figura 7. Classes da aplicao ao fim da customizao do JTable

31

Podemos classificar os eventos da interface com o usurio em duas categorias: Eventos internos So consumidos pelas classes de Viso e no necessrio repass-los ao Controlador. Eventos internos afetam apenas a aparncia e o estado das classes de Viso; eles no levam execuo de mtodos do Modelo. Por exemplo, ao selecionar uma tarefa na lista, devem ser habilitados os botes de editar, remover e marcar como concluda. Quando no houver tarefa selecionada, esses botes devem ser desabilitado, tudo sem passar pelo Controlador ou Modelo. Eventos externos Correspondem s operaes realizadas pelo usurio na aplicao, devendo, portanto, ser repassados s classes de Controlador. Estas, por sua vez, respondem chamando mtodos das classes de Modelo e depois comandando a atualizao dos dados exibidos na Viso. Por exemplo, a adio de uma nova tarefa envolve a chamada ao mtodo na classe do Modelo que realiza esta operao, seguindo pela entrega de uma novas lista de tarefas para a classe de Viso 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 mtodo para o tratamento do seu evento padro (geralmente o evento Action) e abre o editor de cdigo para que possa programar o corpo do mtodo. J os eventos externos devem ser encaminhados ao Controlador. Eventos internos: seleo de linhas na tabela Vamos iniciar pelos eventos internos, que so mais simples por envolverem cdigo em apenas uma classe. Vamos controlar o estado dos botes e menus da classe de Viso ListaTarefas. A operao de edio ser habilitada apenas quando houver uma nica tarefa selecionada; j a marcao e a remoo podem atuar sobre mltiplas tarefas selecionadas, portanto sero habilitadas mesmo havendo uma seleo mltipla. J os botes de adicionar tarefa, de exibir tarefas concludas, e os de ordenao podem estar sempre habilitados. No ser possvel criar o cdigo para o tratamento dos eventos de seleo atravs do editor visual do NetBeans. O motivo que os eventos no so gerados pelo componente JTable, mas pelo objeto ListSelectionModel vinculado a ele e este objeto no acessvel pelo inspetor no editor visual. Ento temos que escrever o cdigo do listener para o evento de seleo, incluindo a lgica que habilita ou desabilita os botes apropriados, e no esquecendo de alternar tambm o estado dos itens de menus correspondentes. A Listagem 10 apresenta as mudanas necessrias na classe ListaTarefas. fcil identificar outros eventos internos das duas classes de Viso. Por exemplo, em EditaTarefa, a marcao do checkbox GerarAlerta deve habilitar o campo de texto diasAlerta. Ou ento, o boto Cancela, que simplesmente fecha (dispose()) o JDialog, sem gerar nenhum outro evento para o controlador. Deixamos a codificao destes eventos simples como exerccio para o leitor. Neste ponto, compile e execute a aplicao e verifique o seu funcionamento antes de avanar.

Listagem 10. Modificaes em ListaTarefas para habilitar e desabilitar os botes e menus conforme a seleo 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); }

// ... cdigo gerado pelo editor visual do NetBeans }

Eventos externos: adicionando e editando tarefas Para no poluir as classes de Viso com dezenas de mtodos no estilo addEditarTarefaListener(), addAdicionarTarefaListener etc. decidimos expor em cada classe de Viso 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 operao foi requisitada pelo usurio, 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 modificaes nas classes ListaTarefa para gerar o evento (lembre-se de configurar os actionCommands no editor visual). A Listagem 13 apresenta as modificaes no dilogo EditaTerefas, de modo que os botes de Salvar e de Remover tambm gerem eventos ActionEvent para o controlador. Como o cdigo de gerenciamento dos listeners e de gerao de evento o mesmo para ambas as classes, ele foi movido para uma classe utilitria ActionSupport, apresentada na Listagem 14. Observe que o Controlador que sabe se o dilogo foi instanciado para adio de uma nova tarefa, ou para a edio de uma nova tarefa existente. Mas o dilogo necessita receber esta informao para desabilitar o boto 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 nvel da aplicao, em vez de erros de baixo nvel como IOException ou NumberFormatException. Note que

33

as reas de mensagens das duas classes de Viso so utilizadas para exibir a mensagem de erro fornecida pelas excees vindas do Modelo.

Listagem 11. Classe controladora modificada para tratar da adio e edio 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(No 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. Modificaes 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

} // ... Cdigo 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 no uma classe de Viso independente. Assim tambm podemos manter o dilogo aberto em caso de erros de validao dos dados digitados pelo usurio. No final das contas, temos no cdigo do controlador um grande switch, onde feita a deciso de qual mtodo da classe de Modelo chamar baseado no valor do actionCommand do evento. Numa aplicao maior, esse cdigo poderia ser parametrizado, como sugere o quadro Frameworks MVC para aplicaes grficas. Mais uma vez estamos em condies de rodar a aplicao e testar a funcionalidade recm-acrescentada. A forma final do projeto est na Figura 8. Estamos quase com a aplicao pronta, exceto pelo fato de que as edies so perdidas quando ela encerrada. A soluo para essa e outras questes ser o assunto da prxima parte.

Figura 8. Classes da aplicao aps o tratamento dos eventos de adicionar e editar tarefas.

Listagem 13. Modificaes 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(Fornea 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); } // cdigo gerado pelo editor visual do NetBeans } Listagem 14. Classe utilitria 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); } }

Concluses Neste artigo vimos como configurar um componente JTable do Swing, que possui flexibilidade de customizao sem igual em outros toolkits grficos. Vimos tambm que a plena explorao das capacidades desse componente exige a criao de novas classes em vez de simplesmente definir propriedades no editor visual. E mostramos como tratar eventos gerados pelos componentes grficos, sem cair no cdigo espaguete - mantendo a separao de responsabilidades proposta pela arquitetura MVC. O prximo artigo da srie ir demostrar como filtrar o contedo da lista de tarefas e como armazenar as tarefas num banco de dados, alm de apresentar um componente para a seleo visual de datas. ________________________________________________________ [1] Os diagramas UML neste artigo foram criados com a aplicao Java. [2] Nesta artigo estamos usando Viso, Modelo e Controle praticamente como sinnimos para os pacotes de mesmo nome; em aplicaes mais complexas, esses pacotes seriam quebrados em vrios.livre ArgoUML (argouml.org). [3] Neste caso, que no ilustrado pela aplicao de exemplo, o vnculo configurado por meio de um atributo do prprio JTable, um Map, onde as chaves so objetos Class (como String.class, Data.class) e os valores so 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 agregao de um Controlador, que obtm dados de um Modelo, com uma Viso que fornecida pelo lookand-feel atual. Esta viso chamada de UI Delegate, indicando que ela que realmente desenha o componente no vdeo. A Figura Q1 ilustra o uso da arquitetura MVC no Swing. (Note que neste diagrama no esto representados nomes de classes ou interfaces reais, mas apenas uma descrio da arquitetura que aplicada individualmente a cada tipo de componente.) Nos casos mais simples, o programador no precisa fornecer a classe model para um componente Swing, pois o prprio componente instancia sua classe model padro (ex.: DefaultTableModel) e oferece mtodos utilitrios para armazenar valores nessa instncia. Assim o programador fica com a impresso de estar lidando com uma nica classe virtual, e essa impresso reforada num editor visual como o do NetBeans. Alguns tipos de models do Swing so compartilhados entre vrios 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). possvel tambm que um componente esteja associado ao mesmo tempo a vrios models diferentes, por exemplo um que representa os dados propriamente ditos e outro que indica os elementos selecionados pelo usurio. 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 aplicao MVC. No o caso, pois estamos lidando com nveis de abstrao diferentes (a aplicao como um todo e o toolkit grfico especfico). Caso as classes de Modelo da aplicao sejam construdas segundo os models do Swing elas ficaro dependentes da Viso, ou seja, haver uma independncia grande e indesejada do Modelo com a Viso.

Figura Q1. Viso geral da arquitetura MVC, conforme utilizada pelos componentes do Swing

Frameworks MVC para aplicaes grficas O leitor com experiencia em frameworks MVC para aplicaes web, como o Struts, ir logo imaginar que o grande switch no Controlador do nosso exemplo poderia ser substitudo por um arquivo de configurao. Esta uma das idias bsicas em frameworks par aplicaes grficas, como o NetBeans Platform (netbeans.org/products/platform). (O NetBeans Platform uma separao do cdigo do Controlador MVC do NetBeans do restante do IDE, de modo que ele possa ser utilizado em outros tipos de aplicaes.) Um exemplo popular de aplicao construda desta forma o MC4J (mc4j.sf.net), um console para gerenciamento de aplicaes que suportam o padro JMX. Vale a pena conhecer e estudar o cdigo dessa ferramenta open source. Infelizmente, frameworks MVC para aplicaes grficas ainda no atingiram o mesmo nvel de popularidade que tm em aplicaes web. Talvez isso seja por conta da facilidade em construir aplicaes simples num editor visual, sem se preocupar com a estruturao do cdigo, 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 aplicao do padroMVC. http://netbeans.org Site oficial do Netbeans

39

Parte 3: Banco de Dados e Preferncias dos Usurios Nesta parte conclumos a aplicao, implementando suporte a preferncias e acesso a banco de dados, e fazendo ajustes finais Neste artigo completamos aplicao de Lista de Tarefas (Todo), que iniciamos nesta coluna na Edio 25 um exemplo completo do desenvolvimento de uma aplicao grfica baseada no Swing, utilizando os recursos do IDE livre NetBeans. O primeiro artigo desta srie demonstrou como usar os recursos do editor visual do NetBeans para prototipar a interface como o usurio. O segundo mostrou como customizar a exibio de dados em JTable do Swing e como organizar o tratamento de eventos na aplicao, de acordo com a arquitetura Model-View-Controller. Este ltimo artigo mostra como filtrar as informaes exibidas na tabela e como ler e gravar as informaes em um banco de dados relacional. Para manter a aplicao simples de instalar e distribuir, ser utilizado o banco de dados HSQLDB (apresentado na Edio 7). Dessa forma, a aplicao final ser formada por apenas dois pacotes jar: o Todo.jar gerado pelo NetBeans, contendo as classes da aplicao; 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 aplicao. E o leitor que preferir utilizar um banco de dados diferente pode consultar o quadro Experimentando com outros bancos. Arquitetura da aplicao No artigo anterior, chegamos a uma aplicao bastante funcional, que permitia edio e consulta de tarefas armazenadas em memoria, mas sem ainda persistir os dados em disco. Utilizamos a arquitetura MVC, pela qual as classes da aplicao devem atuar apenas num dos seguintes papis: gerenciamento dos dados (Modelo), visualizao dos dados (Viso), ou resposta s aes do usurio (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 aplicao de exemplo, desenvolvidas nas duas partes desta srie. As classes em azul-claro so neste artigo, e a classe GerenciadorTarefas praticamente reescrita.

No pacote todo.modelo, temos a classe Tarefa, construda com um VO (Value Object, um repositrio de dados com pouca ou nenhuma inteligncia prpria). H tambm a classe GerenciadorTarefas, cuja verso inicial tinha o objetivo de fazer uma simulao da persistncia mantendo os dados em memria, pois o foco estava nas classes de viso e controle. Nesta edio vamos expandir a classe GerenciadorTarefas para conter o cdigo de persistncia num banco de dados. Tambm ser criada uma nova classe de modelo,

40

chamada Parametros, contendo os dados de configurao da aplicao por exemplo, o diretrio onde so salvos os arquivos do bando de dados. O pacote todo.visao contm duas janelas que foram prototipadas na primeira parte, ListaTarefas (um JFrame) e EditaTarefas (um JDialog), alm de vrias classes auxiliares. A Figura 2 reapresenta as duas janelas, para que o leitor que no tenha idia da aparncia e funcionalidade da aplicao. O pacote todo.controle contm uma nica classe, ConsultaEditaTarefas, que responde s aes do usurio para criar, editar ou listar tarefas. Essas classe delega as aes em si para a classe de modelo GerenciadorTarefas e comanda a atualizao das classes de viso, quando necessrio. Neste artigo ser criada outra classe controladora, chamada CriaAbreListaTarefas, responsvel por criar novos bancos de dados existentes em outras localizaes. Ela tambm receber as funcionalidades de miscelnea da aplicao, por exemplo a exibio da caixa Sobre.

Figura 2. Janelas da aplicao Lista de Tarefas Os trs pacotes contm ainda uma srie de classes auxiliares no descritas aqui, por exemplo, excees customizadas ou modelos para o JTable do Swing. Elas no afetam a arquitetura da aplicao e na maioria dos casos so utilizadas por classes fora dos respectivos pacotes. A Figura 3 apresenta as classes da aplicao, na viso de projetos do NetBeans. O quadro Todas as classes do exemplo apresenta uma breve descrio do papel de cada uma.

Figura 3. Todas as classes da aplicao final, na viso de projeto do NetBeans.

41

Acesso ao banco de dados A nova verso da classe GerenciadorTarefas foi construda como um DAO. A idia que as demais classes da aplicao no tenham conhecimento do uso de banco de dados relacionais ou de outra tecnologia de armazenamento e recuperao de informaes elas apenas chamam os mtodos da classe DAO para ler e gravar objetos. Nosso DAO fornece mtodos como listaTarefas() e editaTarefa(), que sero chamados pelo controlador apropriado (no caso, ConsultaEditaTarefas) de acordo com a operao solicitada pelo usurio. A Listagem 1 fornece o cdigo completo dessa classe, que ser detalhado a seguir. A nova classe de modelo ir interagir com a classe Parametros para obter as configuraes de acesso ao banco de dados, que sero utilizadas em conecta() para criar uma conexo ao HSQLDB. A conexo mantida como varivel de instncia (atributo) da classe DAO. fornecido tambm o mtodo desconecta(), para que a aplicao possa fechar a conexo quando for encerrada. O prprio HSQLDB cria automaticamente uma base de dados vazio no momento da conexo, caso o arquivo referenciado na URL JDBC no exista. Como padro, estamos utilizando a base dB/todo (que corresponde ao arquivo dB/todo.script) no diretrio pessoal do usurio, que ser o $HOME em sistemas Linux ou a pasta correspondente em sistemas Windows. Para simplificar a utilizao, o prprio DAO ir criar as tabelas na base de dados, caso elas no existam. Isso feito pelos mtodos privativos existemTabelas() e criaTabelas(). Visando simplificar a escrita dos mtodos de consulta e alterao de dados propriamente ditos, so fornecidos os mtodos auxiliares finaliza(), executa(), prepara(), consulta() e altera(), que encapsulam seqncias comuns chamadas JDBC. Todos eles, exceto os dois ltimos, poderiam ser movidos para uma superclasse abstrata numa aplicao com vrios DAOs. Os mtodos consulta() e altera() seriam praticamente iguais, variando apenas quanto aos nomes dos campos, em outras classes DAO de uma aplicao maior. O mtodo finaliza() tem importncia especial, pois temos que garantir que os recursos do banco de dados sejam liberados o quanto antes, mesmo que ocorram excees durante o acesso. Todos os mtodos de acesso ou alterao devem conter uma clusula finally que chama finaliza() (veja por exemplo o mtodo executa()). Os mtodos especficos de acesso e alterao de dados adicionaTarefa(), editatarefa(), marcaComoConcluida(), removeTarefa(), listaTarefas() e listaTarefasComAlarme() so todos escritos chamando a finaliza() garantia em caso de erros. Por fim, o DAO fornece o mtodo validaTarefa(), que chamado pelo controlador antes de se inserir ou modificar uma tarefa. Isso permite que a operao seja abortada, e o usurio informado de que houve erros na digitao dos atributos da tarefa. Observe que os mtodos da classe de todo.modelo, nunca retornam excees de baixo nvel ao controlador, por exemplo SQLException ou IOException. So retornadas subclasses de ModeloException, como a BancoDeDadosException. Estas classes, por sua vez, encapsulam a exceo de baixo nvel como causa (acessvel pelo mtodo getCause() de Exception), de modo que a informao esteja disponvel durante a depurao da aplicao. Isso foi feito para que o controlador fique completamente isolado de qualquer conhecimento relativo ao banco de dados ou outro mecanismo de persistncia. O mesmo estilo de programao poderia ser utilizado caso fosse empregada outra forma de persistncia, por exemplo arquivos XML.

42

Listagem 1. Classe de modelo GerenciadorTarefas package todo.modelo; import import import import import java.io.*; java.sql .*; java.text.*; java.util.List; 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( "No foi possvel criar as tabelas no banco de dados", e.getCause()); } catch (ClassNotFoundException e) { throw new BancoDeDadosException( "No foi possvel carregar o driver do banco de da dos" , e); } catch (SQLException e) { throw new BancoDeDadosException( No foi possvel 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 exceo */} } private void finaliza() { try { if (rs != null) rs.close() ; rs = null; if (stmt != null) stmt.close(); stmt = null; } catch (SOLException e) {/* ignora a exceo*/} } private void executa(String sql) throws BancoDeDadosException{ try { stmt = con.createStatement(); stmt.executeUpdate(sql ); } catch (SQLException e) { throw new BancoDeDadosException( "No foi possvel 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{ "No foi possvel 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( "No foi possvel 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 lgica seqencial nos mtodos de tratamento de eventos de ambientes RAD, podem ficar um pouco perdidos com a diviso de responsabilidades imposta pela arquitetura MVC. Para tornar as coisas mais claras, a Figura 4 apresenta um diagrama de seqncia UML para a operao de adicionar tarefa da aplicao. O processo disparado pelo usurio que clica no boto da barra de ferramentas (ou no item de menu correspondente) da viso ListaTarefas. Isso gera um evento, que capturado pelo controlador ConsultaEditaTarefas. O controlador ento exibe o dialogo de edio, que outra viso (EditaTarefas), e o controle retorna para o usurio.

Figura 4. Diagramas de seqncia para a criao de uma nova tarefa. Depois de digitar os dados, o usurio clica no boto Salvar, que provoca o envio de um novo evento, tambm capturado pelo controlador. Em resposta a este segundo evento, o controlador chama o mtodo apropriado da classe DAO, que pode ser adicionaTarefa() ou editaTarefa(). Se houver alguma exceo, o controlador exibe a mensagem de erros na rea reservada para isso no prprio dilogo de edio. Se no houver nenhuma exceo, ele fecha o dilogo e atualiza a viso. Caso o usurio cancele o dilogo de edio, no ser gerado nenhum evento para o controlador, e o dilogo ser simplesmente descartado. Algumas operaes, como remover tarefas e marcar tarefas como concludas atuam sobre uma seleo contendo mltiplas tarefas. Neste caso, o controlador simplesmente chama o mtodo removerTarefa() ou maracrComoConcluida() do modelo num loop for. Para mais detalhes, veja a Listagem 2. Nela est o cdigo do controlador ConsultaEditaTarefas, que tem poucas alteraes com relao verso do artigo anterior.

47

Listagem 2. Classe controladora ConsultaEditaTarefas package todo.controle; import import import import import import import java.util.*; java.text.DateFormat; java.awt.Cursor; java.awt.event.*; javax.swing.*; todo.visao.*; 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 concluso:\n" + df.format(tarefa.getDataConclusao()) + "\n" + "[" + tarefa.getDescricao() + "J", "Alerta", JOptionPane.INFORMATION_MESSAGE); } if (tarefas.size() == 0) { visao.setMensagem( "No h nenhuma tarefa em alerta no momento.",false); } } catch (ModeloException e) { editaTarefaDialog.setMensagem(e.getMessage(), true); } } } Filtragem de informaes na tabela Agora que nossa aplicao mantm a lista de tarefas entre uma execuo e outra, a utilizao se torna muito mais interessante, mas o leitor poder sentir a falta de recursos para filtrar as tarefas. O prottipo criado na primeira parte prev um nico filtro para a lista de tarefas, que exibir (ou no) as tarefas marcadas como concludas, e que caso sejam exibidas sero apresentadas em azul-claro. Alm deste filtro, foi prevista a ordenao das tarefas por prioridade ou por data de concluso, ambas em ordem crescente. H duas estratgias bsicas para fazer a filtragem e a ordenao: pela prpria viso, sem afetar o controlador ou o modelo; ou pelo modelo, de modo a aproveitar os recursos do banco de dados. Filtrar na viso simplifica o modelo (com o custo de deixar a viso mais complexa), mas faz sentido conceitualmente, pois estamos falando apenas de como as informaes so apresentadas para o usurio, e no de regras de validao ou consistncia de dados. J filtrar no modelo, pode ser necessrio caso o volume de dados seja grande, ou os critrios de filtragem, muito complexos. Tambm pode ser mais fcil de programar, pois o SQL permite escrever cdigo de consulta e alterao 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 memria (dentro da classe visao.TarefasTableModel). Nossas operaes de filtragem e ordenao so facilmente escritas em Java, utilizando a API padro de colees, e o usurio tem retorno quase instantneo coisa difcil de se obter com um banco de dados.

50

Adotamos ento a estratgia de fazer a filtragem na viso. Desse modo os eventos do menu Opes so capturados e tratados pela prpria classe de viso ListaTarefas, em vez de serem repassados para o controlador. O cdigo que chama as operaes de ordenao e filtragem simples e poderia ter sido escrito diretamente no mtodo de tratamento de eventos gerado pelo NetBeans (com um clique duplo sobre os botes e itens de menus) mas como temos a mesma operao sendo realizada por menus e botes, mais conveniente criar mtodos auxiliares. Assim garantimos que o status do menu e da barra de ferramentas esteja consistente e evitamos repetio de cdigo, o que sempre uma boa idia, por menor que seja o cdigo duplicado. Criamos os mtodos ordenaPorPrioridade() e mostraConcluidas(), chamados pelos mtodos de tratamento de eventos botaoOrdenarActionPerformed(), botaoMostrarActionPerformed(), menuMostrarActionPerformed(), menuOrdenarPrioridadesActionPerformed() e menuOrdenarDatasActionPerformed(), que so gerados pelo NetBeans para os eventos dos itens de menu. A ordenao e filtragem em si realizada pela classe TarefasTableModel, que j era a responsvel por fornecer e formatar os dados para o JTable contendo as tarefas listadas. As modificaes em TarefasTableModel em relao ao artigo anterior so indicadas em negrito na Listagem 3. Listagem 3. Modificaes na classe TarefasTableModel package todo.visao; import import import import java.util.*; java.text.*; javax.swing.table.*; 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 aplicao pronta. Faltam apenas dois itens do menu Arquivo: um cria uma nova lista de tarefas armazenada em outro arquivo que no o ${user.home}/dB/todo; outro abre listas de tarefas armazenadas em outros arquivos. Este um caso claro de preferncias dos usurios. Uma aplicao mais complexa certamente iria armazenar outras preferncias (deixamos como exerccio armazenar as selees do menu Opes). Como as preferncias tambm devem ser informaes persistentes, criamos uma classe de modelo para lidar com elas. Da a classe Parametros j citada. Ela fornece mtodos get e set para preferncias individuais e construda utilizando a Preferences API (para mais sobre essa API, veja o artigo de Paloma Sol na Edio 10). A classe Parametros bastante simples, como vemos na Listagem 4. Observe que a API de preferncias exige que a aplicao indique os valores padro para cada preferncia, pois apenas valores diferentes sero efetivamente armazenados. Para isso so definidas vrias constantes no incio da classe. Um caso especial o valor para a preferncia chamada database, gerado dinamicamente a partir do diretrio pessoal do usurio. Chegamos ao ponto em que necessrio acrescentar um segundo controlador aplicao, a classe CriaAbreListaTareafas. Sua funo lidar justamente com as opes do menu Arquivo, visto que elas no tm relao direta com o ciclo criar-editar-removerconsultar para as tarefas. Visando simplificar a aplicao, este controlador tambm lida com outro item de menu, ainda no coberto, o Ajuda/Sobre. No h necessidade de criar classes de visao auxiliares para estas operaes, pois as APIs JOptionPane e JFileChooser so sufientes para esta aplicao. Seria possvel definir visualmente no NetBeans as caixas Sobre e Abrir arquivo, mas no nosso caso desnecessrio, pois apenas configuramos propriedades como o ttulo dos dilogos padro, sem acrescentar novos componentes. Acaba portanto sendo bem mais simples chamar diretamente as APIs. A Figura 5 apresenta a aparncia dos dilogos Sobre e Abrir Lista de Tarefas. O dilogo Criar Lista de Tarefas similar a este ltimo.

Figura 5. Caixa Sobre e dilogos de Abrir e Nova Lista de Tarefas. O cdigo 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 dilogos de abrir e salvar listas de tarefas, garantindo que sejam exibidos apenas pastas e arquivos cujo nome contenham a extenso .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 import import import import import java.io.File; java.awt.Cursor; java.awt.event.*; java.io.IOException; javax.swing.*; 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 "No foi possvel 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 exibio de alertas para tarefas prximas das datas de concluso. Esta operao representada pelo cone da barra de ferramentas, e tambm executada no incio da aplicao para chamar a ateno do usurio. A relao 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 viso. Um exemplo desta caixa apresentado na Figura 6.

Figura 6. Alerta avisando sobre uma tarefa prxima da sua data de concluso A Listagem 5 exibe a classe todo.Main alterada para prever a funcionalidade dos alertas. Ela instancia as classes de Modelo, Controlador e Viso e os conecta por meio dos seus construtores. Em seguida esta classe comanda a verificao dos alertas, utilizando o mtodo j apresentado no controlador ConsultaEditaTarefas. A classe Main tambm verifica se algum argumento foi fornecido na linha de comando. Sendo fornecidos vrios argumentos, a aplicao 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. No sendo fornecido nenhum argumento de linha de comando, utilizado o bando de dados padro do usurio, de acordo com suas preferncias. Vale destacar a importncia do suporte linha de comando em uma aplicao grfica. Na nossa aplicao, por exemplo, isso permite que seja criado um atalho/lanador na rea de trabalho do usurio 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 usurios Invocado o comando Build/Clean and Build Main Project no NetBeans, podemos gerar o pacote dist/Todo.jar, um jar executvel para distribuio. Entretanto, como adicionamos o suporte a banco de dados, este pacote no ser mais suficiente para a execuo da aplicao. Uma arquivo .zip hsqldb.jar). ficaro nem estratgia de distribuio muito utilizada nesse caso fornecer um nico contendo o pacote jar executvel e os jars das bibliotecas utilizadas (ou seja, o Com isso, ao descompactar o .zip de distribuio da aplicao, todos os jars mesmo diretrio.

No ser necessrio criar um executvel nativo, ou um script (.bat, .cmd, .sh etc.) para configurar o classpath da aplicao; basta incluir dentro do jar executvel uma referncia aos pacotes de bibliotecas necessrios.

57

Isso feito modificando-se o arquivo de manifesto do pacote jar. Para configurar o manifesto, entre na viso 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 executvel na viso 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 Ento reconstrua (menu Build) o pacote da aplicao, e copie tanto esse pacote quanto o hsqldb.jar para uma pasta qualquer do sistema. Em seguida, execute a aplicao no prompt de comandos: java-jar/caminho/para/aplicacao/Todo.jar Veja como agora a aplicao iniciada corretamente. O mesmo comando pode ser utilizado num atalho/lanador na rea de trabalho do usurio. Concluses Encerramos aqui nossa srie sobre desenvolvimento Java/Swing, com o NetBeans. Outras pequenas modificaes feitas na verso final da aplicao, disponvel para download, so comentadas no quadro Ajustes diversos, onde tambm so fornecidas algumas dicas, por exemplo como detectar um clique duplo. Se voc est com a sensao 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 no fica dependente do IDE utilizado. Exceto pela prototipao visual dos formulrios, 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 srie como o NetBeans oferece recursos suficientes para que o desenvolvedor considere o seu uso em substituio a outros IDEs Java no mercado. No apenas as facilidades visuais, mas tambm o suporte ao Ant, a estrutura de projetos, recursos de refatorao de cdigo, depurador visual e auto-completar cdigo, alm da interface flexvel 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 integrao de redes, desenvolvimento de sistemas e tuning de banco de dados. tambm conselheiro do Linux Professional Institute do Brasil e autor do livro Java em GNU/Linux, alm de lder da comunidade Linux no portal Java.net. Configurando o projeto no NetBeans O bando de dados HSQLDB pode funcionar embutido num aplicao Java, sem criar novos threads nem abrir portas TCP. uma excelente alternativa para aplicaes de automao de escritrio, tanto que o HSQLDB foi incorporado verso 2.0 do OpenOffice em seu novo componente de banco de dados. Neste artigo, foi utilizada a verso 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 aplicao, j que a aplicao ir criar automaticamente o banco de dados e as tabelas necessrias. De volta ao NetBeans, clique com o boto direito no aplicao, na viso de projetos; selecione a opo 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 dilogo de propriedades. Caso deseje inspecionar o banco de dados da aplicao, voc pode utilizar o navegador JDBC incluso no NetBeans. (Para mais informaes sobre esse recurso, consulte o artigo O Novo NetBeans, na Edio 24.)

Figura Q1. Adicionando a biblioteca do HSQLDB ao classpath da aplicao Experimentando com outros bancos As caractersticas da aplicao 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 aplicao proposta no tem nenhuma dependncia com o HSQLDB em particular. Caso o leito deseje experimentar com outros bancos de dados, ser necessrio modificar as configuraes relativas conexo 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 criao do banco de dados vazio. Se for adotado um banco cliente/servidor, no far sentido fornecer as funcionalidades de Abrir lista de tarefas e Criar lista de tarefas, que pressupem hum banco de dados baseado em arquivos locais. Em vez disso, poder ser fornecido um dilogo simples para edio dos parmetros de conexo. Mas essas facilidades permanecero

59

vlidas para outros bancos de dados baseados em arquivos, como o Derby ou McKoi: ser necessrio apenas mudar os filtros de arquivos para extenses apropriadas). Todas as classes do exemplo todo.Main Fornece o mtodo main() para iniciar a aplicao. todo.controle.ConsultaEditaTarefas Para edio e consulta a tarefas, o principal controlador da aplicao. todo.controle.CriaAbreListaTarefas Para criar e abrir um arquivo (do HSQLDB) de uma lista de tarefas, e coordenar outras operaes gerais. todo.modelo.BandoDeDadosException Indica um erro qualquer de banco de dados. todo.modelo.GerenciadorTarefas a classe de modelo principal da aplicao, 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 exceo do pacote todo.modelo. todo.modelo.Parametros Classe de modelo para parmetros da aplicao, 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 validao na digitao dos dados de uma tarefa. todo.visao.ActionSupport Superclasse abstrata das classes de viso, fornece mtodos utilitrios para a gerao de eventos actionPerformed para classes de controle, seguindo o modelo de eventos do Swing/AWT. todo.visao.EditaTarefa Dilogo (JDialog) para a entrada dos dados de uma tarefa, seja na sua adio ou edio. todo.visao.ListaTarefas Janela principal (JFrame) da aplicao, exibe uma lista de tarefas em um componente JTable. todo.visao.TarefasCellRenderer Customiza um JTable para exibir as informaes de uma tarefa em cores diferentes, de acordo com o status de concluda ou com alerta. todo.visao.TarefasColumnModel Customiza um JTable para exibir quatro colunas (Prioridade, Descrio, Alarme e Data de Concluso). As colunas tm tamanho fixo, com exceo de Descrio, que ocupa toda a largura restante. todo.visao.TarefasTableModel

60

Customiza um JTable para exibir as informaes a partir de uma lista de objetos Tarefa (List<Tarefa>). Evita converses desnecessrias dos atributos para um vetor de strings, economizando memria. Ajustes diversos Em relao aplicao prototipada na primeira parte e tornada interativa na segunda, foram feitos uma srie de ajustes. normal que, medida que uma aplicao chegue prxima a sua concluso, sejam identificados algumas pequenas questes relativas interface com o usurio, habilitao de componentes, ou posicionamento de janelas. Ajustes desse tipo esto espalhados pela aplicao e no so apresentados em listagens para poupar espao. Mas os fontes completos da aplicao final e das verses intermediarias esto disponveis para download no site da Java Magazine. Segue uma breve relao dos justes realizados: As duas vises ListaTarefas e EditaTarefas foram centralizadas, uma em relao tela e a outra em relao sua janela me, por meio do mtodo setLocationRelativeTo(), chamado no construtor. O mtodo actionPerformed() dos controladores chama setCursor() para ativar o cursor de ampulhetas (ou o cursor padro de espera na sua plataforma) e restaurar o cursor padro depois de tratar um evento (ou em caso de excees). Assim operaes potencialmente demoradas, como conectar ao banco de dados ou listar vrios registros, tero feedback visual para o usurio. O JTable e JTextField para edio da quantidade de dias para emisso de alertas na edio de uma tarefa no estavam sendo habilitados/desabilitados junto com o JCheckbox correspondente. Problema resolvido. O controlador CriaAbreListaTarefas foi registrado com um WindowListener da classe de viso ListaTarefas. Desse modo, ele garante o fechamento do banco de dados ao final da aplicao. 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 no termos utilizado recursos visuais para o cdigo de banco de dados. O fato que a biblioteca padro de classes do J2SE no inclui componentes no estilo DataControl, DBGrid etc., que aumentam inicialmente a produtividade, mas estimulam a programao de cdigo espaguete misturando lgica de acesso a dados, visualizao de dados e regras de negocio dentro da mesma classe de formulrio (janela), e acabam prejudicando a manuteno da aplicao. Embora existam componentes sililares no mercado, que podem ser acrescentados ao NetBeans (ou outros IDEs) a prtica recomendada em aplicaes Java ou construir classes DAO para acesso a dados via JDBC; ou ento utilizar um framework de mapeamento objeto-relacional com o Hibernate. Em ambos os casos, mantida a separao de responsabilidades entre as vrias classes da aplicao, e o cdigo escrito num estilo completamente orientado a objetos em vez de parcialmente procedural. Para sermos justos, temos que lembrar que nada impede que as mesmas prticas de projetos sejam empregadas no Delphi e outros ambientes RAD orientados a objetos, mas infelizmente a cultura predominante neles de no explorar classes e objetos na arquitetura da aplicao.

61