Introdução a JavaBeans – Parte 2

A parte 2 do tutorial de JavaBean mostrará como você pode escrever um código simples com JavaBeans. Depois, você usará a ferramenta BeanBox para rodar, testar e alterar o seu Bean. Você também poderá criar um JavaBeans e modificar Beans existentes usando as ferramentas gráficas de construção. Para ver a parte 1 do tutorial, clique aqui.
Uma olhada no JavaBeans

Um JavaBean é uma classe Java que define propriedades e que se comunica com outros Beans através de eventos. As propriedades podem ser definidas através da definição da classe JavaBeans, ou podem ser herdadas de outras classes. Um Bean, porém, não precisa herdar características de nenhuma classe ou interface em particular.

O JavaBeans que representa componentes gráficos e que são feitos para serem visíveis precisam ser derivados de um java.awt.Component, como o java.awt.Canvas, para que eles possam ser adicionados a contêineres visuais. Tipicamente, um Java Bean representa objetos gráficos visíveis e estes estendem um componente AWT. De fato, se você começar com uma classe que defina um componente AWT, é fácil modificar essa classe para ela tornar-se um JavaBean.

Observe que existem Beans que não são visíveis, e esses são referenciados como Beans invisíveis. Eles são idênticos a outros JavaBeans, exceto que não possuem nenhuma representação gráfica. Eles são capazes de chamar métodos, disparar eventos, salvar estados persistentes, e assim em diante; eles só não tem uma aparência em tela deles mesmos. Beans invisíveis ainda serão representados visualmente em um ambiente de desenvolvimento, e poderão ser personalizadas dentro dessa ferramenta.

Uma olhada para as Propriedades

As propriedades são análogas as variáveis de instância internas dos objetos. Os manipuladores de eventos são semelhantes ao comportamentos ou métodos definidos para os objetos. A diferença principal é que as propriedades e eventos são expostas à ferramenta de construção para permitir a manipulação visual do Beans. Os valores associados as propriedades podem ser modificados em tempo de desenho. Similarmente você pode selecionar visualmente manipuladores de evento da lista para ligar aos eventos gerados por um Bean para o evento correspondente manipulador de evento de outro.

Quando você define propriedades em um Bean, tipicamente você declara eles como “privado”. Assim outros Beans e aplicações podem acessar essas propriedades, você define métodos get e set públicos para cada propriedade. Um JavaBeans precisa também importar a interface java.io.Serializable de modo que seu estado seja persistente.

Colocando tudo junto

Uma vez que você tiver escrito a classe Java que defina o Beans, compile ela para verificar que você não tenha nenhum erro óbvio no código. Depois, teste-a usando o BeanBox. Esses passos são explicados em detalhes nas seções abaixo.

  • Criando um Bean Essa seção descreve os passos básicos para criar um JavaBeans simples. Você escreve um Bean com uma propriedade simples, e então compila ela. Você também irá passar pela criação do arquivo JAR para que o Bean possa ser testado com o BeanBox.
  • JavaBeans e Pacotes Pacotes são uma maneira útil de manter Beans relacionados em um único arquivo. Essa seção ensinará como usar pacotes em seus próprios JavaBeans.
  • Adicionando Labels aos Beans Essa lição lhe dará alguma experiência adicional com propriedades e JavaBeans.
  • Adicionando Propriedades Essa seção foca em tipos mais complexos de propriedades que podem ser adicionadas ao Beans. Será mostrada como implementar essas propriedades.
  • Manipulando Propriedades Essa parte do tutorial mostrará programaticamente como sobrecarregar os valores das propriedades padrão em seus JavaBeans.
  • Manipulando Eventos JavaBeans usam eventos para se comunicar com outros Beans, então é importante entender como manipular eventos. Essa seção cobre tudo o que é necessário saber para manipular eventos com JavaBeans, incluindo o registro de ouvintes de eventos, disparo de eventos, e uso de eventos para controlar o comportamento do Beans.

Criando um Bean

A maneira mais fácil de aprender a construir um Beans é começar com um Bean clássico, e adicionar uma propriedade a ele. O Bean mais simples precisa implementar a interface java.io.Serializable. De fato, qualquer classe que implemente essa interface é um JavaBean minimo por padrão. Um Bean bem simples, que meramente exibe um retângulo vermelho, estende o componente AWT java.awt.Canvas. O Bean em si precisa apenas definir um construtor que configura o tamanho do retângulo e define a cor de fundo para vermelho.

Criando um Bean simples

Vamos começar com um Bean simples que define um retângulo de um tamanho específico com uma cor de fundo (vermelho) em seu construtor. Dentro desse retângulo vermelho, o Bean exibirá um retângulo menor na cor verde. Além do construtor, o Bean inclui um propriedade simples chamada color que inicialmente é configurada para verde. Observe que a propriedade color é declarada private.

O Bean inclui dois métodos public, um para obter o valor da propriedade color, e outro para altera-lo. Por color ser uma propriedade private, não pode ser acessada diretamente; pode apenas ser acessada por esses métodos getter e setter. Vamos dar uma olhada no código de nosso JavaBean:

import java.awt.*;
import java.io.Serializable;
public class SimpleBean extends Canvas
                 implements Serializable {
  private Color color = Color.green;
  //getter method
  public Color getColor() {
	return color;
  }
  //setter method
  public void setColor(Color newColor) {
	color = newColor;
	repaint();
  }
  //override paint method
  public void paint (Graphics g) {
	g.setColor(color);
	g.fillRect(20,5,20,30);
  }
  //Constructor: sets inherited properties
  public SimpleBean() {
	setSize(60,40);
	setBackground(Color.red);
  }
}

A interface Serializable

Em primeiro lugar, um Bean precisa implementar a interface Serializable. Os objectos que suportam essa interface podem salvar e recuperar seu estado do disco. Os Beans que são personalizados (usualmente pela edição de suas propriedades pelo ambiente de desenvolvimento) precisam ser capazes de salvar e recuperar essas alterações quando requisitado. Para implementar a interface Serializable, importe o pacote java.io.Serializable e declare que seu Beans implementa Serializable na definição da classe. Por esse Beans ser também visível, ele estende o componente AWT java.io.Canvas.

A propriedade Color

A propriedade color é uma variável de instância privada que é inicializada com o valor “verde”.

private Color color = Color.green;

Métodos Get e Set

Devem existir métodos get e set para se ter acesso à variável privada. Note que os nomes dos métodos getter a setter seguem um formato particular. Os nomes dos métodos começam com “get” e “set” respectivamente seguidos pelo nome da propriedade, como a letra inicial do nome da propriedade em maiúscula. (É importante seguir esse formato para que as ferramenta de introspecção do Bean funcione).

Assim, os métodos get e set tem o seguinte formato:

public <returntype> get<Propertyname>
public void set<Propertyname> (parameter)

Para a propriedade color de nosso Beanm definimos o seguinte para de métodos:

public Color getColor() { ... }
public void setColor(Color c) { ... }

Testando e editando o Bean

Nosso Bean está quase pronto para ser utilizado no BeanBox, onde aparecerá na paleta de componentes da ToolBox.

Utilizando o Bean dentro da BeanBox

Primeiro, você deve compilar o código Java. Em seguida, criar um arquivo executável JAR. Abaixo estão os passos apra fazer isso:

  1. Compile o código fonte do Bean e gere o SImpleBean.class:
  2. javac SimpleBean.java
  3. Crie um arquivo de manifesto no editor de texto. O arquivo de manifesto especifica o nome do arquivo *.class e indica que se trata de um JavaBean. O arquivo de manifesto torna-se parte do arquivo JAR. Você pode nomear o arquivo como manifest.tmp. Contém duas linhas:
    Name: SimpleBean.class
    Java-Bean: True
  4. Crie o arquivo JAR executável. Use o formato abaixo do comando jar para incluir o arquivo de manifesto junto com o SImpleBean.class:
    jar cfm SimpleBean.jar manifest.tmp SimpleBean.class
  5. Carregue o arquivo JAR no BeanBox. (A parte 1 desse tutorial explicou como iniciar e usar o BeanBox para testar seu JavaBeans). Do menu File do BeanBox, selecione LoadJar. Isso exibirá o navegador de arquivos.
  6. Localize o arquivo SimpleBean.jar e selecione-o. Observe que o SImpleBean aparecerá no final da lista de Beans na ToolBox.
  7. Para carregar o novo Bean no BeanBox, clique no SimpleBean na ToolBox. O cursor mudará para uma seta em forma de cruz.
  8. Mova o cursos para qualquer ponto da BeanBox, e então clique nesse ponto. O SimpleBean aparecerá como um retângulo vermelho com uma borda em volta de um retângulo verde menor. A borda indica que o SImpleBean está selecionado; suas propriedades aparecem na planilha de Propriedades.

 

Você pode reposicionar o SimpleBean dentro do BeanBox arrastando qualquer parte que não seja um dos cantos da borda. Quando estiver reposicionando um Bean, o cursos muda para o formato de uma seta em forma de cruz. Como o SimpleBean herda caracteristicas de Canvas, você pode redimensiona-lo. Para fazer isso, arraste um dos cantos da borda.

Editando as Propriedades do Bean

A planilha de Propriedades mostra as propriedades do Bean selecionados. O SimpleBean tem quatro propriedades:

  • color
  • background
  • foreground
  • name

O SimpleBean declara a propriedade color, mas herda as outras três propriedades de Canvas. Clique em uma propriedade, como em color, para abrir o editor de propriedade onde você pode alterar o valor da propriedade. O BeanBox fornece um editor de propriedades padrão para os tipos primitivos, assim como para os tipos Font e Color.

É fácil adicionar outras propriedades para o Bean.

  1. Declare uma nova propriedade como uma variável private. Você pode inicializar ela com algum valor padrão na declaração, mas não é obrigatório.
  2. Declare um par de métodos getter e setter públicos para recuperar o valor da propriedade e altera-la. Lembre para formatar o nome desses métodos usando get e set seguido pelo nome da variável, com a primeira letra em maiuscula.

As novas propriedades adicionadas ao Bean aparecem na planilha de Propriedades quando o Bean é aberto no BeanBox, assumindo que você tenha definido corretamente os métodos get e set para a propriedade. Através do mecanismo de introspecção, o BeanBox encontra os métodos get e set que batem com o nome da propriedade e exibe a propriedade na planilha.

No próximo tópico veremos como adicionar outros tipos de propriedades ao Bean e fazer com que o Bean dispare ou receba eventos.

JavaBeans e Pacotes

Tipicamente, os Beans (seja de terceiros ou os seus próprios) devem ser instalados em um local único com outros Beans. Os pacotes Java ajudam você a manter tudo no local apropriado. Os pacotes Java são uma boa maneira de organizar classes relacionadas e manter todas em um único lugar.

Colocando seu Bean em um Pacote

Para colocar o seu Bean em um pacote:

  1. Adicione uma sentença de definição de pacote no topo de seu arquivo. Essa sentença precisa ser a primeira linha no arquivo onde se define a classe. Essa sentença especifica o caminho onde está armazenado o arquivo compilado da classe, e o caminho relativo para o diretório atual. Os sub-diretórios são separados por pontos (.) ao invés de barras. Por exemplo, para Um Bean compilado no diretório \acme\Beans, adicione a seguinte linha no topo de seu arquivo:
    package acme.Beans;

    Acme03Bean, um JavaBeans simples com uma propriedade color apenas, utiliza pacotes. Nesse exemplo, você precisa criar uma estrutura de diretório onde o compilador Java possa colocar o arquivo*.class gerado no local adequado. Você também irá querer definir um nome de pacote para as classes que acompanham seu pacote.

  2. Importe o pacote nos arquivos que usam as classes contidas no pacote. Inclua o nome completo do pacote e classe quando criar um arquivo JAR para incluir o Beans que compõe o pacote. Esses arquivos JAR são o mecanismo de empacotamento preferido para distribuir os Beans que são compostos de múltiplos arquivos – incluindo tanto arquivos de classe quanto imagens.
  3. Crie a estrutura de diretório apropriada para o pacote acme.Beans dentro do diretório atual. Primeiro crie dois diretórios separados, um para o código Java e outro para os arquivos *.class:
    mkdir -p ./src/acme/Beans
    mkdir -p ./classes/acme/Beans
  4. Copie o arquivo de código (ou arquivos) para o diretório onde localizam-se os fontes na estrutura. Em sistemas UNIX:
    cp -p Acme03Bean.java ./src/acme/Beans

    Quando você compilar o JavaBeans, certifique-se de direcionar o arquivo *.class para o diretório apropriado. Se estiver usando um arquivo Makefile, então modifique a conformidade do arquivo.

Adicionando Labels ao Bean

Nessa parte do tutorial, iremos adicionar uma variável de instância ao nosso AcmeBean. Essa nova variável armazena uma String para o Bean.

Adicionando o Label

  1. Defina uma variável privada chamada Label:
    private String label;
  2. Após definir a variável, associe um valor padrão a ela no construtor do Bean. Faz sentido também configurar uma fonte para ser usada na renderização do label. Adicione as duas linhas seguintes ao construtor:
    this.label="Bean"
    setFont(new Font("Dialog"; Font.PLAIN, 12));

    Eventualmente, esse Bean será modificado para parecer um botão. Você irá desejar personalizar esse label no ambiente de desenvolvimento.

  3. Para permitir personalização durante o desenho da aplicação, torne label uma propriedade do Bean adicionando os métodos get e set para ele. Para fazer isso, adicione os métodos getLabel e setLabel na classe do Bean.
    public String getLabel() {
    		return label;
    }
    public void setLabel(String newLabel) {
    		String oldLabel = label;
    		label = newLabel;
    }

    O funcionamento é idêntico ao modo como adicionamos a propriedade color.

  4. Ainda nessa classe, você pode renderizar o Bean para parecer um botão adicionando umas poucas linhas ao método paint.
    g.fillArc(5, 5, 30, 30, 0, 360);
    g.fillArc(25, 5, 30, 30, 0, 360);
    g.setColor(Color.blue);
    int width = size().width;
    int height = size().height;
    FontMetrics fm = g.getFontMetrics();
    g.drawString(label,
       (width - fm.stringWidth(label)) / 2,
    		(height + fm.getMaxAscent()
    		 - fm.getMaxDescent()) / 2);

    A definição do método paint deve ficar assim:

    public void paint(Graphics g) {
    	g.setColor(BeanColor);
    	g.fillRect(20, 5, 20, 30);
    	g.fillArc(5, 5, 30, 30, 0, 360);
    	g.fillArc(25, 5, 30, 30, 0, 360);
    	g.setColor(Color.blue);
    	int width = size().width;
    	int height = size().height;
    	FontMetrics fm = g.getFontMetrics();
    	g.drawString(label, (width -
    	          fm.stringWidth(label)) / 2,
    	(height + fm.getMaxAscent() -
    	          fm.getMaxDescent()) / 2);
    }

Toques finais

Agora quando o botão for desenhado no BeanBox, ele se parecerá com um Bean. A planilha de propriedades mostrará campos tanto para a propriedade color quanto para label. Você poderá personalizar essas propriedades durante o desenho de sua aplicação editando os seus valores na planilha. Observe que a cor do botão pode ser alterada para cinza. Isso é feito pela mudança do valor inicial da variável de instância do BeanColor.

private Color BeanColor = Color.gray;

Uma nova forma para o Bean pode ser arranjada chamando dois novos métodos fillArc no método paint do Bean

g.fillArc(5, 5, 30, 30, 0, 360);
g.fillArc(25, 5, 30, 30, 0, 360);

Essas chamadas adicionam um lados arredondados ao desenho quadrado original do Bean. O restante do código no método paint abaixo garante que a string de label esteja centralizada dentro do botão:

int width = size().width;
int height = size().height;
FontMetrics fm = g.getFontMetrics();
g.drawString(label,
  (width - fm.stringWidth(label)) / 2,
		(height + fm.getMaxAscent() -
		 fm.getMaxDescent()) / 2);

Adicionando Propriedades

A lição anterior mostrou como adicionar propriedades simples ao Bean, além de compilar o Bean e testa-lo no BeanBox. Nessa parte, veremos como implementar outros tipos de propriedades, como:

  • Propriedade ligada
  • Propriedade forçada

Esses dois tipos mais complexos de propriedades requerem que o Bean se comunique com outros. Um Bean fonte envia ou dispara eventos para um Bean destino, depois regitra seu interesse no evento do Bean fonte, recebe ou manipula o evento.

Propriedades ligadas

Essa propriedade fornece ao JavaBean a capacidade de notificar outro JavaBean quando houver uma mudança do valor de sua propriedade. O JavaBean que é notificado pode então reagir a essa mudança. Por exemplo, você pode ter um botão em um JavaBean que, quando pressionado, faz com que outro Bean mostre alguma informação.

Por outros Beans serem notificados quando acontece uma mudança em uma propriedade ligada, é preciso haver um meio para que os Beans se comuniquem. Os Beans se comunicam através de eventos. Uma mudança em uma propriedade ligada dispara um evento. Esses eventos são referenciados como eventos de mudança de propriedades.

Quando você cria uma propriedade ligada, você também configura outros Beans como interessados em aguardar por mudanças na propriedade. Assim, toda vez que o valor da proprieddae for alterada, uma notificação é enviada para todos os Beans registrados para responder ao Bean origem.

Vamos dar uma olhada no arquivo MyButton.java, que usa propriedades ligadas. Esse arquivo inclue código para notificar outros Beans interessados quando a propriedade for alterada. Uma vez que tivermos criado MyButton, iremos configurar um Bean para aguardar mudanças de MyButton e responda a essas mudanças.
Configurando Propriedades Ligadas

Abaixo seguem os passos básicos para criar um propriedade ligada em um Bean:

  1. Importe o pacote java.beans para obter acesso a alguma classes convenientes definidas pelo pacote. o MyButton importa o pacote como segue:
    import java.beans.*;
  2. Instâncie a classe java.beans.PropertyChaneSupport.
    private PropertyChangeSupport changes = new
    		PropertyChangeSupport(this);

    O MyButton cria um novo objeto, chamado changes, que intância a classe PropertyChangeSupport. Essa variável guardará um coleção de objetos que serão notificados em relação a qualquer mudança relativa a propriedade. São definidos dois métodos de suporte, addPropertyChangeListener e removePropertyChangeListener, que fornecem uma interface pública que permite aos interessados se registrarem com MyButton para escutarem mudanças.

  3. Implemente os métodos definidos pela classe PropertyChangeSupport. Essa classe inclue métodos que adicionam e removem objetos que escutam nossa classe, especificamente objetos PropertyChangeListerner. O método addPropertyChangeListerner adiciona um novo objeto a lista. A classe também inclue um terceiro método –firePropertyChane– para disparar objetos PropertyChaneSupport para objetos que estejam escutando. O MyButton inclue o código abaixo para implementar esses métodos.

    Observe que o parametro 1 refere-se ao Bean que escuta a mudança da propriedade que registra ou remove interesse em escutar por mudanças.

    public void addPropertyChangeListener(
                PropertyChangeListener l) {
    	changes.addPropertyChangeListener(l);
    }public void removePropertyChangeListener(
                     PropertyChangeListener l) {
        changes.removePropertyChangeListener(l);
    }
  4. Modifique os métodos setter do Bean para as propriedades ligadas. Para essas propriedades, que devem ser ligadas, modifique o método setter para incluir código que dispare um evento de mudança de propriedade quando o valor for alterado. O MyButton chama o método firePropertyChange dentro de cada método que altera o valor da propriedade. Por exemplo, quando uma aplicação ou usuário muda a fonte do texto do botão, essa ação executa o método MyButton.setFont. Já que o método firePropertyChange requer tanto o valor antigo quanto o novo valor da propriedade alterada, o método setFont precisa primeiro capturar o valor velho chamando o método getFont. O valor é alterado, e então o método changer.firePropertyChange notifica objetos interessados. É passado três parâmetros ao método: o nome da propriedade alterada, o valor antigo e o novo valor.
    public void setFont(Font f) {
    	Font old = getFont();
    	super.setFont(f);
    	sizeToFit();
    	changes.firePropertyChange(
    	   "font", old, f);
    }

Mantenha em mente que com as propriedades ligadas, o valor da propriedade muda primeiro, e depois o evento de mudança é disparado.

Configurando um Ouvinte

Agora iremos configurar um Bean ouvinte que recebe e talvez responda ao evento de mudança de propriedade em MyButton.

  1. Implemente a interface PropertyChangeListener. Existe um método dessa interface, o método propertyChange:
    public abstract void propertyChange(
                PropertyChangeEvent evt)

    Os Beans que disparam eventos de mudança de propriedade chamam o método propertyChange para notificar os ouvintes sobre alterações de propriedades.

  2. Na implementação do método propertyChange no Bean ouvinte, defina as ações que você quer tomar quando a propriedade for alterado. Você faz a conexão entre o Bean origem (o Bean que dispara o evento de mudança) e o Bean que escuta, aquele que reage as mudanças de propriedades no BeanBox. Você pode manualmente criar essa conexão escrevendo uma classe especial. Por exemplo, o Bean ouvinte MyChangeReporter inclue um método reportChange cujo parâmetro é um objeto PropertyChangeEvent. O método extrai o nome da propriedade e o nvo valor do objeto passado, formata esses dados na janela de texto, exibe a informação.
    public void reportChange(PropertyChangeEvent evt) {
     String text = evt.getPropertyName()
      + " := " + evt.getNewValue();
     int width = getSize().width - 10;
     Font f = getFont();
      if (f != null) {
      // Trim the text to fit.
         FontMetrics fm = getFontMetrics(f);
          while (fm.stringWidth(text) > width) {
    	   text = text.substring(
    	              0, text.length()-1);
      }
    }
      setText(text);
    }

    Você pode também criar a conexão entre a origem e o destino pelo próprio código. Para fazer isso, escreve uma classe adaptadora para capturar o evento e configura essa classe adaptadora para chamar o método correto do objeto ouvinte.

  3. Configure o Bean ouvinte para chamar o método de registro de escuta no Bean original. Por exemplo, nosso ouvinte invoca o seguinte método de MyButton:
    MyButton.addPropertyChangeListener(
               aPropertyChangeListener);

Conectando o Bean no BeanBox

O BeanBox reconhece quando um Bean corretamente define uma propriedade ligada – que pode ser disparar eventos para alterações de propriedades – e inclui uma interface propertyChange para esse Bean. Essa interface aparece no menu Events quando esse Bean for selecionada.

  1. Selecione Events no menu Edit do Bean. Uma vez que você tiver selecionado propertyChange, conecte Bean origem ao Bean ouvinte.
  2. Estenda o linha tracejada da origem ao destino. uma caixa de diálogo EventTargetDialog aparecerá.
  3. Selecione o método apropriado no Bean destino (em nosso exemplo, o método reportChange).

Propriedades forçadas

As propriedades forçadas são propriedades ligadas que são direcionadas para um único caminho. Como essa propriedade, um projeto externo – tanto um Bean ouvinte quanto o próprio Bean – podem vetar uma mudança de propriedade. A API do JavaBeans fornece um mecanismo para manipular esse tipo de propriedade que é similar a usada pelas propriedades ligadas.

Para implementar uma propriedade forçada, você precisará ter:

  • Um Bean origem que defina uma propriedade forçada.
  • Objetos esperando por alterações na propriedade que implementem a interface VetoableChangeListener.
  • Um objeto PropertyChangeEvent que contenha o nome da propriedade, o valor antigo e o novo valor (Observe que esse é o mesmo objeto usado pelas propriedades ligadas).

Configurando uma propriedade forçada

Um Bean configura uma propriedade forçada da forma a seguir:

  • Implementando um mecanismo que permite aos ouvintes que implementem a interface VetoableChangeListener registrem e cancelem seu interesse em receber notificações de mudanças na propriedade. AO invés de receber notificações de uma propriedade depois de alteradas, esses objetos recebem uma notificação se a propriedade recebeu uma requisição de alteração. O Bean faz isso usando a classe utilitário VetoableChangeListener, a qual tanto herda características dela quanto a instância. A classe utilitária inclui métodos par adicionar e remver objetos VetoableChangeListener a lista de ouvintes, mais um método que dispara eventos e também captura e responde adequadamente aos vetos.
  • fireVetoableChange(<property name>,
           <old value>, <new value>);
             addVetoableChangeListener(listener);
          removeVetoableChangeListener(listener);
  • Dispara requisições de alterações de propriedades aos ouvintes interessados. Já que essas mudanças precisam ser aprovadas, o Bean origem dispara eventos antes que as mudanças sejam efetivas. O Bean origem dispara PropertyChangeEvent chamando o método vetoableChange do ouvinte.
  • Fornece os meios para que os objetos ouvintes mantenham o valor original da propriedade se algum desses objetos vetar a alteração. Para fazer isso, o Bean origem questiona o vetoableChange de todos os ouvintes uma segunda vez, e passa a eles o valor original da propriedade.

Analisando um exemplo

O JellyBean implementa uma propriedade forçada, que chama priceInCents. O Bean começa pelo instanciamento de dois novos objetos: um objeto vetoableChangeSupport para manter a lista de ouvintes e um objeto PropertyChangeSupport que mantem a lista de alterações na propriedade. Isso é feito dessa maneira:

private VetoableChangeSupport vetos =
         new VetoableChangeSupport(this);
private PropertyChangeSupport changes =
          new PropertyChangeSupport(this);

Em seguida, o JellyBean implementa a interface VetoableChangeSupport para registar ou cancelar os ouvintes:

public void addVetoableChangeListener(
              VetoableChangeListener l) {
	vetos.addVetoableChangeListener(l);
}
public void removeVetoableChangeListener(
                VetoableChangeListener l) {
     vetos.removeVetoableChangeListener(l);
}

Os métodos add e remove acima aplicam-se a todas as propriedades foçadas do Bean. É possível especificar que os ouvintes estejam sintonizados a uma propriedade especifica do Bean. Se isso for desejado, esses dois métodos incluirão um parâmetro String adicional contendo o nome da propriedade. O nome da propriedade aparece na primeira prosição da lista de parâmetros, seguido pelo objeto ouvinte. Por exemplo, para adicionar um Bean ouvindo uma propriedade específica:

public void addVetoableChangeListener(
                      String propertyName,
		      VetoableChangeListener
		                 listener);

Você também pode registrar ou cancelar ouvintes por propriedade especificando um nome de propriedade em particular, como segue:

void add<PropertyName>Listener(
              VetoableChangeListener p);
void remove<PropertyName>Listener(
               VetoableChangeListener p);

O JellyBean define um método para obter o preço atual e outro método para configurar um novo preço. No método para configurar o novo preço, o JellyBean invoca o método fireVetoableChange para notificar os ouvintes:

public void setPriceInCents(int newPriceInCents)
         throws PropertyVetoException {
	vetos.fireVetoableChange("priceInCents",
	new Integer(oldPriceInCents),
	new Integer(newPriceInCents));
	//if vetoed (PropertyVetoException thrown)
	// don't catch
	// exception here so routine exits.
	//if not vetoed, make the property
	// change
	ourPriceInCents = newPriceInCents;
	changes.firePropertyChange(
	           "priceInCents",
	new Integer(oldPriceInCents),
	new Integer(newPriceInCents));
}

Quando o Bean executa o método fireVetoableChange, ele notifica os ouvintes da mudança proposta à propriedade priceInCents, e informa elas do proeço atual e do preço proposto. Se o método for executado com sucesso – isso é, sem que dispare nenhuma exceção – significa que nenhum método vetou a mudança. O método setPriceInCent então executa o método firePropertyChange para disparar um evento que avisa os ouvintes que a propriedade foi alterada.

Porém, se o método fireVetoableChange causar uma exceção PropertyVetoException, isso indica que o ouvinte vetou a alteração proposta. A alteração então não surtirá efeito. O método setPriceInCents não pegará a exceção, o programa deixará o método e permitirá que a exceção seja manipulada por um nível mais alto.

Configurando ouvintes para propriedades forçadas

Os objetos que escutam as propriedades forçadas implementam a interface VetoableChangeListener, que inclui o método vetoableChange. Quando o Bean origem dispara um evento de alteração de uma propriedade, é chamado o método vetoableChange para cada ouvinte registrado. O Ouvinte usa esse método para receber o evento da alteração e aceita ou rejeitar a alteração proposta. Se a alteração for rejeitada, uma exceção PropertyVetoException é disparada.

De modo análogo as propriedades ligadas, seu ouvinte pode implementar a interface VetoableChangeListener e o método vetoableChange ele mesmo, ou você pode deixar que o BeanBox gere uma classe adaptadora ligada ao evento que forneça essa implementação para o seu Bean. Se você deixou o BeanBox gerar a classe, então o ouvinte não precisa implementar a interface VetoableChangeListener. Você precisa apenas implementar o método vetoableChange, que faz o seguinte:

public void vetoableChange(
                        PropertyChangeEvent x)
	         hrows PropertyVetoException {
	if (vetoAll) {
	throw new PropertyVetoException(
	                "NO!", x);
	}
}

Propriedades forçadas no BeanBox

O BeanBox manipula propriedades forçadasde modo bem parecido como faz com propriedades ligadas.

  1. Arraste o Bean que defina a propriedade forçada no BeanBox. Nosso exemplo uso o JellyBean.
  2. Arraste um Bean ouvinte e que possa potencialmentevetarcadas mudança. Em nosso exemplo, o Bean Voter, que veta as mudanças a essa propriedade.
  3. Escolha o evento vetoableChange para o JellyBean. Selecione o JellyBean e, no menu Edit, selecione Events > vetoableChange > vetoableChange.
  4.  

  5. Conecte o evento ao ouvinte e selecione o método vetoableChange.
  6. Conecte a linha tracejada à instância do Bean Voter. Quando o painel EventTargetDialog aparecer, selecione o método vetoableChange. O BeanBox gera a ligação com o evento.
  7. Teste a propriedade. Selecione o JellyBean e, na planilha de propriedades, tente mudar a propriedade priceInCents. Na janela do terminal onde você iniciou o BeanBox, você verá uma mensagem indicando se uma exceção foi disparada e a mudança foi vetada.

Manipulando propriedades
Redimensionamento automático do Bean

Até agora, vimos como gerar um label padrão no construtor do Bean. Uma forma de melhor de fazer isso é sobrecarregar os label padrão definindo um segundo construtor que aceita um argumento String. Por exemplo:

public Acme05Bean(String label) {
  super();
  this.label = label;
  setFont(new Font("Dialog",
                   Font.PLAIN, 12));
}

Exibir um label personalizado em um Bean expoe novos desafios. Mantenha em mente que o label pode ser:

  • Configurado programaticamente pelo construtor
  • Alterado em tempo de desenho pela ambiente de desenvolvimento
  • Modificado em tempo de execução pela chamada ao método setLabel

É importante programar o objeto de forma flexivel ao tamanho do Bean. O que acontece, por exemplo, se o label for muito grande? Obviamente a caixa do Bean precisa ser ajustada para acomodar o label fornecido pelo construtor. Um chamada ao comando resize, com seus parâmetros codificados no construtor, não faz sentido uma vez que podemos alterar o conteúdo do label.

public Acme04Bean() {
      // obsolete constructor
         resize(60,40);/
      // not flexible for customizable label
		this.label="Bean";
		...
}

A API do JavaBean fornece uma forma conveniente paa especificar o tamanho preferido do Bean durante a execução. Defina métodos que especifiquem o tamanho preferido e mínimo de seu Bean.

Usando essa técnica de redimensionamento automático, você pode omitir a chamada a resize do construtor. Por ter definido um construtor que aceite uma String como argumento, o código do construtor padrão pode ser reduzido para apenas suprir um label padrão para o botão.

public Acme05Bean() {
 this("AcmeBean Serial# 05");
}

Use um label padrão longo intencionalmente para mostrar como o botão pode ser dimensionado automaticamente. Existem duas ocasiões para quando um botão precisa ser ajustado para se acomodar ao tamanho do label:

  • Quando é criado inicialmente, como quando o botão é selecionado da paleta do ambiente de desenvvolvimento e carregada no formulário.
  • Quando o label é modificado durante o desenho pela edição do label na planilha de propriedades.

Definindo os tamanhos do Bean

Você pode definir tanto o tamanho preferido quanto o minimo do Bean. Você pode definir um método chamado getPreferredSize e o BeanBox o chama automaticamente quando você arrasta uma nova instãncia do Bean da paleta correspondente para o formulário da aplicação.

public Dimension getPreferredSize() {
   FontMetrics fm =
           getFontMetrics(getFont());
   return new Dimension(fm.stringWidth(label)
                           + TEXT_XPAD,
   fm.getMaxAscent() + fm.getMaxDescent()
                        + TEXT_YPAD);
}

Esse algortimo é similar ao cálculo do tamanho do método paint do Bean para centralizar a string dentro do button. O acolchoamente é adicionado fora da string para que o label não pareça cheio. Nesse caso, porém, o método retorna uma dimensão para o tamanho preferido ao invés de desenhar a string dentro do botão. As contantes simbólicas para o acolchoamente são definidas como variáveis de instância do Bean para serem suadas pelo botão, como segue:

static final int TEXT_XPAD = 12;
static final int TEXT_YPAD = 8;

Especificando um tamanho mínimo

Além do tamanho preferido do Bean, a API do JavaBeans fornece um método getMinimumSize que permite que você especifique u mtamanho mínimo para o Bean. Ambos os métodos ajudam o ambiente de desenvolvimento a determinar como desenhar seu Bean durante o desenho da aplicação. Para manter as coisas simples, defina o tamanho mínimo do Bean igual o tamanho preferido:

public Dimension getMinimumSize() {
		return getPreferredSize();
}

Alterar o tamanho durante o desenho da aplicação

Agora, você precisa manipular a situação onde o tamanho do Bean muda durante o desenho da aplicação quando alguém edita o label na planilha de propriedades. Até agora, você poderia mudar o label durante do desenho, mas o botão não seria redimensionado para acomodar um label de certos tamanhos. para botões típicos, você deseja que o seu tamanho seja redimensionado a cada vez que o label seja alterado no editor de propriedades – isso é, para cada letra o botão deveria crescer ou encolher de acordo com o novo tamanho da label. Esse trabalho é feito pelo método sizeToFit.

private void sizeToFit() {
   Dimension d = getPreferredSize();
   resize(d.width, d.height);
   Component p = getParent();
      if (p != null) {
      p.invalidate();
      p.layout();
		}
}

Observe que o sizeToFit chama o método getPreferredSize toda vez que o tamamnho do Bean precisa ser recalculado. (O BeanBox também chama o método quando for redimensionar o Bean). Observe que a chamada ao método resize também é feita deste método, ao invés do construtor declarado anteriormente. Você pode dar ao método sizeToFit o nome que você quiser, mas precisa chama-lo de setLabel. O ambiente de desenvolvimento chama o método setLabel automaticamente a cada modificação feita na propriedade label no editor de propriedades. Definir sizeToFit para ser seu método de trabalho facilita as modificações que precisem ser feitas em setLAbel; você precisa adicionar uma única linha no final do método:

public void setLabel(String newLabel) {
	String oldLabel = label;
	label = newLabel;
	sizeToFit();// new line
}

Colocando um efeito 3D

A mudança final nessa versão de AcmeBean é criar um efeito 3D, tornando a aparência mais parecida com um botão AWT. Abaixo segue o código para conseguir esse efeito:

  1. Remova as chamadas para fillArc e fillRec.
    // obsolete version
    public void paint(Graphics g) {
    		...
    	g.fillRect(20, 5, 20, 30);
    	g.fillArc(5, 5, 30, 30, 0, 360);
    	g.fillArc(25, 5, 30, 30, 0, 360);
    		...
    }
  2. Substitua elas pela renderização retângular do botão, baseado na largura e altura do Canvas em que foi desenhado:
    g.fillRect(1, 1, width - 2, height - 2);
    g.draw3DRect(0, 0, width - 1,
                          height - 1, true);

    Observe que o último argumento ee draw3DRect é booleano, e determina se o retângulo deve ser mostrado em alto ou baixo relevo. Depois, você modificará essa chamada a draw3DRect para que o visual do botão reflita os estados do mouse pressionado ou não.

  3. Desenhe uma borda em volta do botão:
    g.drawRect(2, 2, width - 4, height - 4);

    Para manter as coisas arrumadas, sincronize o método paint. Abaixo segue a nova definição:

    public synchronized void paint(Graphics g) {
    		int width = size().width;
    		int height = size().height;
    
        g.setColor(beanColor);
    	g.fillRect(1, 1, width - 2, height - 2);
    	g.draw3DRect(0, 0, width - 1, height - 1,
    	                                  true);
    	g.setColor(getForeground());
    	g.setFont(getFont());
    	g.drawRect(2, 2, width - 4, height - 4);
    	FontMetrics fm = g.getFontMetrics();
    	g.drawString(label, (width -
    	            fm.stringWidth(label)) / 2,
    		    (height + fm.getMaxAscent() -
    			     fm.getMaxDescent())
    			                    / 2);
    }

Manipulando eventos

Um Bean que gera eventos precisa manter o rastro dos eventos disparados. No modelo de delegação de eventos, o mecanismo de eventos é separado conceitualmente em envio de eventos e manipulação de eventos. O envio de eventos é de responsabilidade da origem do evento; a manipulação é de resposabilidade do ouvinte. Qualquer objeto que quiser saber quando um evento foi disparado pode dizer ao Bean para ser informado sobre eventos em particular. Em outras palavras, um ouvinte registra seu interesse em um evento chamando um método pre-determinado da origem.

Registrando ouvintes de eventos

Considere um botão típico que gere eventos quando pressionado. Um ouvinte poderia incrementar um contados a cada vez que o botão seja pressionado.

Se o botão quiser ser uma fonte de eventos, precisa fornecer dois métodos que podem ser chamados pelos objetos interessados. Um método adiciona objetos a uma lista d eouvintes que serão notificados quando um evento ocorres. O outro método remove o objeto da lista.

public synchronized void addActionListener
	(ActionListener l) {...}
public synchronized void removeActionListener
 (ActionListener l) {...}

Nomeando ouvintes de eventos

De forma análoga as propriedades, a assinatura dos nomes dos métodos precisam seguir um padrão específico. O mecanismo de introspecção do Java detecta esse padrão e pode determinar os eventos que o Bean origem gera a partir dos métodos registrados, junto com o tipo dos argumentos deles.

O mecanismo de introspecção do Java reconheçe os seguintes padrões para os recursos de geração de eventos:

public synchronized
           void addTYPE(TYPE listener);
public synchronized
        void removeTYPE(TYPE listener);

Observe que TYPE deve ser substituido pelo nome da classe de um ouvinte de eventos em particular; por exemplo, MouseListener ou MouseMotionListener.

Seguindo o ActionEvent

Quando os métodos de registro de eventos são definidos para nosso botão do exemplo, o mecanismo de introspecção do Java é capaz de determinar que um ActionEvent pode ser gerado para o botão. Se o contador do objeto quiser ser notificado quando um ActionEvent ocorre, deve chamar o método addActionListener do botão, passando ele mesmo como argumento. Para fazer isso, o contador ter primeiro que implementar a interface ActionListener, já que o argumento para o método é um objeto ActionListener.

O botão precisa rastrear os ouvintes que se registraram para recer notificações de ActionEvents. Aqui é onde a sentença de importação de Vector trabalha. O botão mantém ua lista d eouvintes. Assim, o Bean origem declara a seguinte linha:

private Vector listeners = new Vector();

Quando o método addActionListener é chamado, o ouvinte fornecido como um argumento para o método é anexado a lista, como segue:

public synchronized void addActionListener
	(ActionListener l) {
        listeners.addElement(l);
}

De forma análoga, quando o método removeActionListener for chamado, o ouvinte fornecido como argumento é removido da lista:

public synchronized void removeActionListener
     (ActionListener l) {
		listeners.removeElement(l);
}

Enviando Eventos para os ouvintes

Quando um evento é disparado, a fonte do evento iterage através da lista de ouvintes e envia a cada um uma notifivação do ActionEvent. É o método fireAction mostrado abaixo:

public void fireAction() {
...
  Vector targets;
  synchronized (this) {
    targets = (Vector) listeners.clone();
	}

   ActionEvent actionEvt =
            new ActionEvent(this, 0, null);
     for (int i = 0; i < targets.size(); i++) {
	ActionListener target =
	(ActionListener)targets.elementAt(i);
	target.actionPerformed(actionEvt);
		}
		...
}

Controlando o comportamento do Bean com Eventos

Com duas mudanças simples, você pode tornar o botão mais redondo – isso é, mais parecido com um botão AWT normal. Quando o botã ofor pressionado, ele vai parecer mais baixo; quando for solto, ele aparecerá levantado.

  1. Adicione uma variável de instância, down, para manter o rastro de quando o botão estiver pressionado ou não:
    private boolean down;
  2. Dentro do método paint do botão, mude a renderização dependendo do valor de down. Mude:
    g.draw3DRect(0, 0, width - 1, height - 1,
                                  true);

    para:

    g.draw3DRect(0, 0, width - 1,
                    height - 1, !down);

    Sem essa modificação, o botão seria sempre desenhado no estado levantado (já que o último argumento seria sempre true). Com essa modificação, o botão seria desenhado no estado levantado somente quando down for false.

    Para que tudo isso funcione adequadamente, precisamos escrever uma pequena quantidade de código de manutenção para certificar que o valor de down esteja correto. Faça isso em handleEvent.

  3. Adicione uma opção case para os eventos relacionados ao botão do mouse.
    public boolean handleEvent(Event evt) {
    ...
     switch (evt.id) {
     case Event.MOUSE_DOWN:
     down = true;
     repaint();
     return true;
    ...
    }

    Quando o mouse for pressionado, esse código muda o estado do botão para down e requisita o re-desenho do botão, retornando true para indicar que o evento foi tratado.

  4. Altere a clausula case para o evento de liberação do mouse. Mude disso:
    case Event.MOUSE_UP:
      fireAction();
      return true;

    para

    case Event.MOUSE_UP:
      if (down) {
       fireAction();
       down = false;
       repaint();
    }
    return true;

Antes dessas mudanças no handleEvent, o botão não seria re-desenhado quando um evento do mouse ocorresse. Como essas mudanças, será sempre re-desenhado nesses casos, assim a posição do botão refletirá corretamente a ação do mouse. Observe que fireAction é chamado, como nos exemplos anteriores, para enviar um ActionEvent para os ouvintes registrados.

Traduzido de http://java.sun.com