Sexta-feira, Junho 06, 2008

Tirando a Poeira

Esse texto é pra tirar poeira (montes e montes de poeira!) desse blog e dar alguma satisfação aos meus dois leitores e meio (meu irmão só vê o título e a conclusão).

Eu continuarei a escrever aqui (a partir de não sei quando) - mas sem grandes pretenções, nada de alcançar grande público (eca! Se bem que mesmo que eu quisesse, seria bem difícil), nada de textos formais (eu tentei, mas não é o meu estilo, que ainda estou procurando) - sobre desenvolvimento para Desktop (com D maiúsculo mesmo!), BrazilUtils, mapeamento O/R, entre outros assuntos.

Ultimamente eu tenho me ocupado bastante em melhorar minhas qualidades como desenvolvedor profissional, buscando certificações (bem vinda SCJP 6.0! em breve, SCWCD 5.0), terminando a facul (fazendo uma matéria por semestre, agora faltam só 3 + monografia), lendo, criando uns PETS. Além disso, continuo a praticar esportes, pedalar sempre que posso... E rezo (além de outras cositas mais!) para que a minha namorada não me dê um chute no traseiro.

Infelizmente não me é possível fazer tudo que quero, assim o blog ficou pra depois.

Um dos meus projetos preferidos, a BrazilUtils, também está nos planos. A equipe toda está ocupada com outras atividades, mas estamos sempre conversando e o projeto está bem direcionado. Também temos aprendendido muito e pensado sobre como explorar o que uma biblioteca como a BrazilUtils pode oferecer em termos de funcionalidade e implantação, mantendo a compatibilidade com o JDK 1.4.

Novidades virão! (Não sei quando, mas virão!)

Aproveito para agradecer ao pessoal que de alguma forma contribui ou contribuiu com a BrazilUtils, com sugestões, relatos e correções de bugs, etc. Acho que esse contato é a coisa mais divertida nesse projeto.


Abraços a todos (menos aos malas)! ... Leia mais!

.

Quinta-feira, Agosto 02, 2007

Existem 1000 Maneiras de Desenvolver em Swing: Invente a Sua

Entre os inúmeros rótulos que "pegou" no Swing, desde mil oitocentos e bolinha até os dias atuais, é que desenvolver aplicativos com Swing é difícil. Boa parte dos softwares em Java são voltados para web basicamente porque seu desenvolvimento é mais fácil em comparação à clientes desktop baseados em Swing.

Entre os principais motivos dessa dificuldade, estão:

  • !n formas diferentes de fazer a mesma coisa. Kirill Grouchnikov lançou uma série de artigos demonstrando vários caminhos possíveis para incluir feedback visual em componestes Swing durante validação (veja a introdução, implementação utilizando repaint manager, bordas, paint, aspectos e finalmente JXLayer
  • o tamanho da API (JButton tem 435 métodos?)
  • poucos frameworks e bibliotecas em geral
  • pouco produtivo, pois é demorado para desenvolver
  • [o seu motivo preferido aqui - já que até quem programa em Delphi tem o seu]

E eu com isso?

Talvez seja do interesse de todos os programadores javaneses que aplicativos desktop com Swing sejam bem sucedidos em termos de satisfação dos usuários e participação no mercado simplesmente porque há demanda para clientes ricos. Se não forem feitos em Java, serão em C# ou outra (ou JSF, Struts, etc. :P).

Alguém pode argumentar que por isso mesmo devemos ser poliglotas. Mas deve haver algo errado em deixar de lado essa ótima plataforma (Java) e migrar para Delphi (como alguns propõem), C# ou o que for, pela incapacidade de produzir eficazmente clientes ricos em Java.

Até quando contruir ERPs, PCPs e outros sistemas de informação empresariais para web, estes que são exatamente a espécie de sistemas que deve ter interfaces ricas, possibilidade de armazenar localmente grande quantidade de dados para tratamento pesado e geração de análises em textos, gráficos, modelagem, apoio a decisão longe dos servidores?

Mas a culpa não é minha se os bobinhos* da Sun não trabalham direito...

Com exceção do tamanho da API, há o que ser feito. Frameworks e bibliotecas estão aparecendo. A produtividade no desenvolvimento com Swing pode ser mais baixa quando comparadas com ferramentas RAD, mas há que se lembrar também da fase de manutenção.

Desenvolvedores devem trocar idéias e experiências, formar uma espécie de inteligência coletiva funcionando com o objetivo de criar melhores práticas, exemplos de softwares com boa arquitetura, que façam uso da OO, mas que sejam simples de entender, alterar, estender.

Softwares open sources com propósitos diferentes (como o ModelMat, OpenFTP [o seu preferido aqui...] deveriam ser debulhados por centenas ou milhares de pares de olhos e mentes dispostas a alcançar o melhor.

No momento, parece que estão apostando nas novidades (Java 6, frameworks...) para alavancar clientes Swing, porém isso pode não ser suficiente por adicionar mais ingredientes na salada mista. Guias podem ser o melhor caminho (Sacaram o trocadillho? Guias, caminhos? Esquece...).

A propósito, o título desse texto é exatamente o oposto do que o texto propõe.

*Quem dera eu ser bobinho como eles, trabalhar na Sun... ... Leia mais!

.

Quarta-feira, Julho 18, 2007

ModelMat e uma Melhor Arquitetura

Resolvi estudar e planejar uma boa arquitetura para o ModelMat, que acaba de ir para a versão 0.4. A concentração dos esforços se deu na separação da view e do model.

O objetivo é utilizá-lo como exemplo para uma boa arquitetura GUI Swing, mesmo sendo um exemplo limitado graças à natureza do ModelMat, que difere da maioria dos softwares comerciais. Ainda assim, deverá ajudar a mostrar o "caminho das pedras".

As principais fontes de inspiração foram o padrão Presentation Model, do Martin Fowler, e JGoodies Binding, do Karsten Lentzsch, também em muito baseado no Presentation Model, ainda que eu não tenha utilizado binding automático.

PresentationModel

De acordo com Martin Fowler,

Presentation Model transfere o estado e comportamento da view para uma classe model que faz parte da apresentação (presentation, no original). O Presentation Model interage com a camada de domínio e provê uma interface para a view que minimiza a tomada de decisões nessa view. A view armazena todo o seu estado no Presentation Model ou o sincroniza com o Presentation Model freqüentemente.
No caso do ModelMat, comportamento e estado foram transferidos da view para o model, com possível exceção dos métodos que tem estrita relação com a view. Um exemplo é o método changeColumnName, da classe Spreadsheet, cuja função é alterar o nome das colunas da planilha. (Faria sentido deslocar tal método para o model?)

A comunicação das mudanças no model para a view é feita através do disparo de eventos do model para a view. Assim, por exemplo, SpreadsheetModel dispara um evento para Spreadsheet (a view) atualizar o seu estado. Sem novidades.

O Swing Application Framework (JSR-296), com o seu gerenciamento e ligação de Action's ajudou a mover o comportamento da view para o model.

Como resultado, as views estão completamente "ignorantes". O código fonte da classe ChartView tem apenas 64 linhas, contando com sua documentação; ReportView tem 69 e Spreadsheet, a mais complexa, 228 linhas. As views apenas definem e organizam os componentes visuais e ajustam o seu model. Eis a classe ChartView (documentação omitida):

public final class ChartView extends JPanel {
.....private ChartPanel chartPanel;

.....public ChartView() {
..........super();
..........initComponents();
.....}

.....public ChartView(ChartModel model) {
..........this();
..........add(createChartPanel(model.getJFreeChart()));
.....}

.....private ChartPanel createChartPanel(JFreeChart chart) {
..........chartPanel = new ChartPanel(chart, true);
..........chartPanel.setMouseZoomable(true, false);
..........return chartPanel;
.....}

.....private void initComponents() {
..........setLayout(new java.awt.BorderLayout());
.....}
}

ChartModel define os métodos sobrecarregados reportResult, que contém toda a rotina para processar o resultado da função e mostrá-la ao usuário, e clear, que limpa a área do gráfico.

Explicando a arquitetura da GUI de forma sucinta, há quatro views: ModelMatView, a principal, é composta das demais: Spreadsheet, ReportView e ChartView. Também contém um JMenu, JToolBar e StatusBar. O Presentation Model de ModelMatView é ApplicationModel, que contém os models das demais views: SpreadsheetModel, ReportModel e ChartModel, respectivamente.

O workflow do software é basicamente definido no ApplicationModel, por isso está mais claro. Sua classe interna DoRegression é uma boa mostra de como isso acontece.

private class DoRegression extends Task<AbstractRegressionResult, Void> {

.....DoRegression() {
..........super(ModelMatApplication.getApplication());
.....}

.....protected AbstractRegressionResult doInBackground()
.........................throws CoordinateNumberException, InsufficientDataException {
..........if (selectedRegressionType == RegressionType.LINEAR) {
...............return linearRegression();
..........} else if (selectedRegressionType == RegressionType.NON_LINEAR) {
...............return nonLinearRegression();
..........}
..........return null;
.....}

.....protected void succeeded(AbstractRegressionResult result) {
..........reportModel.reportResult(result);
..........chartModel.reportResult(result);
..........setMessage("Regression succeeded.");
.....}

.....protected void failed(Throwable cause) {
..........setMessage("Regression failed.");
..........ExceptionNotifier.notifyError(cause);
.....}
}

Antes, o uso do padrão Observer para notificar as diversas operações ocorrendo no software tornava difícil o seu entendimento e depuração. Observers foram retirados onde puderam, sendo utilizados apenas para notificar views sobre mudanças nos models.

The Day After...

A única mudança perceptível para o usuário do aplicativo é a melhora na geração de gráficos. Compare o gráfico das duas figuras desse texto com o dessa figura. Esse aperfeiçoamento se deu graças ao melhor uso da API do JFreeChart. O código para gerar os gráficos está menor, mais claro e mais correto.

O resultado de um workflow mais simples e claro e de uma melhor arquitetura é um software mais fácil de entender e mais difícil de estragar.

Na mesma proporção que os componenes ficaram com responsabilidades mais bem definidas, as rotinas desnecessárias e potencialmente problemáticas foram eliminadas.
Há menos imports, classes, métodos, propriedades e código em geral.

Haviam falhas no conceito do software que tornaram-se evidentes e foram corrigidas. A antiga classe Point, por exemplo, não representava um ponto no espaço 2D, como seu nome, seus métodos e propriedades levavam a crer. Inclusive eu pensei. Point na verdade representava índices na planilha - por exemplo, a célula da primeira linha e segunda coluna tem os índices (0, 1).

Portanto, Point foi renomeada para XYTableIndex e seus dois métodos, para getRow e getColumn. De fato se faz necessário uma classe para representar as coordenadas dos pontos no espaço 2D, por isso foi criada uma nova classe Point, com seus métodos getX, getY e outros.

Aperfeiçoamentos no ModelMat com Base no Swing Application Framework

O ModelMat utiliza os benefícios do Swing Application Framework. Como o seu código fonte está no repositório do sítio do projeto, fica fácil consultar como funciona a implementação da JSR-296 na prática. Entre as funcionalidades, estão:
  • uso de multithreading para melhor resposta do software às ações do usuário (consulte org.modelmat.gui.ApplicationModel)
  • gerencimento do ciclo de vida do aplicativo (veja org.modelmat.application.ModelMatApplication)
  • estado da sessão persistente (ModelMatApplication herda essa funcionalidade de application.SingleFrameApplication)
  • barra de estado (baseada na classe application.example.StatusBar, utilizada em org.modelmat.gui.ModelMatView).
Nota: classes do pacote application e derivados fazem parte do Swing Application Framework.

Nota 2: o ModelMat também pode ser fonte de consulta para implementação de Tray Icon, Tip of the Day e JTable. Novas funcionalidades estão a caminho, como o uso da Java Desktop API e integração com planilhas eletrônicas.

Outros Melhorias

Os nomes das classes foram melhorados. Exemplo: antes, as classes responsáveis pela view chamavam-se VAlgumCoisa, como VReport e VChart; agora chamam-se AlgumaCoisaView, como ReportView e ChartView. A classe Spreadsheet, antiga VTable, é a única exceção a essa regra. Como é claro que uma planilha é uma view, acrescentar essa palavra ao fim da classe (SpreadsheetView) é redundante.

Da mesma forma, nomes de métodos estão mais precisos e concisos: clearReport, da classe ChartView, mudou para clear - suficiente.

Há maior uso de campos (i.e. utilizando o modificador final) e menor utilização de variáveis.

O software está mais documentado. Contudo, sua documentação está incompleta, especialmente a referente ao core do ModelMat.

O algoritmo de regressão foi simplificado. Antes, o uso forçado de Command (GoF), causava indireção e complexidade inúteis. Agora, o uso de Strategy (GoF), padrão mais adequado para um serviço que tem diversas implementações - exatamente o caso das regressões - tornou o processo mais simples, eficiente e elegante.

E a Produtividade Cadê?

Alguém pode indagar "tudo isso, e a única melhora para o usuário final foi uma geração de gráficos mais decente? E a produtividade, cadê ?".

A resposta merece e fica para outro texto.

Muito a Fazer

Esse foi apenas mais um passo na direção certa. Um grande passo considerando que mover o comportamento da view para o model geralmente não está entre as tarefas mais simples em aplicativos Swing.

Referência Utilizada:
Mais sobre ModelMat em:
Veja Também:
... Leia mais!

.

Sexta-feira, Julho 13, 2007

NetBeans IDE 6.0, GNU/Linux Ubuntu, GTK Look and Feel e Outras...


NetBeans IDE 6.0, GNU/Linux Ubuntu e GTK Look and Feel


O primeiro aspecto que notei ao estrear o NetBeans 6.0 no Ubuntu foi o seu look and feel. Está excelente, pois realmente parece um aplicativo GTK nativo.


Acima temos o NetBeans 6.0 com o tema Glossy. Abaixo, com o Clearlooks.


Swing e a Sua Nova Era

Temporada interessante para o Swing. Há pouco tempo atrás, alguns chegaram a soar as trombetas do apocalipse para esse toolkit. Parece que ainda há pessoas que pensam assim, mas está ficando cada vez mais difícil encontrá-las.

Alguém está investindo pesado no Swing. Com o Java 6, houve melhora na sua performance, integração com o sistema operacional nativo e seus aplicativos padrões, anti-aliasing, além de outras facilidades interessantes como a splash screen.

Nesse momento, há projetos fervilhando, sendo o SwingX (do SwingLabs), Beans Binding (JSR-295) e Swing Application Framework (JSR-296) talvez os mais notáveis.

Não seria perdoável esquecer dos vários look and feels disponíveis e de frameworks já existentes e estáveis. Exemplo? Consulte os JGoodies Binding e Validation.

Cadê Meus Plugins?

Com a chegada do NetBeans 6.0, nem estou me dando ao luxo de perguntar "quando vou poder utilizar meus plugins favoritos?", porque é bem provável que nem sejam portados da versão 5.x para a 6.0.

Dentre estes estão o Stripwhitespace e o NISECommons4E4N. Como não tenho tempo nem de fechar os olhos e sonhar que vou portá-los eu mesmo, só me resta uma saída: rezar.

Eclipse Europa

O editor do NetBeans evoluiu, mas ainda não é páreo para o do Eclipse que, com a chegada do Europa 3.3.0, tem até spell checker, entre outras cositas.

... Leia mais!

.

Sexta-feira, Julho 06, 2007

Swing Application Framework (JSR-296) - Parte 3

A terceira parte do artigo sobre o Swing Application Framework trata sobre a execução de tarefas em background.

A primeira parte discorre sobre a JSR-296, gerenciamento de ciclo de vida e inicialização do aplicativo, o armazenamento do estado da GUI entre sessões e persistência local. A segunda parte apresentou a injeção de recursos e a definição, gerenciamento e binding de Action’s.

Para uma total compreensão dessa terceira parte, é necessária a leitura da segunda parte do artigo e do texto O Básico sobre SwingWorker.

Tarefas em Background

Task estende SwingWorker, o que equivale a dizer que representa uma tarefa que executa em [uma thread em] background. De acordo com sua documentação, Task adiciona [à SwingWorker]:

  • uma série de propriedades descritivas que podem ser mostradas aos usuários;
  • um novo conjunto de métodos para customizar o término de uma tarefa;
  • possibilidade de bloquear entrada (input) à GUI enquanto a tarefa está executando; e
  • um TaskListener que permite monitorar os três métodos chaves de SwingWorker: doInBackground, process e done.

Done

O método abstrato doInBackground é a principal questão a ser considerada ao implementar tanto uma subclasse de SwingWorker quanto de Task, já que é sua responsabilidade definir exatamente o que irá ser executado em background.

O método done, porém, é final em Task. done é utilizado para executar atividades complementares na event dispatch thread (EDT) após o termino do método doInBackground. Em Task, done invoca os métodos succeeded, cancelled, interrupted ou failed quando a worker thread termina, respectivamente, com sucesso, porque foi cancelado, interrompido ou por alguma falha de execução.

Independentemente do resultado, done sempre invoca finished ao terminar sua execução, mesmo que uma exceção seja lançada. Por padrão, failed grava o erro em questão num arquivo de log; os demais métodos nada fazem. Como done executa na EDT, todos esses métodos também correrão nessa mesma thread.

Task in Action

Como mostrado em O Básico sobre SwingWorker, obter as entidades cadastradas num banco de dados dificilmente deve ocorrer na EDT, sob pena de congelamento da GUI. Aquele artigo mostra o uso de SwingWorker para resolver essa situação, enquanto esse texto irá mostrar o mesmo com Task, por suas facilidades adicionais.

private class LoadEntitiesWithTask extends Task<List<EntityClass>, Void> {

.....LoadEntitiesWithTask() {
..........super();
..........setLoadEnabled(false);
..........setStopEnabled(true);
.....}

.....protected List<EntityClass> doInBackground() {
..........setMessage("Obtendo " + entityName + "s cadastrados...");
..........return domainManager.findAll();
.....}

.....protected void succeeded(final List<EntityClass> newEntities) {
..........synchronized(this) {
...............if (!entities.isEmpty()) {
....................clear();
...............}
...............entities.add(0, EMPTY_ENTITY);
...............entities.addAll(newEntities);
..........}
..........fireContentsChanged(this, 0, entities.size() - 1);
..........setMessage(
...................."Feito: lista de " + entityName + "s obtida com êxito em "
....................+ getExecutionDuration(TimeUnit.MILLISECONDS) + "ms.");
.....}

.....protected void cancelled() {
..........setMessage("Operação cancelada.");
.....}

.....protected void interrupted(final InterruptedException ex) {
..........setMessage("Operação cancelada.");
.....}

.....protected void failed(final Throwable ex) {
..........setMessage("Erro: não foi possível obter " + entityName + "s cadastrados.");
.....}

.....protected void finished() {
..........setLoadEnabled(true);
..........setStopEnabled(false);
.....}
}

As mudanças mais significativas em relação à sua equivalente com SwingWorker são referentes aos métodos succeeded (no lugar de done), cancelled, interrupted, e failed que enviam mensagens a componentes específicos, como statusbar (mais sobre isso depois), e ao método finished.

Task pode ser utilizada em conjunto com @Action's (sobre Action's, ver parte 2) para definir tarefas que iniciam a partir de eventos gerados pela GUI.

@Action(block=BlockingScope.ACTION, enabledProperty="loadEnabled")
public Task load() {
.....loadEntitiesTask = new DefaultEntityModel.LoadEntitiesWithTask();
.....return loadEntitiesTask;
}

Note que o método load retorna Task. Infelizmente, tanta facilidade tem seu preço. Para alterar o comportamento de LoadEntitiesWithTask de modo que apenas obtenha as entidades se as mesmas não foram previamente carregadas, é preciso estender a classe. (Com SwingWorker, bastaria criar uma variação do método load).

private class LoadEntitiesIfEmpty extends LoadEntitiesWithTask {

.....LoadEntitiesIfEmpty() {
..........super();
.....}

.....protected List<EntityClass> doInBackground() {
..........if (entities.isEmpty()) {
...............return super.doInBackground();
..........}
..........return null;
.....}

.....protected void succeeded(final List<EntityClass> entities) {
..........if (entities == null) {
...............return;
..........}
..........super.succeeded(entities);
.....}
}

O método loadIfEmpty é responsável, sob o ponto de vista de quem o invocar, de carregar as entidades no formulário caso isso ainda não tenha acontecido.

public void loadIfEmpty() {
.....loadEntitiesTask = new DefaultEntityModel.LoadEntitiesIfEmpty();
.....ApplicationContext.
...............getInstance().
...............getTaskService().
...............execute(loadEntitiesTask);
}

Bloqueando a GUI

Ao utilizar a worker thread para executar longas tarefas, pode ser necessário bloquear certos componentes ou o aplicativo inteiro até que a tarefa termine.

Como exemplo, a tarefa de recarregar as entidades cadastradas num banco de dados certamente deve correr em background, e a possibilidade de concorrência dessa mesma tarefa dificilmente teria alguma utilidade. Portanto, bloquear o componente que inicia a tarefa em questão até que termine é interessante.

A opção de bloquear a GUI durante a execução de uma tarefa é feita definindo um valor para a propriedade block da anotação Action. Os possíveis valores para block são obtidos através da enumeração BlockingScope de Task: NONE (o padrão), ACTION, COMPONENT, WINDOW e APPLICATION. O método load, acima, utiliza BlockingScope.ACTION:

@Action(block=BlockingScope.ACTION, . . .)
public Task load() {
...... . .
}

Repare que o botão para carregar as entidades está desabilitado enquanto LoadEntitiesWithTask executa (observe a barra de progresso na statusbar).

BlockingScope.WINDOW e BlockingScope.APPLICATION bloqueiam a janela de nível mais alto e o aplicativo inteiro, respectivamente, por padrão mostrando um dialog indicando que o software está ocupado. O modo como esse bloqueio é feito pode ser customizado implementando a classe abstrata application.Task.InputBlocker e estabelecendo-a como o bloqueador de Task por meio de seu método setInputBlocker.

You've Got a Message

Com o auxílio de application.TaskMonitor, desenhada para ser o model de widgets como barra de estado, e dos métodos de Task setMessage e message, enviar notificações para a GUI sobre o estado da execução das tarefas na worker thread beira o trivial.

setMessage foi utilizado por simplicidade. O modo aconselhável para isso é definir as mensagens num arquivo de propriedade e utilizar o método message (consulte documentação da classe e exemplos que acompanham o framework).


Repare na mensagem avisando sob o término da operação. Além disso, como a operação para carregar as entidades chegou ao fim. o botão para carregar as entidades está habiltado, enquanto o que cancela essa operação está desativado...

Outros Métodos Úteis

A classe Task ainda possui uma série de métodos úteis que incluem, mas não se limitam a:
  • getExecutionDuration retorna o tempo de execução da tarefa;
  • setProgress tem o objetivo de notificar barras de progresso em statubars que a tarefa teve algum progresso;
  • isStarted e isPending complementam os métodos, presentes na classe SwingWorker, isDone e isCancelled.

Mantenha em mente que...

Esse artigo é apenas uma introdução ao assunto. Não deixe de consultar a documentação e até mesmo o código-fonte do projeto, fazer experiências, discutir sobre as funcionalidades do framework.

Veja também:
Fontes e Referências utilizadas:
... Leia mais!

.

Terça-feira, Junho 26, 2007

O Básico sobre SwingWorker

Uma GUI razoável tem certos pré-requisitos. Entre esses pré-requisitos está uma interface gráfica que não trava. Ou seja, é sempre responsiva. Isso implica mover tarefas demoradas para uma thread que executa em background.

Esse artigo é uma pequena introdução sobre execução de tarefas potencialmente longas em background com SwingWorker, para desenvolver GUI's que sempre respondem aos eventos gerados pelo usuário.

Nota: esse texto refere-se à classe SwingWorker presente no Java SE 6 (i.e. javax.swing.SwingWorker).

Processos e Threads

Há fartura de material sobre processos e threads disponíveis em livros, revistas e na Internet (veja, por exemplo, The Java Tutorials - Concurrency). Discorrer sobre threads foge do escopo desse texto, porém uma contextualização é válida.

Processos podem ser vistos como sinônimo de programas, embora um programa possa constituir-se de mais de um processo. O sistema operacional aloca para cada processo recursos, como espaço de memória, aos quais só esse processo tem acesso.

Cada processo tem ao menos uma, mas pode ter várias, threads, que compartilham recursos do processo ao qual pertencem e, por isso mesmo, são mais eficientes e sujeitas a uma série de problemas graves, de difícil depuração, como deadlocks, starvation e race condition.

Threads e Swing

Ao programar interfaces gráficas com Swing, ao menos três diferentes threads devem ser consideradas: initial thread, event dispatch thread (EDT) e worker thread, também chamada de background thread.

Num aplicativo Swing para desktop, a thread inicial apenas agenda a criação da GUI através dos métodos estáticos invokeLater ou invokeAndWait, da classe SwingUtilities.

public static void main(final String args[]) {
.....SwingUtilities.invokeLater(new Runnable() {
..........public void run() {
...............// Cria e mosta a GUI.
...............new Applicatio().setVisible(true);
..........}
.....});
}

Em applets, a thread incial invoca os métodos init e start do objeto applet. O agendamento da construção da GUI em applets deve necessariamente ser feito através de invokeAndWait, em init.

A partir daí, o aplicativo passa a correr "baseado em eventos", executados na event dispatch thread, gerados pelo usuário do software através da GUI. Tarefas executadas nessa thread devem terminar rapidamente, caso contrário a interface irá congelar.

Por exemplo, ao clicar no hyperlink Pedir Suporte, o método actionPerformed abre o cliente de e-mail padrão do usuário, com o endereço do suporte e um título para o e-mail (detalhes aqui).


// actionPerformed executa na EDT...
public void actionPerformed(final ActionEvent e) {
.....if (desktop != null) {
..........if (desktop.isSupported(Desktop.Action.MAIL)) {
...............// try...catch omitido para simplificar exemplo.
...............desktop.mail(uri);
..........}
.....}
}

Longas tarefas podem e devem ser executadas na worker thread. A intercomunicação necessária entre a worker thread e a EDT não é uma tarefa trivial. Considere implementar uma subclasse de SwingWorker, que é abstrata, para rodar tarefas em background.

Boa parte dos componentes Swing não é thread safe (veja o porquê em MultiThreaded toolkits: A failed dream?). Isto é, quando executados em concorrência podem causar erros imprevisíveis. Portanto, comumente, invocações aos métodos dos componentes Swing devem ser feitos na event dispatch thread.

Se realmente for necessário invocar tais métodos na worker thread, consulte a documentação das classes Swing para ter certeza que o método em questão é thread safe.

Congelando GUI's - Como Irritar os Usuários do Seu Software... e Perder Clientes

Um exemplo de atividade que certamente irá travar a interface gráfica é a obtenção de objetos cadastrados através do EntityManager da JPA, no Java SE. (Mais sobre JPA aqui mesmo, nesse blog.)

A classe DefaultEntityModel é utilizada como model da GUI. Seu método load é utilizado para carregar a lista de entidades cadastradas.

public void load() {
.....// Primeiro verifica se a lista já foi carregada com as entidades cadastradas no banco de dados;
.....// isto é, se o usuário já havia acessado esse formulário.
.....if (isEmpty()) {
..........// Código auxiliar omitido para melhor clareza.

..........// Bem aqui, congela a GUI ao carregar a lista com as entidades cadastradas.
..........List<EntityClass> loadedEntities = domainManager.findAll();

..........// Finalmente atualiza a GUI. Só então a interface "descongela".
..........entities.addAll(loadedEntities);
..........fireContentsChanged(this, 0, entities.size() - 1);
.....}
}

As entidades cadastradas são carregadas na interface gráfica sob demanda. Para tanto, load é invocado no momento em que o formulário de cadastro aparece para o usuário.

private void formComponentShown(java.awt.event.ComponentEvent evt) {
.....entityListPanel.load();
}

Operações que envolvem manipulação de entidades com JPA, como é caso do método findAll da classe DomainManager (Service que disponibiliza API de alto nível para gerenciamento de entidades) costumam ser lentas no ambiente Java SE, porque implicam:

  1. conexão ao banco de dados;
  2. tentativa de geração das tabelas do banco de dados, de acordo com a configuração;
  3. execução de operações CRUD; e
  4. desconectar-se do banco de dados.

SwingWorker in Action

A classe javax.swing.SwingWorker fornece a infraestrutura necessária para executar tarefas demoradas na worker thread. Cada instância de SwingWorker implementa uma tarefa em background. De acordo com sua documentação:
é desenhada para situações onde é preciso ter tarefas demoradas executando em background que forneçam atualizações para a GUI, seja quando a tarefa terminar, seja enquanto processando (não mostrado nesse artigo).
SwingWorker é parametrizada. O primeiro parâmetro de tipo é utilizado para definir o resultado final dos métodos doInBackground() e get(). O segundo, para definir o tipo dos resultados intermediários em publish(V... chunks) e process(List<V> chunks).

/**
* Background thread utilizada pelo método load para preencher lista de entidades.
*
* LoadEntities espera carregar todas as entidades
* na instância de List, entities, para só então atualizar a GUI.
*/
private class LoadEntities extends SwingWorker<List<EntityClass>, Void> {

.....@Override
.....protected List<EntityClass> doInBackground() {
..........return domainManager.findAll();
.....}

.....@Override
.....public void done() {
..........// Código auxiliar e bloco try...catch omitidos para melhor clareza.
......... entities.addAll(get());
..........fireContentsChanged(this, 0, entities.size() - 1);
.....}
}

Subclasses de SwingWorker devem implementar o método doInBackground. done executa na EDT assim que o método doInBackground termina. get também corre na EDT e espera doInBackground terminar para só então retornar o resultado da computação desse método. Em LoadEntities, get é chamado em done, portanto retorna imediatamente.

O método load de DefaultEntityModel, atualizado, instancia a classe interna LoadEntities e invoca seu método execute na event dispatch thread, que retorna imediatamente. Repare que a assinatura de load não foi alterada, portanto a interface gráfica não precisa ser atualizada.

public void load() {
.....// Primeiro verifica se a lista já foi carregada com as entidades cadastradas no banco de dados;
.....// isto é, se o usuário já havia acessado esse formulário.
.....if (isEmpty()) {
..........// Agora, executa tarefa em background. Conclusão, a GUI não congela.
..........new DefaultEntityModel.LoadEntities().execute();
.....}
}

Repare em
..........new DefaultEntityModel.LoadEntities().execute();

SwingWorker é desenhada para executar apenas uma vez; não há como reaproveitar o uso de suas instâncias.

E não é só isso!

SwingWorker também possibilita:
  • Cancelar a worker thread em execução com cancel(boolean mayInterruptIfRunning), verificar se essa thread terminou através de isDone(), ou se foi cancelada com isCancelled().
  • Como mencionado anteriormente, SwingWorker pode fornecer resultados intermediários enquando a worker thread está em execução. Para isso, use o método publish((V... chunks). publish faz com que process(List<V> chunks) seja invocado de modo assíncrono na EDT.
  • SwingWorker pode amarrar propriedades. Mudanças nessas propriedades disparam eventos que deverão ser tratados na EDT. Na prática, isso possibilita conhecer o estador da background thread (PENDING, STARTED e DONE), assim como o seu progresso.
Consulte as fontes utilizadas nesse artigo para conhecer os detalhes técnicos necessários para implementar essas funcionalidades de SwingWorker.

Veja Também:
Fontes Utilizadas:
... Leia mais!

.

Sexta-feira, Junho 15, 2007

Swing Application Framework (JSR-296) - Parte 2

A segunda parte desse artigo foi publicado no blog batteries not included, do Daniel (Tritone) Fernandes Martins. Você pode acessá-lo clicando aqui.

Valeu, Daniel!

Veja Também:

Fontes e Referências Utilizadas:
... Leia mais!

.