kramann.info
© Guido Kramann

Login: Passwort:










12.3 Behandlung von Ereignissen

Was ist das Ziel bei der hier verfolgten Art und Weise Ereignisse von JavaBeans zu verarbeiten?

  • Es soll möglichst einfach sein, für die JavaBean-Objekte, die durch Drag and Drop über die NetBeansIDE erstellt werden, Ereignisbehandlung vorzunehmen.
  • Dies verlangt nach einer vereinheitlichten Behandlung von Ereignissen.
  • In unserem Fall wird das JFrame-Objekt, in das die JavaBeans gezogen werden als Listener für JavaBean-Ereignisse implementiert und ein Mechanismus programmiert, durch den sich das JFrame-Objekt bei allen in es hereingezogenen JavaBeans als Listener registriert.
  • Das hat zur Folge, dass bei allen möglichen Ereignissen bei den JavaBeans immer ein und die selbe Listener-Methode in dem JFrame-Objekt aufgerufen wird.
  • Man muß dann nur in dieser Methode einfügen, wie die verschiedenen Ereignisse behandelt werden sollen und nicht noch andere Objekte und Methoden manipulieren.
  • Dies setzt die Komplexität bei der Behandlung von Ereignissen herab.
Drag & Drop einer selbst entwickelten JavaBean in ein Fenster, das von der Klasse JFrame erbt.

Bild 12.3-1: Drag & Drop einer selbst entwickelten JavaBean in ein Fenster, das von der Klasse JFrame erbt.

  • Durch den Drag & Drop-Prozeß wird automatisch in der Klasse Fenster Code ergänzt, mit dem in diesem Beispiel Objekte vom Typ ParametereingabeBean erzeugt werden und mit einem add(..) dem Fenster hinzugefügt werden.
  • Durch den Drag & Drop-Prozeß werden zwar Komponenten zusammengeführt, jedoch nicht mit Funktionalität versehen.
  • Dies muß von Hand im Nachhinein geschehen (tatsächlich geht es auch über die Oberfläche, ist aber kein Vorteil, da dann ständig ähnliche Klickfolgen wiederholt werden müssen).
  • Damit aber diese Arbeit der Ereignisbehandlung nicht zu aufwendig ist, wird im folgenden ein Konzept entwickelt, daß einige der notwendigen Handlungen automatisiert.
  • Eine dieser Automatisierungen ist die, daß sich das Fenster-Objekt bei allen in es hineingezogenen JavaBean-Objekten als Listener registriert.
  • Eine weitere besteht in der Implementierung einer Methode, die es erlaubt aus den dem Fenster-Objekt hinzugefügten Komponenten über die ID einer gewünschten Komponente auf deren Methoden zuzugreifen.
  • Man muß also die hinzugefügten Komponenten nicht noch einmal in Extra-Attributen spichern, um auf sie zugreifen zu können.

Generalisiertes Konzept der Ereignisbehandlung bei JavaBeans

Objektattribute von JavaBeans, deren Änderung bei anderen JavaBeans Methoden starten sollen, werden "Bound Properties" (gebundene Eigenschaften) genannt. Um bei deren Behandlung eine gewisse Vereinheitlichung zu erreichen, sollen solche "Bound Properties" ein "PropertyChangeEvent"-Objekt an alle Objekte schicken, die sich bei dem betreffenden JavaBean registriert haben.

  • Wie sieht das syntaktisch aus?
  • Wird beispielsweise Das Attribut "wert" in Objekten der Klasse ParametereingabeBean geändert, so wird das senden des "PropertyChangeEvent"-Objektes folgendermaßen gewährleistet:
    public void setWertIntern(String text)
    {
        ...
        firePropertyChange("wert",wert_alt,wert);
    }

Code 12.3-1: Abschicken eines "PropertyChangeEvent"-Objektes mit Hilfe der Methode firePropertyChange(...)

  • Durch Einfügen der Methode firePropertyChange(...) wird erreicht, dass jedesmal wenn setWertIntern(...) aufgerufen wird auch ein "PropertyChangeEvent"-Objektes an alle registrierten Listener gesendet wird.
  • Übergabeparameter der Methode firePropertyChange(...) sind der Name des Attributs, die alte Belegung des Attributs und dann die neue aktualisierte Belegung (-auch wieder eine Konvention bei JavaBeans).
  • Eine Klasse, die in der Lage sein soll als Listener für ein PropertyChangeEvent zu fungieren, muß die Schnittstelle PropertyChangeListener implementieren.
  • Dann muß bei dieser Listener-Klasse die Methode propertyChange(PropertyChangeEvent evt) implementiert werden, die jedesmal mit einem "PropertyChangeEvent"-Objekt aufgerufen wird, wenn ein Änderungsereignis eine "Bound Property" bei der Komponente auftritt, bei der Listener sich angemeldet hat.
public class Fenster extends JFrame implements PropertyChangeListener 
{
    public void propertyChange(PropertyChangeEvent evt) 
    {
        if(evt.getPropertyName().equals("wert"))
        {
            ...
        }
    }    
    ...
}

Code 12.3-2: Klasse Fenster, die von JFrame erbt und die Schnittstelle PropertyChangeListener implementiert.

  • Die notwendige Struktur der Listener-Klasse ist oben angedeutet.
  • Die if-Anweisung zeigt auf, wie über das von außen übergebene PropertyChangeEvent-Objekt evt erkannt werden kann, welche Art von Ereignis gerade auftritt.

Automatisierung der Registrierung des Listener bei Hinzufügen einer neuen JavaBean

  • Wie oben beschrieben soll sich das Fenster-Objekt, das auch Listener ist bei jedem ihm hinzugefügten JavaBean als Listener anmelden.
  • Es wäre praktisch, wenn dies automatisch geschieht, sobald eine neue Komponente mit .getContentPain().add(neuesElement) hinzugefügt wird.
  • Dies ist möglich durch Hinzufügen eines weiteren dazu geeigneten Listeners, nämlich eines Container-Listeners.
  • JFrame erweitert die Klasse Container und diese bietet die Möglichkeit an, in sie Component-Objekte hinzufügen zu können.
  • Component ist eine Basisklasse aller JPanel-Objekte und damit auch Basisklasse unserer Bean-Klasse.
  • Der beschriebene Listener reagiert also, sobald in das Container-Objekt ein Component-Objekt hinzugefügt wird.
  • Die Reaktion erfolgt über die Methode componentAdded(ContainerEvent e).
  • In diese Methode muß dann der Befehl eingetragen werden, der für die automatische Registrierung des Fenster-Objektes bei der hinzugefügten Komponente sorgt.
  • Die dies betreffenden Code-Teile der Fenster-Klasse folgen hier:
public class Fenster extends JFrame implements PropertyChangeListener 
{
    ...
    public Fenster()
    {
        ...
        //Container-Listener registrieren:
        getContentPane().addContainerListener(new ContainerLauscher(this));
    }
    /**Fenster registriert seinen PropertyChangeListener bei allen 
    ihm hinzugefügten Komponenten:*/
    public class ContainerLauscher implements ContainerListener
    {
        public Fenster fenster = null;
        public ContainerLauscher(Fenster fenster)
        {
            this.fenster = fenster;
        }
        public void componentAdded(ContainerEvent e)
        {
            Component komponente = e.getChild(); 
            komponente.addPropertyChangeListener(fenster);
            //außerdem automatische ID-Vergabe wie bei Textkapiteln:
            ((BasisBean)komponente).setID(
             fenster.getID()+"."+getContentPane().getComponents().length
                              );
            System.out.println("ID der hinzugefuegten Komponente:"+((BasisBean)komponente).getID());
        }
        public void componentRemoved(ContainerEvent e)
        {
            Component komponente = e.getChild(); 
            komponente.removePropertyChangeListener(fenster);
        }
    }    

Code 12.3-3: }

  • Man sieht an dem Code-Beispiel, dass immer wenn eine Komponente hinzugefügt wird, sich das Fenster bei ihr als Listener registriert.
  • Wenn eine Komponente entfernt wird, wird auch der Listener entfernt.
  • Ferner wurden die Java-Bean-Klasse und auch die Fenster-Klasse um die Möglichkeit erweitert, sie mit einer eindeutigen Kennung, einer ID zu versehen.
  • Dies bereitet das Vorhaben vor, die Methoden dieser Objekte durch Angabe dieser Kennung aufzurufen.
  • Die Extraktion eines dem Container hinzugefügten Objekts um dann dessen Methoden aufzurufen, wird mit Hilfe folgender Objekt-Methode in der Klasse Fenster realisiert:
    public Component getComponentByID(String ID)
    {
        for(Component komponente : getContentPane().getComponents() )
            if(((BasisBean)komponente).getID()!=null && ((BasisBean)komponente).getID().equals(ID))
                return komponente;
        return null;
    }

Code 12.3-4: Herausgabe eines im Container gespeicherten Objektes aufgrund einer übergebenen ID.

  • Man beachte die besondere Art der for-Schleife, bei der alle Elemente eines Arrays durchgegangen werden können.
  • Das Array liefert: getContentPane().getComponents()
  • Zugriff in der Schleife auf das aktuelle Element des Arrays über: komponente
  • Das folgende UML-Klassendiagramm zeigt im Überblick die oben erwähnte Vererbungsstruktur für die Fenster-Klasse und die ParametereingabeBean-Klasse.
Vererbungsstruktur der verwendeten Elemente.

Bild 12.3-2: Vererbungsstruktur der verwendeten Elemente.

Standalone-Projekt

Um Klarheit über das Zusammenspiel der einzelnen Elemente zu bekommen, sind im folgenden die Quelltxte eines Lauffähigen Projektes aufgelistet, die die oben beschriebenen Eigenschaften und Fähigkeiten umsetzen:

  • Als Funktionalität ist wieder das Aufsummieren der Zaheln zweier Textfelder in einem Dritten umgesetzt worden.
  • Jedoch nun unter Verwendung der Speziellen Bean-spezifischen Listener und Event-fire-Methoden und der besprochenen automatischen Listener-Registrierung.
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;
import java.beans.*;
public class Main
{
    public static void main(String[] args)
    {
        Fenster mF = new Fenster();
        mF.setLayout(new GridLayout(3,1));
        ParametereingabeBean meineParametereingabeBean1 = new ParametereingabeBean();
        ParametereingabeBean meineParametereingabeBean2 = new ParametereingabeBean();
        ParametereingabeBean meineParametereingabeBean3 = new ParametereingabeBean();
        mF.getContentPane().add(meineParametereingabeBean1); 
        mF.getContentPane().add(meineParametereingabeBean2);
        mF.getContentPane().add(meineParametereingabeBean3);
        mF.pack();
        mF.setSize(500,100);
        mF.setLocation(10,10);
        mF.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        mF.setVisible(true);
        meineParametereingabeBean1.setWert(1.0);
        meineParametereingabeBean1.setBezeichnung("Wert1:");
        meineParametereingabeBean2.setWert(2.0);
        meineParametereingabeBean2.setBezeichnung("Wert2:");
        meineParametereingabeBean3.setWert(3.0);
        meineParametereingabeBean3.setBezeichnung("Wert1+Wert2:");
    }
}

Code 12.3-5: Main.java

import java.awt.Color;
import java.beans.XMLDecoder;
import java.awt.*;
import javax.swing.*;
import java.io.Serializable;
/**Schnittstelle Serializable ist in Oberklasse Component bereits implementiert*/
public class BasisBean extends JPanel
{
    private String ID = null;    
    public void setID(String ID)
    {
        this.ID = ID;
    }
    public String getID()
    {
        return ID;
    }
}

Code 12.3-6: BasisBean.java

import java.awt.Color;
import java.beans.XMLDecoder;
import java.awt.*;
import javax.swing.*;
import java.io.Serializable;
/**Schnittstelle Serializable ist in Oberklasse Component bereits implementiert*/
public class ParametereingabeBean extends BasisBean
{
    private JLabel     aufkleber;
    private Textfeld textfeld;
    /**
    Für die einzustellenden Parameter müssen get- und set-Methoden bereitgestellt werden.
    */
    private double wert;
    private String bezeichnung;
    private int breite;
    private int hoehe;
    public void setWert(double wert) 
    {
        textfeld.setText(""+wert);
//        firePropertyChange("wert",this.wert,wert);
        this.wert=wert;
    }
    public double getWert() 
    {
        return this.wert;
    }
    public void setBezeichnung(String bezeichnung) 
    {
//hier nicht setName() verwenden, sonst wird von Panel-Objekt vergebener Name ungültig.
//        setName(bezeichnung); //Um aus Komponente die Bezeichnung extrahieren zu können.
        aufkleber.setText(bezeichnung);
        firePropertyChange("bezeichnung",this.bezeichnung,bezeichnung);
        this.bezeichnung=bezeichnung;
    }
    public String getBezeichnung() 
    {
        return this.bezeichnung;
    }
    public void setBreite(int breite)
    {
        Dimension dim = this.getSize();
        this.setSize(breite,(int)dim.getHeight());
        firePropertyChange("breite",this.breite,breite);
        this.breite = breite;
    }
    public int getBreite()
    {
        return this.breite;
    }
    public void setHoehe(int hoehe)
    {
        Dimension dim = this.getSize();
        this.setSize((int)dim.getWidth(),hoehe);
        firePropertyChange("hoehe",this.hoehe,hoehe);
        this.hoehe = hoehe;
    }
    public int getHoehe()
    {
        return this.hoehe;
    }
    public void setWertIntern(String text)
    {
        double x = wert;
        double wert_alt = wert;
        boolean ok = true;
        try
        {
            x = Double.parseDouble(text);
        }
        catch(Exception e)
        {
            ok = false;
        }
        if( ok==true )
            wert = x;
        firePropertyChange("wert",wert_alt,wert);
    }
    /**
    JavaBeans müssen einen leeren Kontruktor besitzen.
    */
    public ParametereingabeBean() 
    {
        wert        = 0.0;
        bezeichnung = "Zahl";        
        textfeld  = new Textfeld(""+wert);
        aufkleber = new JLabel(bezeichnung); 
        setLayout(new GridLayout(1,2));
        breite = 300;
        hoehe  = 30;
        setBounds(10,60,breite,hoehe);
        add(aufkleber);
        add(textfeld);
        setBackground( Color.BLUE );
        aufkleber.setForeground( Color.WHITE );
    } 
    public class Textfeld extends TextField
    {
        Textfeld(String text)
        {
            super(text);
            enableEvents (AWTEvent.ACTION_EVENT_MASK);             
            enableEvents (AWTEvent.TEXT_EVENT_MASK);             
            enableEvents (AWTEvent.FOCUS_EVENT_MASK);             
        }
        protected void processEvent(AWTEvent e) 
        { 
             setWertIntern(getText());
        } 
    }
}

Code 12.3-7: ParametereingabeBean.java

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;
import java.beans.*;
/**Schnittstelle Serializable ist in Oberklasse Component bereits implementiert*/
public class Fenster extends JFrame implements PropertyChangeListener 
{
    private String ID;    
    public void setID(String ID)
    {
        this.ID = ID;
    }
    public String getID()
    {
        return ID;
    }
    public Fenster()
    {
//        super("Fenster");
        setID("1");
        getContentPane().addContainerListener(new ContainerLauscher(this));
    }
    public Component getComponentByID(String ID)
    {
        for(Component komponente : getContentPane().getComponents() )
            if(((BasisBean)komponente).getID()!=null && ((BasisBean)komponente).getID().equals(ID))
                return komponente;
        return null;
    }
    public void propertyChange(PropertyChangeEvent evt) 
    {
        System.out.println("Ereignisname:"+evt.getPropertyName());
        if(evt.getPropertyName().equals("wert"))
        {
            try
            {
                 ((ParametereingabeBean)getComponentByID("1.3")).setWert(
                     ((ParametereingabeBean)getComponentByID("1.1")).getWert()+
                     ((ParametereingabeBean)getComponentByID("1.2")).getWert()
                 );
            }
            catch(Exception e)
            {
                 System.out.println("Fehler:"+e);
            }    
        }
    }    
    /**Fenster registriert seinen PropertyChangeListener bei allen 
    ihm hinzugefügten Komponenten:*/
    public class ContainerLauscher implements ContainerListener
    {
        public Fenster fenster = null;
        public ContainerLauscher(Fenster fenster)
        {
            this.fenster = fenster;
        }
        public void componentAdded(ContainerEvent e)
        {
            Component komponente = e.getChild(); 
            komponente.addPropertyChangeListener(fenster);
            //außerdem automatische ID-Vergabe wie bei Textkapiteln:
            ((BasisBean)komponente).setID(
             fenster.getID()+"."+getContentPane().getComponents().length
                              );
            System.out.println("ID der hinzugefuegten Komponente:"+((BasisBean)komponente).getID());
        }
        public void componentRemoved(ContainerEvent e)
        {
            Component komponente = e.getChild(); 
            komponente.removePropertyChangeListener(fenster);
        }
    }    
}

Code 12.3-8: Fenster.java

Obiges Projekt gepackt.
Übung
  • Überführen Sie obiges Projekt in die NetBeansIDE-Entwicklungsumgebung.
  • Das Projekt dort soll aber demonstrieren, wie über Drag & Drop Komponenten dem JFrame-Objekt hinzugefügt werden können und auf einfache Weise dann im Anschluß Funktionalität ergänzt werden kann.
  • Dies bedeutet, dass der Inhalt der Methode propertyChange(PropertyChangeEvent evt) der Klasse Fenster erst nach dem Drag & Drop-Vorgang hinzugefügt wird.
Hinweise zum Vorgehen
  • Orientieren Sie sich an der Art und Weise, wie ein Projekt im vorangegangenen Kapitel angelegt wurde.
  • Dem Kompiler sind BasisBean und ParametereingabeBean erst nach dem Drag & Drop-Vorgang bekannt. - Nicht wundern, wenn sich Fenster vorher nicht fehlerfrei kompilieren läßt.
  • Legen Sie wie zuvor als erstes ein JFrame-Form mit dem Namen Fenster an.
  • Ergänzen Sie dann im Quellcode die fehlenden Methoden aus Fenster.java aus obigem Projekt und insbesondere im Kopf der Klasse "implements PropertyChangeListener".
  • Kontrollieren Sie die Funktionstüchtigkeit Ihres Programms, indem Sie es von der Konsole aus starten und die Ausgaben auf der Konsole beurteilen.
Übung
  • Setzen Sie obige Übung auch für eine "Schieberegler-Variante" um.