201 Copyright 1996-1998 by Axel T. Schreiner. All Rights Reserved. Ein Manager mit CardLayout layout/cards.java CardLayout dient vor allem zum Aufbau von Inspektoren: In der gleichen Fläche soll je nach Kontext eine andere Oberfläche erscheinen. [ Applet] Cards verwaltet beliebig viele allerdings gleichartige Komponenten, hier zum Beispiel TextField-Objekte als Demo. Cards ist ein Container für gleichartige, benannte Komponenten, die das Cards.Card-Interface implementieren und mit CardLayout nacheinander dargestellt und über Listener verwaltet werden. import java.applet.*; import java.awt.*; import java.awt.event.*; import java.util.*; {apps/layout/cards.java /** A class to manage Card objects with a CardLayout */ public class Cards extends Applet { /** An interface to be managed by Cards */ public interface Card extends ActionListener { // receives "add", "delete" from card s name Card newcard (); { In der Mitte der oberen Zeile kann ein Name eingegeben werden. Ist er unbekannt, wird dazu eine neue Komponente erzeugt; sie erfährt dies vom Namen mit add als actioncommand. Rechts oben ist ein Choice mit allen bekannten Namen. Dort oder durch Eingabe in der Mitte kann auf eine vorhandene Komponente positioniert werden; ein ComponentListener der Komponente wird durch componentshown() und componenthidden() informiert. Links oben ist ein Knopf, mit dem die aktuelle Komponente gelöscht werden kann; sie erfährt dies vom Namen mit delete als actioncommand. Eine Komponente muß allerdings immer übrigbleiben. Neue Komponenten werden von einer Komponente mit newcard() erzeugt, denn man Klassenmethoden nicht in einem Interface vorgeben.
202 Ein Beispiel Demo {apps/layout/cards.java /** A class to demonstrate the Cards */ protected static class Demo extends TextField implements Card { public Demo () { super(20); addcomponentlistener(new ComponentAdapter() { public void componentshown (ComponentEvent e) { selectall(); requestfocus(); ); public Card newcard () { return new Demo(); if (e.getactioncommand().equals("add")) settext(e.getsource().tostring()); /** runs a Demo, quits when window is closed */ public static void main (String args []) { Frame f = new Frame(); f.add("center", new Cards("first", new Demo())); f.addwindowlistener(new WindowAdapter() { public void windowclosing (WindowEvent w) { System.exit(0); ); f.pack(); f.show(); { actionperformed() erfolgt, wenn eine Card erzeugt oder gelöscht wird. Hier wird der Name als Inhalt übernommen, den der ActionEvent stammt vom Namen. componentshown() sorgt bei Auftauchen einer Demo-Card dafür, daß der Text sofort geändert werden kann. Man sollte mindestens vernünftig fokussieren.
203 Management {apps/layout/cards.java protected Hashtable db = new Hashtable(); // name -> card protected Panel cards; // contains CardLayout protected CardLayout layout; // used for cards protected Button delete; // to destroy a card protected TextField name; // to create/select a card protected Choice names; // to select a card /** builds controls and first card */ public Cards (String firstname, final Card firstcard) { setlayout(new BorderLayout()); Panel p; add("north", p = new Panel(new GridLayout(1,3))); p.add(delete = new Button("delete")); p.add(name = new TextField()); p.add(names = new Choice()); add("center", cards = new Panel(layout = new CardLayout())); addcard(firstname, firstcard); /* kludge: a Component ought to receive COMPONENT_SHOWN when it is first shown. since it does not, but cards receives COMPONENT_RESIZED, we fudge a single COMPONENT_SHOWN to firstcard. on Windows, the demo will thus focus into firstcard. on Linux/Motif Demo still focusses into name. */ cards.addcomponentlistener(new ComponentAdapter() { public void componentresized (ComponentEvent e) { ((Component)firstCard).dispatchEvent(new ComponentEvent(cards, ComponentEvent.COMPONENT_SHOWN)); cards.removecomponentlistener(this); ); { Theoretisch sollte jede Component beim ersten Erscheinen COMPONENT_SHOWN erhalten; mit den Techniken im Teil 3 stellt man fest, daß das nur für Frame gilt. Da der Frame den Event COMPONENT_RESIZED beim Start offfenbar sehr spät bekommt, wird er mit dispatchevent() in den nötigen Event für die erste Card umfunktioniert. Leider fokussiert das Beispiel nur unter Windows wie beabsichtigt, nämlich in die erste Card. Unter Linux/Motif wird der Fokus implizit danach nochmals an name vermittelt.
204 {apps/layout/cards.java delete.addactionlistener(new ActionListener() { deletecard(); ); name.addactionlistener(new ActionListener() { String n = name.gettext(); if (n.length() > 0) { Object c = db.get(n); if (c == null) addcard(n, ((Card)cards.getComponent(0)).newCard()); else showcard(n, (Card)c); ); names.additemlistener(new ItemListener() { public void itemstatechanged (ItemEvent e) { String n = names.getselecteditem(); showcard(n, (Card)db.get(n)); ); name.addfocuslistener(new FocusListener() { public void focusgained (FocusEvent e) { /* bug: after settext, Linux/Motif positions at end, Windows at begin */ name.setcaretposition(name.gettext().length()); public void focuslost (FocusEvent e) { name.settext(names.getselecteditem()); ); { Die Kommandozeile wird mit den nötigen Zuhörern verbunden, die über db, falls möglich, zu Namen die Card-Objekte bestimmen und dann an die nachfolgend beschriebenen Methoden übergeben. Eine neue Card wird aus der ersten mit newcard() gewonnen. Es ist sehr wichtig, daß der Name im TextField und im Choice übereinstimmen, deshalb muß der Fokus verfolgt werden. Da Windows nach settext() am Anfang, Linux/Motif aber am Ende des Texts positioniert, wird die Position mit setcaretposition() explizit kontrolliert. Leider produziert Linux/Motif dann einen Block-Cursor, Windows den üblichen Strich.
205 Die restlichen Funktionen bilden das Card-Management: {apps/layout/cards.java /** inserts new card and shows it */ protected synchronized void addcard (String name, Card card) { db.put(name, card); names.add(name); delete.setenabled(db.size() > 1); cards.add(name, (Component)card); card.actionperformed(new ActionEvent(name, ActionEvent.ACTION_PERFORMED, "add")); showcard(name, card); /** shows existing card */ public void showcard (String name) { showcard(name, (Card)db.get(name)); protected Card current; // current card protected synchronized void showcard (String name, Card card) { if (card!= current) { names.select(name); this.name.settext(name); layout.show(cards, name); // callout for Card, causes componentshown.. current = card; //..and componenthide /** deletes current and shows previous (or first) */ protected synchronized void deletecard () { if (db.size() > 1) { String name = names.getselecteditem(); // must agree with current int next = names.getselectedindex(); if (next > 0) --next; db.remove(name); names.remove(name); delete.setenabled(db.size() > 1); cards.remove((component)current); // bugged on Linux/Motif current.actionperformed(new ActionEvent(name, ActionEvent.ACTION_PERFORMED, "delete")); current = null; showcard(names.getitem(next)); { Bei Linux/Motif wird der Choice durch Entfernen eines Eintrags unter Umständen verkleinert; außerdem beobachtet man anschließend IllegalArgumentException von MChoicePeer zu Choice.