inhoudstafel en auteursrecht
* STER *


8. Visueel programmeren met componenten

8.1 Begrippen

8.1.1 Component

Een component is een zelfstandige, herbruikbare programma-eenheid die visueel kan geïntegreerd worden in een compositie met behulp van een visueel systeem voor de ontwikkeling van toepassingen.

In het component/containermodel van software-ontwikkeling zijn er twee verschillende rollen voor de programmeurs. Enerzijds heb je de bouwers van componenten, anderzijds de toepassingsprogrammeurs die bestaande componenten verwerken tot grotere, praktisch bruikbare gehelen. Het grotere geheel wordt dan een container genoemd.

'Insert Object': een Bean toevoegen in de ontwikkelingsomgeving Symantec Visual Cafe
een Bean toevoegen in de ontwikkelingsomgeving Symantec Visual Cafe

Voorbeeld: een elektronische agenda zou kunnen opgebouwd zijn met behulp van een kalender en een database-component.

Bij software-ontwikkeling in Java kunnen de containers zelfstandige toepassingen zijn, maar ook applets, servlets en zelfs andere componenten, zodat een vertakte hiërarchie ontstaat.

'Add components': een Bean toevoegen in de ontwikkelingsomgeving Inprise JBuilder
een Bean toevoegen in de ontwikkelingsomgeving Inprise JBuilder

8.1.2 JavaBean

Het standaard Java-paradigma voor component based development heet JavaBeans. Technisch is een Bean eender welke klasse. De bruikbaarheid van een Bean als component hangt af van de mate waarin bepaalde conventies worden gehanteerd bij de naamgeving en de werking van de publieke methoden van die klasse.

Meestal wordt van een JavaBean-klasse aangenomen dat ze de interface java.io.Serializable implementeert, al staat dit strikt genomen niet in de technische specificatie. Overigens is dit een erg eenvoudige interface om te implementeren: hij bevat namelijk geen enkele methode. Als een klasse de interface java.io.Serializable implementeert, dan kunnen objecten van die klasse in een gecodeerd formaat worden verstuurd via netwerken of weggeschreven naar een serieel bestand.

8.1.3 Architectuur

Voor het succesvol gebruik van componenten is het bijzonder belangrijk dat de samenwerking tussen componenten en container nauwkeurig gedefinieerd wordt in een goed afgelijnd raakvlak.

Een JavaBean-component biedt publieke methoden aan. Hij is eveneens in staat bepaalde gebeurtenissen af te handelen, en andere gebeurtenissen zelf te veroorzaken. Een component heeft geen publieke attributen.

Niet alleen de container moet op de hoogte zijn van de diensten die de Bean aanbiedt, maar ook de ontwikkelingsomgeving. De ontwikkelaar van een Bean moet dus eigenlijk twee raakvlakken definiëren: één met de eigenlijke toepassing, en één met de visuele omgeving waarin de toepassing gebouwd wordt.

De ontwikkelingsomgeving achterhaalt de diensten van een Bean op twee manieren:

8.1.4 Een Bean ontwikkelen

Zoals gezegd zijn bij het component/containermodel voor software-ontwikkeling twee verschillende rollen voor de ontwikkelaars: de makers van nieuwe componenten resp. degenen die de componenten integreren in een groter geheel. Deze tekst gaat vooral over de eerste rol.

Sun levert een gratis ontwikkelingsomgeving voor het testen van Beans: de Beans Development Kit (BDK). Momenteel (april 2001) is de meest recente versie BDK1.1. Voor het eigenlijke programmeren van de Beans is geen speciale omgeving nodig, bijvoorbeeld de gewone Java Development Kit volstaat.

Het belangrijkste hulpmiddel in de BDK is de BeanBox. Dat is een primitieve simulatie van een ontwikkelingsomgeving waar JavaBeans kunnen geïntegreerd worden in een eenvoudige container (een formulier). De BeanBox kan bijvoorbeeld automatisch een applet genereren die de gevraagde componenten bevat.

Na installatie van de BDK kan de BeanBox bijvoorbeeld als volgt gestart worden.

  1. open een DOS-commandovenster
  2. ga naar de map c:\bdk1.1\beanbox (of een andere map, naargelang waar de Bean Development Kit geïnstalleerd is), bijvoorbeeld door de typen cd c:\bdk1.1\beanbox
  3. typ run
  4. er verschijnen nu vier vensters: de ToolBox, de BeanBox, het venster Properties en de Method Tracer
vier vensters: ToolBox, BeanBox, Properties, Method Tracer

Enkele belangrijke functies van de BeanBox zijn:

8.2 Eenvoudig voorbeeld

Gebruik een teksteditor (bijvoorbeeld Notepad/Kladblok) om de volgende klassedefinitie in te voeren in een bestand met de naam RoodVlak.java.

import java.awt.*;
import java.io.Serializable;

public class RoodVlak
  extends Canvas implements Serializable {
  public RoodVlak() {
    setSize(60, 40);
    setBackground(Color.red);
  }
}

Het enige wat een object van het type RoodVlak onderscheidt van een gewone rode rechthoek is het feit dat we de interface Serializable implementeren.

Compileer het bestand (zoals gewoonlijk, bijvoorbeeld met javac RoodVlak.java om een binair klassenbestand RoodVlak.class te verkrijgen.

Alvorens een JavaBean in de BeanBox op te nemen, moeten we hem verpakken in een archief met behulp van het programma jar (Java archive), dat we bespraken in hoofdstuk 5b. Dit programma maakt gewoon deel uit van de JDK. Als onderdeel van het archief moeten we ook een manifestbestand opnemen. Dat manifestbestand, dat bijvoorbeeld manifest.txt kan heten, heeft de volgende inhoud:

Name: RoodVlak.class
Java-Bean: True

Nauwkeurig ! En met een regeleinde na de laatste regel. Let op de hoofdletter T van True

Creëer een archiefbestand RoodVlak.java met de opdracht

jar cfm RoodVlak.jar manifest.txt RoodVlak.class

Open het archiefbestand in de BeanBox met het menu-commando File/LoadJar... Nu verschijnt de nieuwe JavaBean RoodVlak onderaan in het lijstje van de ToolBox.

Je kan een JavaBean uit de ToolBox inlassen in de container van de BeanBox door het desbetreffende item te selecteren in de ToolBox, en vervolgens een rechthoek te slepen in de BeanBox. Het resultaat ziet er ongeveer als volgt uit.

rode rechthoek

Opdracht

Experimenteer met de verschillende types Bean die in de ToolBox beschikbaar zijn.

8.3 Eigenschappen

Het venster "Properties" (eigenschappen) verandert van uitzicht naargelang van de component die op elk moment geselecteerd is. Ook de container als geheel kan worden geselecteerd door met de muis buiten het domein van de componenten te klikken.

Eigenschappen kunnen worden gewijzigd door erop te klikken in het Properties-venster. Soms, zoals bij een tekstvak, kan je dan gewoon een waarde invullen; soms, zoals bij kleuren, komt een aparte dialoog tevoorschijn om een nieuwe waarde van de eigenschap te specificeren:

ColorEditor: yellow

8.3.1 Een eigenschap toevoegen

Je kan een nieuwe eigenschap aan een Beantype toekennen door de creatie van twee methoden die aan bepaalde vormvereisten voldoen:

  1. De namen van de methoden zijn identiek, behalve dat de ene met het woord get en de andere met het woord set begint;
  2. beide methoden zijn niet-statisch en publiek
  3. de setter-methode heeft één parameter en heeft terugkeertype void; de getter-methode geen enkele parameter, en als terugkeertype het parametertype van de settermethode.

In tegenstelling tot wat de benaming 'eigenschap' doet vermoeden, is het dus niet nodig een attribuut te declareren. Natuurlijk zullen eigenschappen in de praktijk vaak geïmplementeerd worden door een private attribuut, dat door de twee methoden respectievelijk geraadpleegd en ingevuld wordt.

Voorbeeld

We geven aan bovenstaande Bean RoodVlak een extra eigenschap, Tekst. Daartoe declareren we een attribuut

private String woorden = "";

en twee methoden

public void setTekst(String t) {
  woorden = t;
}

public String getTekst() {
  return woorden;
}

Op die manier kunnen eigenschappen worden gecreëerd van de types String, Color, Font en int. Voor andere (object)types moet een aparte editor worden gedefinieerd - zie verder onder 'aanpassing'. Overigens zijn de 'standaard' zichtbare eigenschappen (font, name, foreground en background) gewoon afkomstig van de publieke methoden van de klasse Component, geërfd door de klasse Canvas...

Wil je in bovenstaande Bean de eigenschap tekst ook echt zichtbaar maken in de Beanrechthoek zelf, dan moet je de methode paint herdefiniëren:

public void paint(Graphics g) {
  g.drawString(woorden, 5, 35);
}

In dat geval verdient het tevens aanbeveling, op het einde van de setTekst-methode de opdracht

  repaint();

op te nemen.

Uitgebreider voorbeeld: flexibele muntconvertor

Het nu volgende voorbeeld illustreert dat de eigenschappen die de Bean in de BeanBox laat zien, niet noodzakelijk overeenkomen met de 'zichtbare' eigenschappen van de componenten in de draaiende toepassing.

We beginnen met een Bean zonder speciale eigenschappen. Het betreft een paneel met enkele tekstvelden en een drukknop, om de conversie van geldbedragen van Euro naar Frank om te rekenen.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Convertor extends JPanel
  implements java.io.Serializable, ActionListener {
  JButton btnConverteer = new JButton("Converteer");
  JLabel lblOorsprong = new JLabel("Euro");
  JLabel lblDoel = new JLabel("Frank");
  JTextField txtOorsprong = new JTextField(15);
  JTextField txtDoel = new JTextField(15);

  public Convertor() {
    setLayout(new GridLayout(3, 2, 10, 10));
    add(txtOorsprong);
    add(lblOorsprong);
    add(btnConverteer);
    btnConverteer.addActionListener(this);
    add(new JPanel());  // opvulsel
    add(txtDoel);
    add(lblDoel);
  }

  public void actionPerformed(ActionEvent e) {
    if (e.getSource().equals(btnConverteer)) {
      try {
        double bedragOorsprong = Double.parseDouble(txtOorsprong.getText());
        double bedragDoel = bedragOorsprong * 40.3399;
        txtDoel.setText("" + bedragDoel);
      }
      catch (NumberFormatException exc) {
        txtDoel.setText("Error");
      }
    }
  }
}

Na compilatie, verwerking tot archiefbestand en laden in de BeanBox geeft dit inderdaad een werkende Eurocalculator:

15.30 Euro, Converteer, 617.20047 Frank

We zullen nu vier aspecten van deze Bean "flexibiliseren", zodat ze door een toepassingsprogrammeur eenvoudig kunnen worden aangepast:

  1. de naam van de munt van oorsprong
  2. de naam van de doelmunt
  3. de tekst op de drukknop
  4. de conversiefactor

We doen dit door het scheppen van vier eigenschappen: oorsprong, doel, knoptekst en factor.

  public void setOorsprong(String naam) {
    //...
  }
  public String getOorsprong() {
    //...
  }
  public void setDoel(String naam) {
    //...
  }
  public String getDoel() {
    //...
  }
  public void setKnoptekst(String tekst) {
    //...
  }
  public String getKnoptekst() {
    //...
  }
  public void setFactor(double f) {
    //...
  }
  public double getFactor() {
    //...
  }

Oefening 8.1

Implementeer bovenstaande acht methoden. Voeg de nodige private attributen toe. Pas de bestaande code aan zodat ze gebruik maakt van de nieuwe eigenschappen. Importeer de nieuwe Bean in de BeanBox en maak er een duim/centimeter-convertor van door enkele simpele ingrepen in het venster Properties. (1" = 2.51 cm)

3.5 '', Omzetting, 8.785 cm

Oplossing


8.3.2 Gebonden eigenschappen

Een eigenschap heet gebonden als eventuele wijzigingen in de eigenschap automatisch aanleiding geven tot een gebeurtenis. Dergelijke gebeurtenissen zijn van het type PropertyChangeEvent. De eenvoudigste manier om een PropertyChangeEvent te genereren is door gebruik te maken van de methode firePropertyChange van de hulpklasse java.beans.PropertyChangeSupport.

Daarnaast moet een bean met een of meer gebonden eigenschappen ook de methoden addPropertyChangeListener en removePropertyChangeListener implementeren:

public void addPropertyChangeListener(PropertyChangeListener l);

public void removePropertyChangeListener(PropertyChangeListener l);

Ook dit kan eenvoudig worden opgelost door de gelijknamige methoden van de hulpklasse PropertyChangeSupport op te roepen.

Voorbeeld

We maken een bean die bestaat uit een getal en een bijhorende tekst (zowel visueel als in de vorm van design-time eigenschappen). Veranderingen in het getal genereren een PropertyChangeEvent. Het getal is een int, maar de methode firePropertyChange ontvangt een Integer omdat ze geen primitieve gegevenstypes aanvaardt.

import java.awt.*;
import javax.swing.*;
import java.beans.*;

public class TekstEnGetal extends JPanel {
  private int hetGetal = 0;
  private String deTekst = "";
  private JLabel txtGetal = new JLabel(hetGetal + "");
  private JLabel txtTekst = new JLabel(deTekst);
  private PropertyChangeSupport sup
    = new PropertyChangeSupport(this);

  public TekstEnGetal() {
    setLayout(new GridLayout(2, 1, 10, 10));
    add(txtTekst);
    add(txtGetal);
  }

  public int getWaarde() {
    return hetGetal;
  }
  public void setWaarde(int w) {
    Integer oudeWaarde = new Integer(hetGetal);
    hetGetal = w;
    sup.firePropertyChange("waarde", oudeWaarde, new Integer(hetGetal));
    txtGetal.setText(hetGetal + "");
  }

  public String getTitel() {
    return deTekst;
  }
  public void setTitel(String t) {
    deTekst = t;
    txtTekst.setText(deTekst);
  }

  public void addPropertyChangeListener(PropertyChangeListener l) {
    sup.addPropertyChangeListener(l);
  }
  public void removePropertyChangeListener(PropertyChangeListener l) {
    sup.removePropertyChangeListener(l);
  }
}

Om een dergelijke gebeurtenis op te vangen, moet een bean (meestal een andere bean) de interface java.beans.PropertyChangeListener implementeren. Het mechanisme is hetzelfde als bij het opvangen van gebeurtenissen in een grafische gebruikersinterface (zie paragraaf 5.3): één component genereert een gebeurtenis, een ander object handelt de gebeurtenis af. De koppeling van de twee wordt tot stand gebracht door een oproep van de methode addPropertyChangeListener van de eerste.

Het verschil ligt hier niet zozeer in de programmacode, maar in de wijze waarop de programmacode wordt gemaakt. De koppeling kan namelijk automatisch tot stand worden gebracht door de ontwikkelingsomgeving (bijvoorbeeld de BeanBox) door een zogenaamde "adaptor class" te genereren. We zullen de aanroep van de methode addPropertyChangeListener dus niet intypen, maar genereren vanuit de BeanBox.

De interface PropertyChangeListener bestaat slechts uit één methode:

public void propertyChange(PropertyChangeEvent e);

Voorbeeld (vervolg)

We maken de bean RoodVlak van paragraaf 5.2 gevoelig voor de veranderingen in een bean van het type TekstEnGetal. Als de getalwaarde verandert, gaat de afhandelaar aan de bean die de gebeurtenis veroorzaakte, zijn titel opvragen. Als dit één van de drie woorden "rood", "groen" of "blauw" is, dat wordt de kleur van het paneel aangepast: de overeenkomstige kleurcomponent krijgt de gegeven getalwaarde tussen 0 en 255.

import java.awt.*;
import java.beans.*;

public class KleurVlak
  extends Canvas implements PropertyChangeListener {
  public KleurVlak() {
    setSize(60, 40);
    setBackground(Color.red);
  }

  public void propertyChange(PropertyChangeEvent e) {
    Object bron = e.getSource();
    if (bron instanceof TekstEnGetal) {
      TekstEnGetal teg = (TekstEnGetal) bron;
      String titel = teg.getTitel();
      Color c = getBackground();
      int r = c.getRed();
      int g = c.getGreen();
      int b = c.getBlue();
      Integer waarde = (Integer) e.getNewValue();
      if (titel.equalsIgnoreCase("rood"))
        setBackground(new Color(waarde.intValue(), g, b));
      else if (titel.equalsIgnoreCase("groen"))
        setBackground(new Color(r, waarde.intValue(), b));
      else if (titel.equalsIgnoreCase("blauw"))
        setBackground(new Color(r, g, waarde.intValue()));
    }
  }
}

Eigenschappen binden in de BeanBox

Verschillende beans in eenzelfde container beschikken niet noodzakelijk over referenties naar elkaar. In het bijzonder weet de bean die voor de afhandeling van een gebeurtenis verantwoordlijk is, niet altijd bij welke bronbean ze zich als Listener moet registreren. Het is aan de ontwikkelingsomgeving (bijvoorbeeld de BeanBox) om de ontwerper de verbinding op grafische wijze te laten leggen. We illustreren dit door een verband te leggen tussen een exemplaar van de twee klassen TekstEnGetal en KleurVlak hierboven.

Importeer beide beans in de BeanBox. Je kan dit bijvoorbeeld doen via eenzelfde archief, met als manifest (in het bestand TekstEnGetal.txt)

Name: TekstEnGetal.class
Java-Bean: True

Name: KleurVlak.class
Java-Bean: True

en met de DOS-commandos

javac TekstEnGetal.java KleurVlak.java
jar cfm TekstEnGetal.jar TekstEnGetal.txt TekstEnGetal.class KleurVlak.class

Start de BeanBox en gebruik het menu-item File/LoadJar... om het archief TekstEnGetal.jar te laden. In de Toolbox moeten nu onderaan twee nieuwe bean-types beschikbaar zijn: TekstEnGetal en KleurVlak. Breng van elk van beide types telkens één exemplaar in het BeanBox-ontwerpvenster aan.

ToolBox: ..., KleurVlak, TekstEnGetal - BeanBox: (rode rechthoek), (smalle rechthoek met cijfer 0)

Activeer de tweede component (die van het type TekstEnGetal) en sleep aan een van zijn hoeken zodat hij een tweetal centimeter breed wordt. Kies het menu-item Edit/Events/propertyChange/propertyChange. De muisaanwijzer begeleidt nu het uiteinde van een lijn waarvan het beginpunt vastgemaakt is aan de actieve bean. Klik op de andere bean (van het type KleurVlak). Het lijnstuk verdwijnt en er verschijnt een dialoogvenster met alle publieke methoden van de klasse KleurVlak. Selecteer de methode propertyChange en druk op OK.

Please chose (sic) a target method: ..., propertyChange, ...

De BeanBox genereert nu intern een nieuwe klasse die voor de communicatie tussen de twee beans zorgt. Een dergelijke klasse staat in handboeken over object-georiënteerd ontwerp bekend als een adaptor, zoals ook het dialoogvenster vermeldt.

Generating and compiling adaptor class

We zijn nu klaar om gebeurtenissen te generern. Zet via het venster Properties de eigenschappen titel en waarde van de bean TekstEnGetal respectievelijk op blauw en 255. Dit laatste produceert een gebeurtenis van het type PropertyChangeEvent. De bean KleurVlak detecteert dat deze gebeurtenis afkomstig is van een TekstEnGetal met titel "blauw", en past dus zijn kleur aan door de blauwe component van de rgb-waarden op 255 te zetten (de rode component was al 255, het resultaat is dus magenta).

magenta rechthoek - 'blauw' 255

Door dezelfde bean daarna achtereenvolgens de titels "groen" en "rood" te geven, kan je de rechthoek eender welke kleur doen aannemen. Je kan natuurlijk ook gebeurtenissen laten afvuren door drie verschillende exemplaren van het beantype TekstEnGetal.

De adaptor class die intern gegenereerd is, heeft niets magisch. Ze bevindt zich bijvoorbeeld in de map c:\bdk1.1\beanbox\tmp\sunw\beanbox en ziet er ongeveer als volgt uit.

// Automatically generated event hookup file.

package tmp.sunw.beanbox;
import KleurVlak;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;

public class ___Hookup_1700aa3ba8
  implements java.beans.PropertyChangeListener, java.io.Serializable {

    public void setTarget(KleurVlak t) {
        target = t;
    }

    public void propertyChange(java.beans.PropertyChangeEvent arg0) {
        target.propertyChange(arg0);
    }

    private KleurVlak target;
}

Het adaptor-object wordt geconstrueerd met behulp van het KleurVlak-object dat de gebeurtenissen moet ontvangen. Het adaptor-object wordt geregistreerd (door de omgeving) als een PropertyChangeListener die geïnteresseerd is in dergelijke events vanwege het TekstEnGetal-object.

8.3.3 Beperkte eigenschappen

In voorbereiding

8.3.4 Geïndexeerde eigenschappen

In voorbereiding

8.4 Gebeurtenissen

Een PropertyChangeEvent (zie paragraaf 8.3.2) is niet de enige soort gebeurtenis die door JavaBeans kan worden voortgebracht. De ontwerper kan ook zelf nieuwe soorten gebeurtenissen definiëren. Nieuwe types van gebeurtenissen moeten aan de volgende naamconventies voldoen.

We gaan ervan uit dat de gebeurtenis de naam G draagt. In onze voorbeelden gaan we ervan uit dat we een nieuwe soort gebeurtenis, KleurGewijzigd, willen definiëren.

  1. Declareer een publieke klasse met de naam GEvent die rechtstreeks of onrechtstreeks is afgeleid van de klasse java.util.EventObject. Bijvoorbeeld
    public class KleurGewijzigdEvent extends java.util.EventObject {
      // ...
    }
    
    Hou er rekening mee dat de constructor van de klasse EventObject een parameter verwacht (het object dat de gebeurtenis voortbrengt), dus je eigen klasse zal waarschijnlijk een constructor hebben die begint met een aanroep van super(...).
  2. Declareer een publieke interface met de naam GListener die rechtstreeks of onrechtstreeks is afgeleid van de interface java.util.EventListener. De interface heeft nul of meer methoden, maar ze moeten allemaal één argument van het type GEvent verwachten en terugkeertype void hebben. Bijvoorbeeld
    public interface KleurGewijzigdListener extends java.util.EventListener {
      void kleurGewijzigd(KleurGewijzigdEvent e);
    }
    
  3. De JavaBean die de gebeurtenis voortbrengt, moet een paar publieke methoden hebben waarmee geïnteresseerde GListeners hun interesse kunnen kenbaar maken en ongedaan maken:
      public void addGListener(GListener l) {
        // ...
      }
      public void removeGListener(GListener l) {
        // ...
      }
    
    In de meeste implementaties zullen deze methoden gewoon een dynamische lijst bijhouden met alle actieve luisteraars, bijvoorbeeld in de vorm van een private Vector:
      private Vector kleurListeners = new Vector();
      public void addKleurGewijzigdListener(KleurGewijzigdListener l) {
        kleurListeners.add(l);
      }
      public void removeKleurGewijzigdListener(KleurGewijzigdListener l) {
        kleurListeners.remove(l);
      }
    

Uitgewerkt voorbeeld

In het voorbeeld van paragraaf 8.3.2 moest de ontvanger van een PropertyChangeEvent nog de methode getTitel van de bron van de gebeurtenis oproepen omdat de titel geen deel uitmaakte van de gebeurtenis-informatie. Een PropertyChangeEvent bevat namelijk alleen informatie over de oude en nieuwe waarden van de gewijzigde eigenschap zelf, in dit geval de eigenschap waarde. We herschrijven dit voorbeeld nu aan de hand van het nieuwe gebeurtenistype KleurGewijzigdEvent dat hierboven reeds werd aangekondigd. De nieuwe versies van de oorsprong en afhandelaar van de gebeurtenis heten nu TekstEnGetal2 resp. KleurVlak2. We beginnnen met de definitie van het gebeurtenistype zelf.

/** Gebeurtenis die aangeeft dat een waarde getiteld "rood",
*   "groen" of "blauw" is gewijzigd. Deze gebeurtenissen
*   zijn ondermeer afkomstig van JavaBeans van het type
*   TekstEnGetal2.
*   @see TekstEnGetal2
*/
public class KleurGewijzigdEvent extends java.util.EventObject {

  /** Symbolische constante die aankondigt dat
  *   de rode kleurcomponent wijzigt.
  */
  public static final int ROOD = 100;

  /** Symbolische constante die aankondigt dat
  *   de groene kleurcomponent wijzigt.
  */
  public static final int GROEN = 200;

  /** Symbolische constante die aankondigt dat
  *   de blauwe kleurcomponent wijzigt.
  */
  public static final int BLAUW = 300;

  /** Symbolische constante die aankondigt dat
  *   de gewijzigde kleurcomponent niet bepaald
  *   kon worden.
  */
  public static final int ONBEPAALD = 400;

  /** De component waarvan de bijdrage wijzigt. */
  private int component = ONBEPAALD;

  /** De bijdrage van de kleurcomponent. */
  private int waarde = 0;

  /** Construeer een gebeurtenis die aangeeft dat
  *   een kleurcomponent wijzigt.
  *   @param bron
  *     De oorsprong van de gebeurtenis.
  *   @param component
  *     Symbolische constante die aangeeft welke
  *     van de drie hoofdkleuren (rood, groen, blauw)
  *     gewijzigd is. Moet de waarde KleurGewijzigdEvent.ROOD,
  *     KleurGewijzigdEvent.GROEN of KleurGewijzigdEvent.BLAUW
  *     aannemen.
  *   @param waarde
  *     Geheel getal tussen 0 en 255 dat de nieuwe specifieke
  *     intensiteit in de gegeven hoofdkleur aangeeft.
  */
  public KleurGewijzigdEvent(Object bron, int component, int waarde) {
    super(bron);
    if (component == ROOD || component == GROEN || component == BLAUW)
      this.component = component;
    else
      this.component = ONBEPAALD;
    if (waarde < 0)
      this.waarde = 0;
    else if (waarde > 255)
      this.waarde = 255;
    else
      this.waarde = waarde;
  }

  /** Geef aan welke van de drie hoofdkleuren wijzigt.
  *   @returns
  *     Een van de symbolische constanten KleurGewijzigdEvent.ROOD,
  *     KleurGewijzigdEvent.GROEN of KleurGewijzigdEvent.BLAUW.
  */
  public int getComponent() {
    return component;
  }

  /** Geef aan wat de nieuwe specifieke intensiteit in de gegeven
  *   hoofdkleur is.
  *   @returns
  *     De specifieke intensiteit, een geheel getal tussen 0
  *     (afwezig) en 255 (maximale intensiteit).
  */
  public int getWaarde() {
    return waarde;
  }
}

De belangrijkste wijziging in de klasse TekstEnGetal2 ten opzichte van haar voorganger TekstEnGetal ligt in het feit dat we nu zelf de Vector van luisteraars bijhouden in plaats van dit over te laten aan een PropertyChangeSupport. Dit uit zich vooral in de nieuwe private methode lanceerWijziging, die bij aanpassingen van de eigenschap "waarde" de nodige verwittigingen rondstuurt door over de componenten van de Vector te itereren.

import java.awt.*;
import javax.swing.*;
import java.util.Vector;
import java.util.Enumeration;

public class TekstEnGetal2 extends JPanel {
  private int hetGetal = 0;
  private String deTekst = "";
  private JLabel txtGetal = new JLabel(hetGetal + "");
  private JLabel txtTekst = new JLabel(deTekst);
  private Vector kleurListeners = new Vector();

  public TekstEnGetal2() {
    setLayout(new GridLayout(2, 1, 10, 10));
    add(txtTekst);
    add(txtGetal);
  }

  public int getWaarde() {
    return hetGetal;
  }
  public void setWaarde(int w) {
    Integer oudeWaarde = new Integer(hetGetal);
    hetGetal = w;
    if (getTitel().equalsIgnoreCase("rood"))
      lanceerWijziging(KleurGewijzigdEvent.ROOD);
    else if (getTitel().equalsIgnoreCase("groen"))
      lanceerWijziging(KleurGewijzigdEvent.GROEN);
    else if (getTitel().equalsIgnoreCase("blauw"))
      lanceerWijziging(KleurGewijzigdEvent.BLAUW);
    txtGetal.setText(hetGetal + "");
  }
  private void lanceerWijziging(int component) {
    Enumeration e = kleurListeners.elements();
    while (e.hasMoreElements()) {
      KleurGewijzigdListener k = (KleurGewijzigdListener) e.nextElement();
      k.kleurGewijzigd(new KleurGewijzigdEvent(this, component, getWaarde()));
    }
  }

  public String getTitel() {
    return deTekst;
  }
  public void setTitel(String t) {
    deTekst = t;
    txtTekst.setText(deTekst);
  }

  public void addKleurGewijzigdListener(KleurGewijzigdListener l) {
    kleurListeners.add(l);
  }
  public void removeKleurGewijzigdListener(KleurGewijzigdListener l) {
    kleurListeners.remove(l);
  }
}

De klasse KleurVlak2 wordt vooral een stuk eenvoudiger dan haar voorganger KleurVlak, omdat de detectie van het soort kleurcomponent nu reeds vóór het verzenden van de gebeurtenis plaatsgrijpt. De methode propertyChange wordt vervangen door een veel eenvoudiger kleurGewijzigd.

import java.awt.*;

public class KleurVlak2
  extends Canvas implements KleurGewijzigdListener {
  public KleurVlak2() {
    setSize(60, 40);
    setBackground(Color.red);
  }

  public void kleurGewijzigd(KleurGewijzigdEvent e) {
    Color c = getBackground();
    int r = c.getRed();
    int g = c.getGreen();
    int b = c.getBlue();
    switch (e.getComponent()) {
      case KleurGewijzigdEvent.ROOD:
        setBackground(new Color(e.getWaarde(), g, b));
        break;
      case KleurGewijzigdEvent.GROEN:
        setBackground(new Color(r, e.getWaarde(), b));
        break;
      case KleurGewijzigdEvent.BLAUW:
        setBackground(new Color(r, g, e.getWaarde()));
        break;
    }
  }
}

De samenwerking van beide Beans in de BeanBox verloopt uiterlijk op dezelfde manier als tevoren. Hier is nog een tafereeltje waarbij de kleur van de rechthoek wordt gedicteerd door drie afzonderlijke beans van het type TekstEnGetal2:

rood 128, groen 192, blauw 192 - Properties: 192, blauw

8.5 BeanInfo

In voorbereiding

8.6 Aanpassing

In voorbereiding

8.7 Persistentie

In voorbereiding

8.8 Bean context

In voorbereiding


inhoudstafel en auteursrecht
* STER *

Valid HTML 4.0! Valid CSS!