Terça-feira, Maio 29, 2007

Swing Application Framework (JSR-296) - Parte 1

A missão do Swing Application Framework (JSR-296), segundo os seus criadores, é definir uma infraestrutura que é comum à maioria dos aplicativos Desktop:

  • gerenciamento do ciclo de vida dos aplicativos, especialmente GUI startup e shutdown
  • gerenciamento e carregamento de recursos, como strings, mensagens formatadas, cores, fontes e outros, em componentes Swing
  • apoio para a definição, gerenciamento e ligação de Actions
  • Actions assíncronos (executam em "background")
  • Estado da sessão persistente.
O Swing Application Framework visa fornecer uma base padrão e um ponto de partida para aplicativos Swing, tornando mais fácil o seu desenvolvimento, e ser simples o suficiente para que até mesmo programadores novatos entendam como utilizá-lo dentro de horas, ao invés de meses.

Inicialização e Gerenciamento do Ciclo de Vida de Aplicativos

Através da classe Application, a base desse framework, é possível executar tarefas específicas de acordo com o ciclo de vida do aplicativo.

O aplicativo deve estender essa classe e implementar ao menos o método startup, responsável por preparar a GUI inicial e torná-la visível. A implementação dos outros métodos que lidam com o ciclo de vida dos aplicativos - initialize, ready e shutdown - é opcional.

Use initialize se precisar configurar propriedades do sistema antes da construção da GUI. Utilize ready se precisar executar trabalho extra depois que a GUI estiver pronta e visível. O objetivo é fazer com que startup execute menos tarefas, tornando a GUI visível o mais rápido possível. O método shutdown fecha a GUI e executa outras tarefas de limpeza antes de encerrar o aplicativo.

A subclasse concreta de Application inicia o aplicativo por meio de seu método launch.

public final class OhMyApplication extends Application {
.....private SingleFrame mainFrame;

.....@Override
.....protected void initialize(final String [] args) {.....}

.....protected void startup() {
..........mainFrame = new SingleFrame();
..........mainFrame.addWindowListener(new MainFrameListener());
..........mainFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
..........mainFrame.pack();
..........mainFrame.setVisible(true);
.....}

.....@Override
.....protected void ready() {
..........TipOfTheDay.show(mainFrame);
.....}

.....
@Override
.....protected void shutdown() {
..........mainFrame.setVisible(false);
.....}

.....public static void main(final String[] args) {
..........PlasticLookAndFeel.setPlasticTheme(new ExperienceBlue());
..........Application.launch(OhMyApplication.class, args);
.....}

.....private class MainFrameListener extends WindowAdapter {
..........public void windowClosing(final WindowEvent e) {
...............exit(e);
..........}
.....}
}

Se o aplicativo em questão contar com apenas um JFrame principal, há a opção de criar uma subclasse concreta de SingleFrameApplication, ao invés de estender Application.

Os aplicativos devem ter um arquivo de propriedades com o mesmo nome da classe que estende Application ou SingleFrameApplication num subpacote denominado resources. Esse ResourceBundle irá compartilhar seus recursos por todo o aplicativo e deverá contar com as seguintes propriedades padrões:
Application.name        = Nome do aplicativo
Application.id = Apropriado para identificadores específicos do aplicativo
Application.title = Título para dialogs e frames.
Application.version = Versão pode ser incorporada em mensagens
Application.vendor = Nome da organização/pessoa responsável pelo software
Application.vendorId =
Apropriado para identificadores específicos do aplicativo
Application.homepage = Uma URL como http://rafaelfiume.wordpress.com
Application.description = Breve descrição sobre o software.
Application.lookAndFeel = system, default, ou uma classe LookAndFeel


Estado da Sessão Persistente

É interessante manter o estado da interface gráfica entre sessões do aplicativo. Para isso existe a classe SessionStorage. O estado da sessão é armazenado baseado em componentes, desde que estes tenham definidos a propriedade name e uma classe SessionState.Property equivalente.

Por padrão, SessionStorage possui implementações de SessionState.Property para os componentes Window, JTabbedPane, JSplitPane, e JTable, armazenado as propriedades bounds (posição e tamanho da janela) da classe Rectangle para Window's, dividerLocation para JSliderPane's, selectedIndex para JTabbedPane's e width (tamanho das colunas) para JTable's.

SessionStorage é obtido através do método getSessionStorage de ApplicationContext, um Singleton utilizado para encontrar variáveis e serviços globais, o que corresponde a maior parte dos componentes do Swing Application Framework.

Durante a inicialização do software (startup), restore é chamado para restaurar o estado da sessão anterior da GUI. Ao encerrar a execução do programa (shutdown), store é invocado para persisitir a sessão atual da GUI.

@Override
protected void startup() {
...... . .
.....ApplicationContext.getInstance().getSessionStorage().restore(mainFrame, "session.xml");
...... . .
}

@Override
protected void shutdown() {
...... . .
.....ApplicationContext.getInstance().getSessionStorage().save(mainFrame, "session.xml");
...... . .
}

O estado da sessão é armazenado a partir do diretório do usuário. As propriedades vendorId e applicationId (veja a seção Inicialização e Gerenciamento do Ciclo de Vida de Aplicativos) devem estar definidas para garantir que o diretório correto seja acessado em diferentes plataformas (de acordo com a seção Persistência Local, logo abaixo).

A classe SingleFrameApplication implementa rotinas para persistir / restaurar o estado da sessão da GUI, inclusive de janelas secundárias, como dialogs, livrando o programador dessa tarefa.

Persistência Local

SessionStorage utiliza a classe LocalStorage para persistir / restaurar um java.util.Map com dados sobre o estado da GUI numa sessão, por meio de seus métodos save e load.

public void save(Component root, String fileName) throws IOException {
...... . .
.....LocalStorage lst = ApplicationContext.getInstance().getLocalStorage();
.....lst.save(stateMap, fileName);
}

public void restore(Component root, String fileName) throws IOException {
...... . .
.....LocalStorage lst = ApplicationContext.getInstance().getLocalStorage();
.....Map stateMap = (Map) (lst.load(fileName));
...... . .
}

LocalStorage deve ser obtido através do método getLocalStorage de ApplicationContext, e pode ser utilizado para persistir / restaurar outros dados, desde que estejam de acordo com os mecanismos da API Java Beans Persistence, o que inclui Java Beans, bem como a maioria dos tipos primitivos. O método save é implementado com a ajuda de java.beans.XMLEncoder, enquanto load utiliza java.beans.XMLDecoder.

Como visto na seção acima Estado da Sessão Persistente, as propriedades vendorId e applicationId devem estar definidas para garantir que o diretório correto seja acessado em diferentes plataformas. O diretório onde os dados serão persistidos dependem do sistema operacional. No Windows esse diretório poderá ser:

${userHome}\Application Data\${vendorId}\${applicationId}

no Mac OS X:

${userHome}/Library/Application Support/${applicationId}

por fim, em sistemas UNIX e derivados será:

${userHome}/.${applicationId}/

Cenas dos Próximos Capítulos...

Nas partes seguintes serão explicadas as funcionalidades de Injeção de Recursos, definição de Actions e Tasks (tarefas que executam em background).

Fontes e Referências utilizadas:

5 comentários:

Daniel F. Martins disse...

Legal o post (e o framework, embora eu estivesse com um pé atrás em relação a ele)!

Parece que, utilizar o Swing Framework em conjunto com o Beans Binding (JSR-295) tornará o desenvolvimento desktop em Java bem mais simples.

Voltando ao assunto, estou aguardando os próximos artigos sobre o Swing Framework!

[]s

Rafael Fiume disse...

Logo eles serão publicados, Daniel!

Luca Bastos disse...

Seu blog é legal e desconfio que aborda assuntos interessantes.
.
Me desculpe a franqueza mas com este monte de propaganda no meio do texto fica muito difícil de ler.
.
Outro problema que me afasta dos blogs é quando me deparo com link "Leia mais...". Eu sempre vou embora.

Rafael Fiume disse...

Luca, não precisa pedir desculpas, porque o que eu mais quero é feedback.

O "Leia Mais..." é necessário porque em certas categorias existem mais de 15 artigos, com muitas figuras, e aumentando, como é o caso do SwingX. Isso facilita a navegação e poupa a preciosa banda do usuário.

Quanto às propagandas, tem muita gente que critica e talvez um dia eu as remova - tô falando do AdSense. Mas, por enquanto, nada mais justo do que tirar a grana pro fim de semana.

Eu me empenho bastante nesse blog, gostaria de tirar algo palpável também. Além disso, não há propaganda no meio do texto.

Agora, pra saber se é interessante, só lendo mesmo! :)

Aproveito pra dizer que leio o seu blog e até agora gostei da maior parte do que você publicou, especialmente os artigos sobre exceções.

Abraços!

Rafael Fiume disse...

Atenção: A API do Swing Application Framework está sujeita a mudanças, portanto o artigo pode encontrar-se fora de sincronia com a API mais recente. Ou seja, desatualizado.

O ModelMat utiliza o Swing Application Framework, portanto é possível consultá-lo como exemplo de aplicação desse framework.

Também é aconselhável estudar os exemplos contidos nas fontes do Swing Application Framework.