Kapitel 2 Videre med Swing

Du læser en gammel bog
Den bog, du læser her, er fra 1998, og mange ting kan have ændret sig siden da.
Vi håber, at du stadig kan finde relevant information i den.
Hvis du vil læse aktuelle oplysninger om de avancerede dele af Java, anbefaler vi
bogen Core Java – Advanced Features

Forrige kapitel beskrev de grundlæggende aspekter i forbindelse med Swing. I dette kapitel vil denne gennemgang fortsætte med de mere avancerede komponenter.

 

Menuer

 

En central del af enhver grafisk applikation er menuen. Her har brugeren mulighed for at vælge blandt de funktioner, programmet stiller til rådighed, og vedkommende har også mulighed for at se hvilke genvejstaster, der er knyttet til de forskellige menupunkter.

 

Menuer i Swing minder i høj grad om de menuer, der kendes fra AWT. Hvis man er kendt med opbygningen af disse, vil man hurtigt kunne sætte sig ind i de nye muligheder, Swing giver.

 

Swing indeholder en række klasser, der hver repræsenterer en del af menuen.

 

Klassen JMenuItem repræsenterer et punkt i en menu.

 

public class JMenuItem extends java.awt.swing.AbstractButton implements java.awt.accessibility.Accessible, java.awt.swing.MenuElement

{

public java.awt.swing.JMenuItem();

public java.awt.swing.JMenuItem(java.awt.swing.Icon);

public java.awt.swing.JMenuItem(java.lang.String);

public java.awt.swing.JMenuItem(java.lang.String,int);

public java.awt.swing.JMenuItem(java.lang.String,java.awt.swing.Icon);

 

public java.awt.swing.KeyStroke getAccelerator();

public java.awt.Component getComponent();

public java.awt.swing.MenuElement[] getSubElements();

public boolean isArmed();

public void menuSelectionChanged(boolean);

public void processKeyEvent(java.awt.event.KeyEvent, java.awt.swing.MenuElement[], java.awt.swing.MenuSelectionManager);

public void processMouseEvent(java.awt.event.MouseEvent, java.awt.swing.MenuElement[], java.awt.swing.MenuSelectionManager);

 

 

public void setAccelerator(java.awt.swing.KeyStroke);

public void setArmed(boolean);

public void setEnabled(boolean);

}

 

Som det ses af metoderne getAccelerator og setAccelerator, kan man i Swing koble en genvejstast til et menupunkt. Genvejstaster vil blive beskrevet senere i dette kapitel. Funktionen getArmed angiver, om der er ved at blive klikket på menupunktet. Hvis musen er blevet trykket ned over menupunktet og endnu ikke sluppet, vil getArmed returnere sandt. Hvis musen bliver sluppet over menupunktet, vil den handling, der er tilknyttet punktet, blive udført. Hvis musen bliver sluppet et andet sted, sker der intet, og setArmed vil atter returnere falsk.

 

Klassen JCheckBoxMenuItem nedarver fra JMenuItem. JCheckBoxMenuItem angiver et menupunkt, der enten kan være markeret eller ikke markeret. Ligesom den tilsvarende komponent JCheckBox er JCheckBoxMenuItem uafhængig af andre menupunkter.

 

public class JCheckBoxMenuItem extends java.awt.swing.JMenuItem implements java.awt.swing.SwingConstants, java.awt.accessibility.Accessible

{

public java.awt.swing.JCheckBoxMenuItem(java.lang.String,java.awt.swing.Icon,boolean);

public java.awt.swing.JCheckBoxMenuItem(java.lang.String,boolean);

 

public synchronized java.lang.Object[] getSelectedObjects();

public boolean getState();

public synchronized void setState(boolean);

}

 

Allerede i constructoren kan man angive, om menupunktet skal være markeret. Senere kan man bruge metoderne getState og setState til henholdsvis at se og ændre menupunktets tilstand. Metoden getSelectedObjects er speciel. Den returnerer et array med længden 1, hvis menupunktet er markeret. Indholdet af array’et er navnet på den tekst, der står ved menupunktet. Hvis menupunktet ikke er markeret, returneres null.

 

Klassen JRadioButtonMenuItem nedarver også fra JMenuItem. Klassen afspejler komponentet JRadioButton, der som bekendt er en knap i en gruppe, hvoraf kun én kan være markeret. Klassen har samme signatur som JMenuItem – dog er enkelte af metoderne redefineret. Til at angive hvilke objekter, der hører sammen, bruger man klassen ButtonGroup. Denne bruges på præcis samme måde som ved almindelige radioknapper (alternativknapper). Anvendelsen af ButtonGroup-klassen blev gennemgået i kapitel 1.

 

De forskellige menupunkter skal naturligvis samles i en menu. Det kan man gøre ved hjælp af klassen JMenu.

 

public class JMenu extends java.awt.swing.JMenuItem implements java.awt.accessibility.Accessible,

 

java.awt.swing.MenuElement

{

public java.awt.swing.JMenu();

public java.awt.swing.JMenu(java.lang.String);

public java.awt.swing.JMenu(java.lang.String,boolean);

 

public java.awt.Component add(java.awt.Component);

public java.awt.swing.JMenuItem add(java.awt.swing.Action);

public java.awt.swing.JMenuItem add(java.awt.swing.JMenuItem);

public void add(java.lang.String);

public void addMenuListener(java.awt.swing.event.MenuListener);

public void addSeparator();

public int getDelay();

public java.awt.swing.JMenuItem getItem(int);

public int getItemCount();

public java.awt.Component getMenuComponent(int);

public int getMenuComponentCount();

public java.awt.Component[] getMenuComponents();

public java.awt.swing.JPopupMenu getPopupMenu();

public java.awt.swing.MenuElement[] getSubElements();

public java.awt.swing.JMenuItem insert(java.awt.swing.Action, int);

public java.awt.swing.JMenuItem insert(java.awt.swing.JMenuItem, int);

public void insert(java.lang.String, int);

public void insertSeparator(int);

protected void processKeyEvent(java.awt.event.KeyEvent);

public void processKeyEvent(java.awt.event.KeyEvent, java.awt.swing.MenuElement[], java.awt.swing.MenuSelectionManager);

public void processMouseEvent(java.awt.event.MouseEvent, java.awt.swing.MenuElement[], java.awt.swing.MenuSelectionManager);

public void remove(int);

public void remove(java.awt.swing.JMenuItem);

public void removeAll();

public void removeMenuListener(java.awt.swing.event.MenuListener);

public void setAccelerator(java.awt.swing.KeyStroke);

public void setDelay(int);

public void setMenuLocation(int, int);

public void setPopupMenuVisible(boolean);

public void setSelected(boolean);

}

 

Klassen indeholder naturligvis en add-metode, der tilføjer et JMenuItem-objekt til menuen. Derudover er der mulighed for at tilføje komponenter, tekststrenge og også Action-objekter. Disse vil blive beskrevet senere. Fælles for alle add-metoderne er, at de tilføjer elementerne i slutningen af menuen.

 

Hvis man vil indsætte et menupunkt på en bestemt position, skal man bruge insert-metoden.

 

For at gøre menuen mere overskuelig kan man vælge at opdele den ved hjælp af seperatorer. Disse kan tilføjes ved hjælp af addSeperator og indsættes med insertSeperator-metoden.

 

Den tid, der går, fra brugeren klikker på menuens overskrift, til menuen vises, kan ændres med setDelay-metoden. Parametret til denne funktion er længden af forsinkelsen i milisekunder. Hvis man vil vide, hvor stor forsinkelsen er, kan man bruge getDelay-funktionen.

 

JMenu-klassen indeholder en række funktioner, der kan returnere information om menuen. Det drejer sig blandt andet om antallet af menupunkter inklusiv adskillere og antallet af komponenter i menuen. Klassen indeholder naturligvis også funktioner til at opnå referencer til de enkelte menupunkter.

 

Når man opretter JMenu-objektet, kan man vælge, om menuen skal kunne “rives af” menulinien og placeres flydende på skærmen. Det gør man ved at angive true i kaldet til constructoren. Man kan herefter selv angive menuens position ved at kalde metoden setMenuLocation.

 

Ligesom de øvrige elementer i en menu har JMenu-objekter mulighed for at få en genvejstast tilknyttet. Disse gennemgås senere.

 

Hvis man ønsker en undermenu i sin menu, kan det også lade sig gøre. I så fald skal det være en popup-menu, som i Swing repræsenteres af klassen JPopupMenu. Popup-menuer kan også bruges som lokalmenuer, der åbnes, når brugeren eksempelvis højre-klikker på en komponent i brugergrænsefladen. JPopupMenu-klassen minder på mange punkter om JMenu, og som det ses af nedenstående signatur, er der også en del sammenfald mellem klassernes metoder.

 

public class JPopupMenu extends java.awt.swing.JComponent implements java.awt.accessibility.Accessible, java.awt.swing.MenuElement

{

public java.awt.swing.JPopupMenu();

public java.awt.swing.JPopupMenu(java.lang.String);

 

public java.awt.Component add(java.awt.Component);

public java.awt.swing.JMenuItem add(java.awt.swing.Action);

public java.awt.swing.JMenuItem add(java.awt.swing.JMenuItem);

public void addPopupMenuListener(java.awt.swing.event.PopupMenuListener);

public void addSeparator();

public java.awt.Component getComponent();

public java.awt.Component getComponentAtIndex(int);

public int getComponentIndex(java.awt.Component);

public java.lang.String getLabel();

public java.awt.Insets getMargin();

public java.awt.swing.MenuElement[] getSubElements();

public void insert(java.awt.Component, int);

 

public void insert(java.awt.swing.Action, int);

public boolean isBorderPainted();

public boolean isLightWeightPopupEnabled();

public void menuSelectionChanged(boolean);

public void processKeyEvent(java.awt.event.KeyEvent, java.awt.swing.MenuElement[], java.awt.swing.MenuSelectionManager);

public void processMouseEvent(java.awt.event.MouseEvent, java.awt.swing.MenuElement[], java.awt.swing.MenuSelectionManager);

public void removePopupMenuListener(java.awt.swing.event.PopupMenuListener);

public void setBorderPainted(boolean);

public void setLabel(java.lang.String);

public void setLightWeightPopupEnabled(boolean);

public void setLocation(int, int);

public void setPopupSize(int, int);

public void setPopupSize(java.awt.Dimension);

}

 

En forskel er det dog, at JPopupMenu har et marginbegreb. I denne forbindelse er en margin det antal pixels, der er mellem popup-menuens ramme og dens indhold. Margin manipuleres af metoderne setMargin og getMargin. Derudover skal man naturligvis lægge mærke til, at event-håndtering i en popup-menu sker ved hjælp af en PopupMenuListener, mens det i en almindelig menu sker ved hjælp af en MenuListener.

 

Når alle menuerne er oprettet, kan de samles på en menulinie. Menulinier repræsenteres i Swing af klassen JMenuBar.

 

public class JMenuBar extends java.awt.swing.JComponent implements java.awt.accessibility.Accessible, java.awt.swing.MenuElement

{

public java.awt.swing.JMenuBar();

 

public java.awt.swing.JMenu add(java.awt.swing.JMenu);

public java.awt.Component getComponent();

public java.awt.Component getComponentAtIndex(int);

public int getComponentIndex(java.awt.Component);

public java.awt.swing.JMenu getHelpMenu();

public java.awt.Insets getMargin();

public java.awt.swing.JMenu getMenu(int);

public int getMenuCount();

public void menuSelectionChanged(boolean);

public void processKeyEvent(java.awt.event.KeyEvent, java.awt.swing.MenuElement[], java.awt.swing.MenuSelectionManager);

public void processMouseEvent(java.awt.event.MouseEvent, java.awt.swing.MenuElement[], java.awt.swing.MenuSelectionManager);

public void remove(int);

public void setHelpMenu(java.awt.swing.JMenu);

 

public void setMargin(java.awt.Insets);

}

 

Det essentielle i klassen er naturligvis add-metoden, der tilføjer en menu til menulinien. Derudover skal man lægge mærke til metoderne setHelpMenu og getHelpMenu, der kan bruges til at håndtere den specielle hjælpemenu.

 

Nedenstående eksempel viser, hvordan man kan oprette et vindue med en menulinie og en række forskellige menuer.

 

import java.awt.swing.*;

import java.awt.event.*;

 

public class SimpleMenu extends JFrame

{

private JMenuBar menulinie;

private JCheckBoxMenuItem check1, check2, check3;

private JRadioButtonMenuItem radio1, radio2, radio3;

private ButtonGroup radioGruppe;

private JMenuItem item1, item2, item3;

private JMenu checkMenu, radioMenu, itemMenu;

private JTextArea tekst;

private ActionListener listener;

 

public SimpleMenu()

{

super (“Simpel menu”);

 

// Tilføj et tekstfelt

 

tekst = new JTextArea();

getContentPane().add(tekst);

 

// Opret action listener

 

listener = new Listener (tekst);

 

// Opret menulinien

 

menulinie = new JMenuBar();

 

// Opret check-menuen

 

check1 = new JCheckBoxMenuItem (“Mulighed 1”, true);

check2 = new JCheckBoxMenuItem (“Mulighed 2”);

check3 = new JCheckBoxMenuItem (“Mulighed 3”);

 

check1.setActionCommand (“Checkbox nr. 1 ændret”);

check2.setActionCommand (“Checkbox nr. 2 ændret”);

check3.setActionCommand (“Checkbox nr. 3 ændret”);

 

check1.addActionListener (listener);

check2.addActionListener (listener);

check3.addActionListener (listener);

 

checkMenu = new JMenu(“Checkbox-menu”);

 

 

checkMenu.add(check1);

checkMenu.add(check2);

checkMenu.add(check3);

 

menulinie.add (checkMenu);

 

// Opret radio-menuen

 

radio1 = new JRadioButtonMenuItem (“Mulighed 1”);

radio2 = new JRadioButtonMenuItem (“Mulighed 2”);

radio3 = new JRadioButtonMenuItem (“Mulighed 3”);

 

radio1.setActionCommand (“Alternativknap nr. 1 valgt”);

radio2.setActionCommand (“Alternativknap nr. 2 valgt”);

radio3.setActionCommand (“Alternativknap nr. 3 valgt”);

 

radio1.addActionListener (listener);

radio2.addActionListener (listener);

radio3.addActionListener (listener);

 

radioGruppe = new ButtonGroup();

radioGruppe.add(radio1);

radioGruppe.add(radio2);

radioGruppe.add(radio3);

 

radioMenu = new JMenu(“Alternativknap-menu”);

radioMenu.add(radio1);

radioMenu.add(radio2);

radioMenu.add(radio3);

 

menulinie.add(radioMenu);

 

// Opret en almindelig menu

 

item1 = new JMenuItem (“Mulighed 1”);

item2 = new JMenuItem (“Mulighed 2”);

item3 = new JMenuItem (“Mulighed 3”);

 

item1.setActionCommand (“Mulighed 1 valgt”);

item2.setActionCommand (“Mulighed 2 valgt”);

item3.setActionCommand (“Mulighed 3 valgt”);

 

item1.addActionListener (listener);

item2.addActionListener (listener);

item3.addActionListener (listener);

 

itemMenu = new JMenu(“Almindelig menu”);

itemMenu.add (item1);

itemMenu.add (item2);

itemMenu.add (item3);

 

 

menulinie.add(itemMenu);

 

// Tilføj menuen til vinduet

 

setJMenuBar(menulinie);

 

// Tilpas størrelsen

 

setSize (400,200);

}

 

public static final void main (String args[])

{

SimpleMenu me = new SimpleMenu();

me.show();

}

}

 

Programmet opretter en række menupunkter. Til hvert af disse tilknyttes en action command, som er en tekst, der bliver vist, når brugeren klikker på det pågældende menupunkt. Til at håndtere events bruges en instans af klassen Listener:

 

class Listener implements ActionListener

{

private JTextArea status;

 

public Listener (JTextArea status)

{

this.status = status;

}

 

public void actionPerformed (ActionEvent e)

{

status.append (e.getActionCommand() + “\n”);

}

}

 

En kørsel af programmet kan give et billede som det i figur 2.1.

 

 

Fig. 2-1 – Et vindue med en menulinie.

 

 

 

 


Genvejstaster

 

Som nævnt tidligere kan man bruge genvejstaster i menuer i Swing. Dette kan gøres på to måder. Man kan bruge de almindelige genvejstaster, der også kan bruges på knapper. Det vil sige, at når først menuen er åben, behøver man kun at trykke på en tast for at aktivere et menupunkt. Denne form for genvejstaster sættes ved hjælp setMnemonic-metoden, og nedenstående kodestump viser, hvordan de kan tilføjes det foregående eksempel.

 

// Tilføj mnemonics

 

check1.setMnemonic (‘1’);

check2.setMnemonic (‘2’);

check3.setMnemonic (‘3’);

checkMenu.setMnemonic (‘C’);

 

radio1.setMnemonic (‘1’);

radio2.setMnemonic (‘2’);

radio3.setMnemonic (‘3’);

radioMenu.setMnemonic (‘R’);

 

item1.setMnemonic (‘1’);

item2.setMnemonic (‘2’);

item3.setMnemonic (‘3’);

itemMenu.setMnemonic (‘A’);

 

Så får man en menu, der ligner den i figur 2.2.

 

 

Fig. 2.2 – En menu med mnemonics.

 

Der findes imidlertid også en anden form for genvejstaster. Det er en tastekombination, man kan udføre når som helst for at få en given funktion udført. For eksempel vil tastekombinationen alt+F4 i Windows lukke det aktuelle program og alle åbne vinduer i programmet.

 

Disse tastekombinationer repræsenteres i Java af klassen KeyStroke.

 

public class KeyStroke extends java.lang.Object implements java.io.Serializable

{

public static java.awt.swing.KeyStroke getKeyStroke(char);

public static java.awt.swing.KeyStroke getKeyStroke(char, boolean);

 

public static java.awt.swing.KeyStroke getKeyStroke(int, int);

public static java.awt.swing.KeyStroke getKeyStroke(int, int, boolean);

public static java.awt.swing.KeyStroke getKeyStroke(java.lang.String);

public static java.awt.swing.KeyStroke getKeyStrokeForEvent(java.awt.event.KeyEvent);

public int getModifiers();

public boolean isOnKeyRelease();

}

 

Man opretter et KeyStroke-objekt ved at benytte en af de statiske getKeyStroke-funktioner. Disse findes i fem forskellige udgaver. Den første tager en char som parameter – denne char angiver den ene tast, tastaturkombinationen består af. Den næste udgave af getKeyStroke tager en char og en boolean som parametre. char-værdien angiver igen den tast, tastekombinationen består af, mens boolean-værdien angiver, om tastaturkombinationen er fuldført, når tasten slippes eller når den trykkes ned. Hvis true er kombinationen først aktiv, når tasten slippes.

 

Den næste getKeyStroke-funktion har to int-værdier som parametre. Den første værdi angiver koden for den tast, der skal trykkes ned, mens den anden angiver modifiers. Modifiers er de taster, der skal holdes nede samtidigt med tasten. Der findes følgende modifiers:

 

  • java.awt.Event.SHIFT_MASK (1)
  • java.awt.Event.CTRL_MASK (2)
  • java.awt.Event.META_MASK (4)
  • java.awt.Event.ALT_MASK (8)

 

Flere modifiers kan kombineres ved at lægge dem sammen eller ved at foretage en logisk eller-operation.

 

Der findes også en getKeyStroke-funktion, der tager to int-værdier og en boolean-værdi. Her angiver boolean-værdien igen, hvornår tastekombinationen skal aktivere.

 

Til at angive tasten kan man bruge de konstanter, der findes i klassen java.awt.event.KeyEvent. Denne klasse indeholder konstanter for samtlige taster.

 

I selve programmet tilføjer man genvejstaster ved at benytte setAccelerator-metoden på JMenuItem-objekterne. Nedenstående programstump viser, hvordan man kan tilføje tastekombinationer til menu-eksemplet.

 

// Tilføj tastekombinationer

 

check1.setAccelerator (KeyStroke.getKeyStroke(KeyEvent.VK_1, Event.ALT_MASK));

check2.setAccelerator (KeyStroke.getKeyStroke(KeyEvent.VK_2, Event.ALT_MASK));

check3.setAccelerator (KeyStroke.getKeyStroke(KeyEvent.VK_3, Event.ALT_MASK));

 

radio1.setAccelerator (KeyStroke.getKeyStroke(KeyEvent.VK_1, Event.ALT_MASK | Event.CTRL_MASK));

 

radio2.setAccelerator (KeyStroke.getKeyStroke(KeyEvent.VK_2, Event.ALT_MASK | Event.CTRL_MASK));

radio3.setAccelerator (KeyStroke.getKeyStroke(KeyEvent.VK_3, Event.ALT_MASK | Event.CTRL_MASK));

 

item1.setAccelerator (KeyStroke.getKeyStroke(KeyEvent.VK_1, Event.CTRL_MASK));

item2.setAccelerator (KeyStroke.getKeyStroke(KeyEvent.VK_2, Event.CTRL_MASK));

item3.setAccelerator (KeyStroke.getKeyStroke(KeyEvent.VK_3, Event.CTRL_MASK));

 

Genvejstasterne bliver automatisk vist på menuerne. Det betyder, at man nu har menuer, der ligner dem i figur 2.3.

 

 

Fig. 2.3 – Menuer med genvejstaster.

 

 

 

 

 

Action

 

Det sker ofte, at den samme funktion er repræsenteret flere gange i brugergrænsefladen. Det kan for eksempel være i en menu, på en værktøjslinie og i en lokalmenu. Hvis der sker en ændring i funktionens tilstand – for eksempel at den ikke længere kan aktiveres – skal man ændre dette alle steder, funktionen optræder i brugergrænsefladen. Dette er uhensigtsmæssigt, og derfor findes i Swing interfacet Action. Dette interface repræsenterer en funktionalitet. Det vil sige, at interfacet indeholder informationer om funktionen og også den kode, der skal udføres, når funktionen bliver valgt.

 

Action-objekter kan tilføjes direkte til de fleste elementer i Swing, og elementerne vil selv finde den rigtige repræsentation. For eksempel vil en menu repræsentere et Action­-objekt ved hjælp af en tekststreng, mens en værktøjslinie vil vælge at vise funktionen ved dens ikon.

 

Action-interfacet har følgende signatur:

 

public abstract interface Action extends java.lang.Object implements java.awt.event.ActionListener

{

 

public abstract java.lang.Object getValue(java.lang.String);

public abstract boolean isEnabled();

public abstract void putValue(java.lang.String, java.lang.Object);

public abstract void setEnabled(boolean);

}

 

Interfacet er abstrakt, fordi det ikke implementerer actionPerformed fra ActionListener.

 

Action-interfacet kan ikke kun bruges i brugergrænsefladen. Det kan bruges mange af de steder, hvor man har brug for at repræsentere en funktionalitet abstrakt.

 

Til at lette arbejdet med actions findes klassen AbstractAction. Denne klasse implementerer de dele af Action-interfacet, der fremgår af ovenstående signatur. Det vil sige, at klassen indeholder metoder til at ændre og læse et Action-objekts værdier.

 

Hvis man vil bruge et Action-objekt, behøver man blot at nedarve fra klassen AbstractAction og implementere metoden actionPerfomed. Nedenstående eksempel viser, hvordan man kan tilføje et Action-objekt til en menu.

 

import com.sun.java.swing.*;

import java.awt.event.*;

 

public class ActionMenu extends JFrame

{

public ActionMenu()

{

super (“Actionmenu”);

JMenuBar menulinie = new JMenuBar();

 

// Opret action-objektet i to menuer

 

JMenu menu1 = new JMenu(“Menu1”);

JMenu menu2 = new JMenu(“Menu2”);

 

Action a = new TestAction(“Action!”);

menu1.add (a);

menu2.add (a);

 

menulinie.add (menu1);

menulinie.add (menu2);

 

// Opret en menu med indstillinger

 

JMenu indstillinger = new JMenu (“Indstillinger”);

JMenuItem aktiver = new JMenuItem (“Aktiver Action-objekt”);

JMenuItem deaktiver = new JMenuItem (“Deaktiver Action-objekt”);

 

aktiver.setActionCommand (“ak”);

 

deaktiver.setActionCommand (“deak”);

 

MenuActionListener listener = new MenuActionListener(a);

aktiver.addActionListener (listener);

deaktiver.addActionListener (listener);

 

indstillinger.add (aktiver);

indstillinger.add (deaktiver);

 

menulinie.add (indstillinger);

 

// Tilføj menulinie og tilpas vindue

 

setJMenuBar (menulinie);

setSize (400,100);

}

 

public static final void main (String args[])

{

ActionMenu me = new ActionMenu();

me.show();

}

}

 

Eksemplet opretter to menuer, der hver indeholder det samme Action-objekt. Programmet opretter også en menu, hvorfra man kan aktivere eller deaktivere Action-objektet.  Selve Action-objektet er en instans af klassen TestAction. Den er implementeret således:

 

class TestAction extends AbstractAction

{

public TestAction (String text)

{

super (text);

}

 

public void actionPerformed(ActionEvent e)

{

JOptionPane.showMessageDialog(null, “Funktionen er udført”, “Action”, JOptionPane.PLAIN_MESSAGE);

}

}

 

I dette eksempel bruges Action-objektet kun på en menu. Derfor er det kun nødvendigt at overloade den constructor, der tager en tekst som parameter. Klassen AbstractAction indeholder også constructors, der accepterer ikoner som parametre – disse bruges, hvis man tilføjer objektet til et knappanel.

 

Programmet gør også brug af eventhåndtering til at bestemme, om action-menupunktet skal være aktivt eller ej. Dette foregår i klassen MenuActionListener:

 

class MenuActionListener implements ActionListener

 

{

private Action a;

 

public MenuActionListener (Action a)

{

this.a = a;

}

 

public void actionPerformed (ActionEvent e)

{

if (e.getActionCommand().equals (“ak”))

{

a.setEnabled(true);

}

else

if (e.getActionCommand().equals (“deak”))

{

a.setEnabled(false);

}

}

}

 

Som det ses, er det ikke Action-punktet i begge menuer, der bliver aktiveret eller deaktiveret. Man behøver blot at aktivere eller deaktivere Action-objektet  – så bliver ændringen automatisk afspejlet overalt i brugergrænsefladen.

 

Indikatorer

 

Hvis man skriver et program, der skal arbejde i lang tid uden input fra brugeren, er det godt at bruge en indikator til at vise, hvor langt programmet er kommet med opgaven. Indikatorer har to formål: dels viser de, at programmet ikke er gået ned, men bare udfører en eller anden kompliceret opgave, og dels fortæller de brugeren, om der er tid til at hente en kop kaffe, inden programmet er færdigt.

 

I Swing bruger man klassen com.sun.java.swing.JProgressBar til at repræsentere en indikator. Denne klase har følgende signatur:

 

public class JProgressBar extends com.sun.java.swing.JComponent implements com.sun.java.swing.SwingConstants, com.sun.java.accessibility.Accessible

{

public com.sun.java.swing.JProgressBar();

public com.sun.java.swing.JProgressBar(int);

public com.sun.java.swing.JProgressBar(int,int);

public com.sun.java.swing.JProgressBar(int,int,int);

 

public int getMaximum();

public int getMinimum();

public int getOrientation();

public double getPercentComplete();

 

public java.lang.String getString();

public com.sun.java.swing.plaf.ProgressBarUI getUI();

public java.lang.String getUIClassID();

public int getValue();

public void setMaximum(int);

public void setMinimum(int);

public void setOrientation(int);

public void setString(java.lang.String);

public void setValue(int);

}

 

Klassen har tre constructors, der tager et variabelt antal integers som parametre. Angiver man kun en parameter, bestemmer denne, om indikatoren skal være lodret eller vandret. Det gøres ved hjælp af konstanterne JProgressBar.VERTICAL og JProgressBar.HORIZONTAL. Angiver man to parametre, er den første retningen, mens den anden angiver den maksimale værdi. Angiver man tre parametre, er den første retningen, den anden minimumsværdien og den tredje maksimumværdien.

 

Formålet med at angive minimums- og maksimumværdier er, at man kan overlade det til JProgressBar-klassen at beregne hvor stor en del af opgaven, der er fuldført. Man kan endda – ved hjælp af funktionen getPercentComplete – få at vide, hvor stor en procentdel af opgaven, der er udført. Der findes to måder, at opdatere en indikator på. Enten kan den opgave, indikatoren afspejler, opdatere indikatoren hver gang, der sker fremskridt, eller også kan man tilføje en timer, der med et givent interval spørger opgaven, hvor langt den er kommet, og herefter opdaterer indikatoren.

 

Nedenstående eksempel viser, hvordan man kan lave en indikator, hvor opgaven automatisk opdaterer den. Opgaven er den simple at tælle fra nul til en million.

 

import com.sun.java.swing.*;

import java.awt.event.*;

import java.awt.*;

 

public class MillionIndicator extends JFrame

{

public MillionIndicator()

{

// Tilpas vinduet

 

super (“Hvem kan hurtigst tælle til en million?”);

 

getContentPane().setLayout(new GridLayout(4,1));

setSize (300,400);

 

// Opret variantknapper til at vælge lodret/vandret

 

JRadioButton vandret = new JRadioButton (“Vandret”, true);

JRadioButton lodret = new JRadioButton (“Lodret”);

 

 

ButtonGroup varianter = new ButtonGroup();

varianter.add (vandret);

varianter.add (lodret);

 

getContentPane().add(vandret);

getContentPane().add(lodret);

 

// Opret indikatoren

 

JProgressBar indikator = new JProgressBar (JProgressBar.HORIZONTAL, 0, 1000000);

 

getContentPane().add(indikator);

 

// Opret startknap

 

JButton startknap = new JButton (“Start”);

 

getContentPane().add(startknap);

 

// Tilføj eventhåndtering

 

ActionListener listener = new EventHandler (indikator);

 

vandret.setActionCommand (“vandret”);

lodret.setActionCommand (“lodret”);

startknap.setActionCommand (“start”);

 

vandret.addActionListener (listener);

lodret.addActionListener (listener);

startknap.addActionListener (listener);

}

 

public static final void main (String args[])

{

MillionIndicator me = new MillionIndicator();

me.show();

}

}

 

Hovedparten af ovenstående klasse opretter den grafiske brugergrænseflade. Der bliver oprettet to variantknapper, som kan bruges til at vælge, om indikatoren skal fyldes vandret eller lodret. Derudover blive selve indikatoren oprettet. Til at starte med, er den indstillet til at blive fyldt vandret. Indikatorens interval sættes til fra 0 til 1.000.000. Endelig tilføjes en startknap, der sørger for, at optællingen begynder.

 

Når brugeren vælger en af de to variantknapper eller startknappen, overgår kontrollen til action listener’en. Denne er i dette eksempel en instans af klassen EventHandler, der er defineret således:

 

class EventHandler implements ActionListener

{

private JProgressBar indikator;

 

 

public EventHandler (JProgressBar indikator)

{

this.indikator = indikator;

}

 

public void actionPerformed (ActionEvent e)

{

String command = e.getActionCommand();

 

if (command.equals(“vandret”))

{

indikator.setOrientation (JProgressBar.HORIZONTAL);

}

else

if (command.equals(“lodret”))

{

indikator.setOrientation (JProgressBar.VERTICAL);

}

else

if (command.equals(“start”))

{

MillionCounter counter = new MillionCounter (indikator);

counter.start();

}

}

}

 

Når brugeren vælger vandret eller lodret, kaldes metoden setOrientation på indikatoren, og retningen tilpasses brugerens valg.

 

Hvis brugeren klikker på startknappen, bliver der oprettet en instans af klassen MillionCounter, der udfører selve opgaven og håndterer opdateringen af indikatoren. MillionCounter-klassen er implementeret således:

class MillionCounter extends Thread

{

JProgressBar indikator;

 

public MillionCounter (JProgressBar indikator)

{

this.indikator = indikator;

}

 

public void run()

{

for (int i = 0; i < 1000000; i++)

{

indikator.setValue (i);

}

}

}

 

 

Klassen nedarver fra Thread-klassen, hvilket gør, at den kan afvikles asynkront. For hver gang klassen tæller tællervariablen 1 op, bliver indikatoren opdateret. Dette er godt nok i dette eksempel, men af hensyn til hastigheden vil man i en virkelig applikation nok vælge kun at opdatere i givne intervaller – for eksempel hver gang tællervariablen er talt et tusind op.

 

Afviklingen af programmet giver et skærmbillede svarende til det i figur 2.4.

 

 

Fig. 2.4 – en vandret indikator.

 

Det er ikke de store ændringer, der skal foretages for at ændre programmet til at være timer-styret. Faktisk behøver selve brugergrænsefladen slet ikke at blive ændret. Kun den klasse, der håndterer events og den klasse, som udfører opgaven, skal opdateres.

 

Tællerklassen skal ikke længere selv håndtere opdateringen af brugergrænsefladen, og derfor bliver den simplere. Til gengæld skal den implementere en funktion, der angiver, hvor langt den er kommet med opgaven, og en funktion, der angiver, om opgaven er udført.

 

Det betyder, at tællerklassen nu ser således ud:

 

class TimerMillionCounter extends Thread

{

private int i;

 

public void run()

{

for (i = 0; i < 1000000; i++);

}

 

public int getStatus ()

 

{

return i;

}

 

public boolean isDone ()

{

return i == 1000000;

}

}

 

Det ses, at tællervariablen – i – er blevet flyttet fra at være en lokal variabel til at være en privat instansvariabel. Det skyldes, at den nu også bruges i funktionerne getStatus og isDone.

 

Prisen for den simplere tællerklasse betales i eventhåndteringen. Her er det ikke længere nok bare at starte tælleren. Nu skal der også startes en timer, der er tilknyttet tælleren. Derudover er det nu eventhåndteringen, der skal sørge for opdateringen af brugergrænsefladen.

 

Klassen, der håndterer events, kommer derfor til at se således ud ved den timerstyrede indikator:

 

class TimerEventHandler implements ActionListener

{

private JProgressBar indikator;

private Timer timer = null;

private TimerMillionCounter counter = null;

 

public TimerEventHandler (JProgressBar indikator)

{

this.indikator = indikator;

counter = new TimerMillionCounter ();

timer = new Timer (1, this);

}

 

public void actionPerformed (ActionEvent e)

{

String command = e.getActionCommand();

 

if (command != null)

{

if (command.equals(“vandret”))

{

indikator.setOrientation (JProgressBar.HORIZONTAL);

}

else

if (command.equals(“lodret”))

{

indikator.setOrientation (JProgressBar.VERTICAL);

}

else

if (command.equals(“start”))

{

 

counter.start();

timer.start();

}

}

else

{

indikator.setValue (counter.getStatus());

if (counter.isDone())

{

timer.stop();

}

}

}

}

 

Som det ses, bliver timeren repræsenteret af klassen Timer. Både timeren og referencen til indikatoren er private variable, da de bruges flere forskellige steder i programmet. Timer-klassen har følgende signatur:

 

public class Timer extends java.lang.Object implements java.io.Serializable

{

public com.sun.java.swing.Timer(int,java.awt.event.ActionListener);

public void addActionListener(java.awt.event.ActionListener);

public int getDelay();

public int getInitialDelay();

public void removeActionListener(java.awt.event.ActionListener);

public void restart();

public void setDelay(int);

public void setInitialDelay(int);

public void start();

public void stop();

}

 

Hver gang et bestemt tidsrum er gået, genererer Timer-klassen en ActionEvent. Derfor skal der tilknyttes en action-listener. Både action-listener’en og intervallet angivet i millisekunder skal angives i klassens constructor. I eksemplet er intervallet 1 millisekund og den samme action-listener bruges til timeren og til de øvrige events.

 

Hvis man bruger et langt interval, kan man med fordel bruge metoden setInitialDelay for at sikre, at event’en opstår hurtigere første gang. Den parameter, der sendes med til setInitialDelay, angiver, hvor mange millisekunder, der skal gå, før den første begivenhed indtræffer. Herefter opstår events med det interval, som blev angivet i constructoren.

 

Skydere

 

Skydere (sliders) bruges til at lade brugeren angive en talværdi i et afgrænset område. Typisk anvendes skydere når talværdien bruges til at regulere

 

hastighed, lydstyrke eller lignende – svarende til de skydere, der blev brugt på gamle fjernsyn til at regulere lydstyrken.

 

Fordelene ved at bruge en skyder fremfor et almindeligt indtastningsfelt er, at man undgår indtastningsfejl, og at man sikrer, at værdien ligger i det tilladte interval.

 

Klassen JSlider bruges til at oprette en skyder. Denne klasse har følgende signatur:

 

public class JSlider extends com.sun.java.swing.JComponent implements com.sun.java.swing.SwingConstants, com.sun.java.accessibility.Accessible

{

public com.sun.java.swing.JSlider();

public com.sun.java.swing.JSlider(int,int,int,int);

 

public void addChangeListener(com.sun.java.swing.event.ChangeListener);

public java.util.Hashtable createStandardLabels(int);

public java.util.Hashtable createStandardLabels(int, int);

public int getExtent();

public boolean getInverted();

public java.util.Dictionary getLabelTable();

public int getMajorTickSpacing();

public int getMaximum();

public int getMinimum();

public int getMinorTickSpacing();

public int getOrientation();

public boolean getPaintLabels();

public boolean getPaintTicks();

public boolean getPaintTrack();

public boolean getSnapToTicks();

public int getValue();

public boolean getValueIsAdjusting();

public void removeChangeListener(com.sun.java.swing.event.ChangeListener);

public void setExtent(int);

public void setInverted(boolean);

public void setLabelTable(java.util.Dictionary);

public void setMajorTickSpacing(int);

public void setMaximum(int);

public void setMinimum(int);

public void setMinorTickSpacing(int);

public void setOrientation(int);

public void setPaintLabels(boolean);

public void setPaintTicks(boolean);

public void setPaintTrack(boolean);

public void setSnapToTicks(boolean);

public void setValue(int);

public void setValueIsAdjusting(boolean);

}

 

Hvis man bruger den constructor, der tager fire parametre, skal disse være retningen – JSlider.VERTICAL eller JSlider.HORIZONTAL – minimumsværdien

 

, maksimumværdien og den værdi, skyderen skal være indstillet på.

 

En skyder er opdelt i små og store intervaller. De store intervaller kan eksempelvis være 10 enheder, mens de små er en enkelt enhed. Metoderne setMinorTickSpacing og setMajorTickSpacing bruges til at angive dette. Man kan vælge, om brugeren må angive værdier, der ligger imellem intervallerne. Hvis man kalder setSnapToTicks(true), kan man kun vælge værdier, der ligger på intervallerne.

 

Man har mange muligheder for at angive, hvordan skyderen skal se ud. Med metoden setPaintLabels kan man vælge, om der skal sættes labels på skyderen, mens setPaintTicks angiver, om der skal tegnes linier til at indikere intervallerne. Metoden setOrientation angiver, om skyderen skal være lodret eller vandret. Hvis man har brug for at vende skyderen om, så den største værdi er til venstre og den mindste til højre, kan man gøre det ved hjælp af setInverted.

 

Alle de ovenstående metoder har naturligvis tilknyttede get-funktioner. Den vigtigste get-funktion er nok getValue, der returnerer den værdi, brugeren har valgt.

 

Nedenstående eksempel viser, hvordan man kan lade brugeren vælge udseendet af skyderen og bruge skyderen til at bestemme, hvor lang tid der skal gå mellem udsendelse af bip.

 

import com.sun.java.swing.*;

import com.sun.java.swing.event.*;

import java.awt.event.*;

import java.awt.*;

 

public class Beep extends JFrame

{

public Beep()

{

// Tilpas vinduet

 

super (“Beep”);

setSize (400,300);

 

// Opret valgmuligheder for brugeren

 

JRadioButton vandret = new JRadioButton (“Vandret”, true);

JRadioButton lodret = new JRadioButton (“Lodret”);

ButtonGroup gruppe = new ButtonGroup();

gruppe.add (vandret);

gruppe.add (lodret);

 

JCheckBox labels = new JCheckBox (“Vis labels”);

JCheckBox intervaller = new JCheckBox (“Vis intervaller”);

 

JPanel panel = new JPanel();

panel.setLayout (new GridLayout(2,2));

 

panel.add (vandret);

 

panel.add (lodret);

panel.add (labels);

panel.add (intervaller);

 

getContentPane().add(panel, BorderLayout.NORTH);

 

// Opret skyder

 

JSlider slider = new JSlider (JSlider.HORIZONTAL, 0, 30, 15);

slider.setPaintTicks (false);

slider.setPaintLabels (false);

slider.setMajorTickSpacing (5);

slider.setMinorTickSpacing (1);

 

getContentPane().add (slider, BorderLayout.SOUTH);

 

// Opret en timer, der bestemmer, hvornår der skal lyde et bip

 

Timer timer = new Timer (slider.getValue()*1000, new TimerListener());

timer.start();

 

// Tilføj eventhåndtering

 

ActionListener listener = new BeepListener(slider);

 

vandret.setActionCommand (“vandret”);

lodret.setActionCommand (“lodret”);

labels.setActionCommand (“labels”);

intervaller.setActionCommand (“intervaller”);

 

vandret.addActionListener (listener);

lodret.addActionListener (listener);

labels.addActionListener (listener);

intervaller.addActionListener (listener);

 

slider.addChangeListener (new SliderListener(timer));

}

 

public static final void main (String args[])

{

Beep me = new Beep();

me.show();

}

}

 

Den første del af klassens constructor opretter den del af brugergrænsefladen, der giver brugeren mulighed for at indstille udseendet af skyderen.

 

Selve skyderen oprettes som en vandret skyder med værdier i intervallet 0 til 30 og med startværdien 15. Derefter kaldes setPaintTicks og setPaintLabels, der sørger for at skyderens udseende initialt passer med

 

indstillingerne i brugergrænsefladen. Endeligt sættes intervallet til 5 sekunder. Hvert interval inddeles i underintervaller hvert på et enkelt sekund.

 

Eventhåndteringen i programmet er stort set som forventet. Blot skal man lægge mærke til, at JSlider-objekter bruger ChangeListener-objekter og ikke ActionListener-objekter.

 

Håndteringen af de events, der ændrer udseendet af skyderen, varetages af klassen BeepListener. Denne klasse er implementeret således:

 

class BeepListener implements ActionListener

{

private JSlider slider;

 

public BeepListener (JSlider slider)

{

this.slider = slider;

}

 

public void actionPerformed (ActionEvent e)

{

 

String command = e.getActionCommand();

 

if (command.equals(“vandret”))

{

slider.setOrientation (JSlider.HORIZONTAL);

}

else

if (command.equals(“lodret”))

{

slider.setOrientation (JSlider.VERTICAL);

}

else

if (command.equals(“labels”))

{

slider.setPaintLabels(!slider.getPaintLabels());

}

else

if (command.equals(“intervaller”))

{

slider.setPaintTicks(!slider.getPaintTicks());

}

slider.revalidate();

 

}

}

 

De kommandoer, der ændrer skyderen fra vandret til lodret og omvendt, implementeres ved hjælp af setOrientation-metoden. De to øvrige – visning af labels og intervaller – ændres ved hjælp af henholdsvis setPaintLabels og setPaintTicks. I stedet for at se om afkrydsningsfelterne (checkboksene) er markerede, benyttes JSlider-klassens get-funktioner til at finde ud af, om elementerne skal vises eller fjernes.

 

 

Håndtering af de events, der opstår, når brugeren flytter på skyderen, håndteres af klassen SliderListener. Implementeringen af SliderListener er som følger:

 

class SliderListener implements ChangeListener

{

private Timer timer;

 

public SliderListener (Timer timer)

{

this.timer = timer;

}

 

public void stateChanged (ChangeEvent e)

{

timer.setDelay (((JSlider) e.getSource()).getValue() * 1000);

}

}

 

Hver gang skyderens værdi ændres, bliver stateChanged-metoden kaldt. Denne ændrer intervallet på den timer, der kontrollerer den tid, som går mellem bippene. For at få en reference til JSlider-objektet kaldes metoden getSource på ChangeEvent-objektet. Når dette er castet til et JSlider-objekt, kan metoden getValue kaldes. Denne returnerer skyderens værdi. Da Timer-objekter regner i millisekunder, og skyderen regner i sekunder, skal resultatet ganges med 1.000.

 

Hver gang Timer-objektet aktiveres, opstår som bekendt en ActionEvent. Denne håndteres af klassen TimerListener:

 

class TimerListener implements ActionListener

{

public void actionPerformed (ActionEvent e)

{

Toolkit.getDefaultToolkit().beep();

}

}

 

Afvikling af programmet giver et skærmbillede svarende til det i figur 2.5.

 

 

Fig. 2.5 – En vandret skyder med intervaller og labels.

 

 

 

 

 

 

 

 

Faneblade

 

Ligesom faneblade i et ringbind bruges til at gruppere ting, der logisk hører sammen, og i øvrigt skabe overblik, bruger man faneblade i et program til at samle de komponenter på brugergrænsefladen, der hører sammen.

 

Alle komponenter – det vil sige alle klasser, der nedarver fra java.awt.Component – kan tilføjes som faneblade. Oftest vil man dog tilføje paneler, der indeholder flere komponenter som en fane.

 

Fanebladene håndteres af klassen JTabbedPane. Denne har følgende signatur:

 

public class JTabbedPane extends com.sun.java.swing.JComponent implements java.io.Serializable, com.sun.java.accessibility.Accessible, com.sun.java.swing.SwingConstants

{

public com.sun.java.swing.JTabbedPane();

public com.sun.java.swing.JTabbedPane(int);

 

public java.awt.Component add(java.awt.Component);

public java.awt.Component add(java.awt.Component, int);

public void add(java.awt.Component, java.lang.Object);

public void add(java.awt.Component, java.lang.Object, int);

public java.awt.Component add(java.lang.String, java.awt.Component);

public void addChangeListener(com.sun.java.swing.event.ChangeListener);

public void addTab(java.lang.String, com.sun.java.swing.Icon, java.awt.Component);

public void addTab(java.lang.String, com.sun.java.swing.Icon, java.awt.Component, java.lang.String);

public void addTab(java.lang.String, java.awt.Component);

public int getSelectedIndex();

public int getTabCount();

public void insertTab(java.lang.String, com.sun.java.swing.Icon, java.awt.Component, java.lang.String, int);

public void remove(java.awt.Component);

public void removeAll();

public void removeChangeListener(com.sun.java.swing.event.ChangeListener);

public void removeTabAt(int);

 

public void setSelectedComponent(java.awt.Component);

public void setSelectedIndex(int);

public void setTabPlacement(int);

}

 

Klassen indeholder en række add-funktioner, der hver tilføjer en fane til oversigten. Den tekst, der vises på en fane, kan enten være komponentens navn, eller man kan angive sin egen tekst. Hvis man benytter en af addTab-funktionerne i stedet, kan man tilføje en komponent og give fanen en titel og eventuelt også et ikon. Ikoner bliver beskrevet yderligere i kapitel 3. Både add- og addTab-metoderne tilføjer fanen i slutningen af listen. Hvis man vil indsætte en fane på et bestemt sted i listen, skal man bruge insertTab-metoden.

 

JTabbedPane-klassen sørger selv for at vise det rigtige faneblad, når brugeren klikker på en fane. Hvis man alligevel har brug for at vide, hvilket faneblad, der vises i øjeblikket, kan man kalde metoden getSelectedIndex, der returnerer positionen på den fane, som er markeret, eller getSelectedComponent, der returnerer det komponent, som er markeret.

 

Nedenstående eksempel viser, hvordan man kan tilføje forskellige komponenter til en liste af faneblade.

 

import com.sun.java.swing.*;

import java.awt.*;

 

public class TabDemo extends JFrame

{

public TabDemo()

{

// Tilpas vinduet

 

super (“Faneblade”);

setSize (400,300);

 

// Opret JTabbedPane

 

JTabbedPane faneblade = new JTabbedPane();

 

// Tilføj nogle faner vha. forskellige metoder

 

faneblade.add (“Tekstfelt”, new JTextField (“Tekstfelt”));

faneblade.addTab (“Knap”, new JButton (“Klik her!”));

faneblade.insertTab (“Label”, null, new JLabel (“Label”), null, 2);

 

JPanel panel = new JPanel();

panel.setLayout (new GridLayout(3,1));

panel.add (new JTextField(“Et tekstfelt,”));

panel.add (new JLabel(“lidt tekst”));

panel.add (new JButton(“og en knap”));

 

faneblade.add (“Panel”, panel);

 

 

// Tilføj fanebladene til vinduet

 

getContentPane().add (faneblade);

}

 

public static final void main (String args[])

{

TabDemo me = new TabDemo();

me.show();

}

}

 

Programmet tilføjer et tekstfelt, en knap, en label og et panel til fanebladsoversigten, før denne føjes til vinduet. Det giver et skærmbillede svarende til figur 2.6.

 

 

Fig. 2.6 – Faneblade.

 

 

 

 

 

 

 

 

Træer

 

I Swing bruges træer til grafisk repræsentation. Det vil sige, at de ikke virkeligt indeholder dataene. Træer skal således i denne sammenhæng ikke forstås som eksempelvis binære søgetræer. Meget af teorien er imidlertid den samme.

 

Figur 2.7 viser, hvordan et træ kan se ud. Hvert element i træet er en knude – eller på engelsk en node. Den øverste knude  – ”Avanceret Java-programmering” – er roden af træet, mens de knuder, der ikke har andre knuder under sig – for eksempel Swing kontra AWT – kaldes blade. Knuder, der er på samme niveau, kaldes søskende. Knuder, der er på et niveau højere end den aktuelle knude, er denne knudes forældre, mens knuder på et lavere niveau er knudens børn. I eksemplet i figur 2.7 betyder det altså, at ”Swing kontra AWT” og ”Vinduer” er søskende, der begge er børn af ”Kapitel 1”.

 

 

Fig. 2.7 – Trærepræsentation af data.

 

I Java repræsenteres alle knuder af klassen DefaultMutableTreeNode, der ligger i pakken com.sun.java.swing.tree. Denne klasse har følgende signatur:

 

 

public class DefaultMutableTreeNode extends java.lang.Object implements java.lang.Cloneable, com.sun.java.swing.tree.MutableTreeNode, java.io.Serializable

{

public com.sun.java.swing.tree.DefaultMutableTreeNode();

public com.sun.java.swing.tree.DefaultMutableTreeNode(java.lang.Object);

public com.sun.java.swing.tree.DefaultMutableTreeNode(java.lang.Object,boolean);

 

public void add(com.sun.java.swing.tree.MutableTreeNode);

public com.sun.java.swing.tree.TreeNode getChildAfter(com.sun.java.swing.tree.TreeNode);

public com.sun.java.swing.tree.TreeNode getChildAt(int);

public com.sun.java.swing.tree.TreeNode getChildBefore(com.sun.java.swing.tree.TreeNode);

public int getChildCount();

public int getDepth();

public com.sun.java.swing.tree.TreeNode getFirstChild();

public com.sun.java.swing.tree.DefaultMutableTreeNode getFirstLeaf();

public int getIndex(com.sun.java.swing.tree.TreeNode);

public com.sun.java.swing.tree.TreeNode getLastChild();

public com.sun.java.swing.tree.DefaultMutableTreeNode getLastLeaf();

public int getLeafCount();

public int getLevel();

public com.sun.java.swing.tree.DefaultMutableTreeNode getNextLeaf();

public com.sun.java.swing.tree.DefaultMutableTreeNode getNextNode();

public com.sun.java.swing.tree.DefaultMutableTreeNode getNextSibling();

 

public com.sun.java.swing.tree.TreeNode getParent();

public com.sun.java.swing.tree.DefaultMutableTreeNode getPreviousLeaf();

public com.sun.java.swing.tree.DefaultMutableTreeNode getPreviousNode();

public com.sun.java.swing.tree.DefaultMutableTreeNode getPreviousSibling();

public com.sun.java.swing.tree.TreeNode getRoot();

public com.sun.java.swing.tree.TreeNode getSharedAncestor(com.sun.java.swing.tree.DefaultMutableTreeNode);

public int getSiblingCount();

public void insert(com.sun.java.swing.tree.MutableTreeNode, int);

public boolean isLeaf();

public boolean isNodeAncestor(com.sun.java.swing.tree.TreeNode);

public boolean isNodeChild(com.sun.java.swing.tree.TreeNode);

public boolean isNodeDescendant(com.sun.java.swing.tree.DefaultMutableTreeNode);

public boolean isNodeRelated(com.sun.java.swing.tree.DefaultMutableTreeNode);

public boolean isNodeSibling(com.sun.java.swing.tree.TreeNode);

public boolean isRoot();

public void remove(int);

public void remove(com.sun.java.swing.tree.MutableTreeNode);

public void removeAllChildren();

public void removeFromParent();

public void setAllowsChildren(boolean);

public void setParent(com.sun.java.swing.tree.MutableTreeNode);

}

 

Klassen indeholder mange af de funktioner, der er typiske for træer. Mulighed for at få information om den aktuelle knude – er den et blad, er den roden, og så videre – og mulighed for at få referencer til de knuder, der befinder sig i nærheden af den aktuelle knude.

 

Hvis man blot vil have en grafisk repræsentation af sine data, kan man nøjes med at kende metoden add. Den tilføjer en knude som barn til den aktuelle knude.

 

Den første knude – roden – bruges til at oprette selve træet. Træet repræsenteres af klassen Jtree, og roden sendes med som parameter til dennes constructor.

 

JTree-klassen indeholder følgende metoder:

 

public class JTree extends com.sun.java.swing.JComponent implements com.sun.java.swing.Scrollable, com.sun.java.accessibility.Accessible

{

public com.sun.java.swing.JTree();

 

public com.sun.java.swing.JTree(com.sun.java.swing.tree.TreeNode);

public com.sun.java.swing.JTree(com.sun.java.swing.tree.TreeNode,boolean);

public com.sun.java.swing.JTree(java.util.Hashtable);

public com.sun.java.swing.JTree(java.util.Vector);

public com.sun.java.swing.JTree(java.lang.Object[]);

 

public void addTreeExpansionListener(com.sun.java.swing.event.TreeExpansionListener);

public void addTreeSelectionListener(com.sun.java.swing.event.TreeSelectionListener);

public void addTreeWillExpandListener(com.sun.java.swing.event.TreeWillExpandListener);

public void collapsePath(com.sun.java.swing.tree.TreePath);

public void collapseRow(int);

public void expandPath(com.sun.java.swing.tree.TreePath);

public void expandRow(int);

public com.sun.java.swing.tree.TreePath getClosestPathForLocation(int, int);

public int getClosestRowForLocation(int, int);

public com.sun.java.swing.tree.TreePath getEditingPath();

public java.lang.Object getLastSelectedPathComponent();

public com.sun.java.swing.tree.TreePath getLeadSelectionPath();

public int getLeadSelectionRow();

public int getMaxSelectionRow();

public int getMinSelectionRow();

protected com.sun.java.swing.tree.TreePath[] getPathBetweenRows(int, int);

public com.sun.java.swing.tree.TreePath getPathForLocation(int, int);

public com.sun.java.swing.tree.TreePath getPathForRow(int);

public int getRowCount();

public int getRowForLocation(int, int);

public int getRowForPath(com.sun.java.swing.tree.TreePath);

public int getRowHeight();

public int getSelectionCount();

public com.sun.java.swing.tree.TreeSelectionModel getSelectionModel();

public com.sun.java.swing.tree.TreePath getSelectionPath();

public com.sun.java.swing.tree.TreePath[] getSelectionPaths();

public void makeVisible(com.sun.java.swing.tree.TreePath);

public void removeTreeExpansionListener(com.sun.java.swing.event.TreeExpansionListener);

 

public void removeTreeSelectionListener(com.sun.java.swing.event.TreeSelectionListener);

public void removeTreeWillExpandListener(com.sun.java.swing.event.TreeWillExpandListener);

public void scrollPathToVisible(com.sun.java.swing.tree.TreePath);

public void scrollRowToVisible(int);

}

 

Mange af JTree’s metoder kan bruges til at få informationer om træet og dets indhold. Disse vil blive beskrevet senere i dette kapitel. I første omgang er det nok at se, at klassen har en constructor, der tager et TreeNode-objekt. TreeNode er et interface, som DefaultMutableTreeNode implementerer. Som det ses af nedenstående eksempel, betyder det, at man kan oprette et træ baseret på én knude – roden.

 

import com.sun.java.swing.*;

import com.sun.java.swing.tree.*;

 

public class Indholdsfortegnelse extends JFrame

{

public Indholdsfortegnelse ()

{

// Tilpas vinduet

 

super (“Indholdsfortegnelse”);

setSize (400,300);

 

// Opret knuder

 

DefaultMutableTreeNode bog = new DefaultMutableTreeNode (“Avanceret Java-programmering”);

 

DefaultMutableTreeNode kap1 = new DefaultMutableTreeNode (“Kapitel 1”);

DefaultMutableTreeNode kap2 = new DefaultMutableTreeNode (“Kapitel 2”);

 

bog.add(kap1);

bog.add(kap2);

 

DefaultMutableTreeNode emne = null;

 

// Tilføj nogle emner fra kapitel 1

 

kap1.add(new DefaultMutableTreeNode (“Swing kontra AWT”));

kap1.add(new DefaultMutableTreeNode (“Vinduer”));

kap1.add(new DefaultMutableTreeNode (“Standarddialogbokse”));

 

// Tilføj nogle emner fra kapitel 2

 

 

DefaultMutableTreeNode menuer = new DefaultMutableTreeNode (“Menuer”);

menuer.add (new DefaultMutableTreeNode (“Genvejstaster”));

menuer.add (new DefaultMutableTreeNode (“Action”));

kap2.add (menuer);

kap2.add (new DefaultMutableTreeNode (“Indikatorer”));

 

// Opret træet

 

JTree tree = new JTree (bog);

 

// Tilføj træet til vinduet

 

getContentPane().add (tree);

}

 

public static final void main (String args[])

{

Indholdsfortegnelse me = new Indholdsfortegnelse();

me.show();

}

}

 

Ovenstående klasse giver det skærmbillede, der sås af figur 2.7.

 

Mange af de funktioner, der findes i JTree, er relateret til at finde stier i træet. Disse stier repræsenteres af klassen TreePath. TreePath-objekter er en række af objekter, der findes i en sti fra en knude i træet til en anden. TreePath-klassen har følgende signatur:

 

public class TreePath extends java.lang.Object implements java.io.Serializable

{

public com.sun.java.swing.tree.TreePath(java.lang.Object);

public com.sun.java.swing.tree.TreePath(java.lang.Object[]);

 

public java.lang.Object getLastPathComponent();

public com.sun.java.swing.tree.TreePath getParentPath();

public java.lang.Object[] getPath();

public java.lang.Object getPathComponent(int);

public int getPathCount();

public boolean isDescendant(com.sun.java.swing.tree.TreePath);

public com.sun.java.swing.tree.TreePath pathByAddingChild(java.lang.Object);

}

 

Metoden getLastPathComponent kan bruges til at få en reference til det sidste element i stien. Når man har bearbejdet dette, kan man få en reference til resten af stien ved at kalde getParentPath.

 

Når træet er defineret, kan man uden videre tilføje det til et vindue, og det

 

vil så blive vist. Hvis man ønsker det layoutet på en anden måde end den, Java sædvanligvis bruger, kan man gøre det ved hjælp af klassen BasicTreeCellRenderer. Med denne klasse er det blandt andet muligt at ændre de ikoner, der bruges til at vide, at en knude har børn. BasicTreeCellRenderer vil blive beskrevet yderligere i kapitel 3.

 

Tråde

 

Swing er ikke fuldstændigt synkroniseret. Det vil sige, at man selv skal være opmærksom på, at der kan opstå synkroniseringsproblemer.

 

Hvis ens program er en applikation – det vil sige ikke en applet – og det har følgende opbygning, er der ingen problemer:

 

public class MyApp

{

public static final void main (String args[])

{

JFrame vindue = new JFrame();

 

// Tilføj komponenter…

 

vindue.pack()

vindue.show();

 

// Herefter ændres der ikke på brugergrænsefladen

}

 

// Al øvrig manipulation af brugergrænsefladen sker i

// eventhandlers som for eksempel actionPerformed.

}

 

Er ens program derimod en applet, eller opretter man tråde, der manipulerer brugergrænsefladen, kan der opstå problemer. Generelt er der en regel, der skal overholdes:

“Når et Swing-komponent er blevet realiseret, skal al kode, der kan påvirke eller afhænger af komponentets tilstand, afvikles i en eventhåndteringstråd.”

 

For at forstå reglen må man forstå, hvornår et komponent er realiseret. Det er det, når komponentets paint-metode er blevet kaldt. Ved vinduer sker det ved at kalde setVisible(true), show eller pack. Alle komponenter, der er en del af et vindue, som bliver realiseret, bliver også selv realiseret. Et komponent bliver også realiseret, hvis det bliver tilføjet til en container, der allerede er realiseret.

 

Som bekendt findes der ikke en regel uden undtagelse. I dette tilfælde er undtagelsen de metoder, der i Java-specifikationerne er markeret som thread safe. Dette gælder blandt andet metoderne repaint, revalidate og invalidate. Derudover er det altid tilladt at tilføje eller fjerne en listener.

 

Generelt er følgende fremgangsmåde sikker:

 

1.   Man opretter en brugergrænseflade uden at vise den. Man må altid oprette en brugergrænseflade, når bare den ikke refererer til en brugergrænseflade, der allerede er realiseret.

 

 

2.   Man realiserer komponenterne i brugergrænsefladen ved at kalde pack-metoden.

3.   Umiddelbart derefter viser man vinduet ved hjælp af setVisible(true) eller show. Dette er ikke en sikker operation, da komponenterne allerede er blevet realiseret, men da brugergrænsefladen ikke bliver vist, er det særdeles usandsynligt, at paint-metoden vil blive kaldt, før setVisible eller show returnerer.

4.   Der bliver ikke udført yderligere funktioner relateret til brugergrænsefladen i hovedtråden. Al manipulation foretages i metoder, der bliver kaldt som respons på events.

 

I langt de fleste programmer falder det naturligt kun at opdatere brugergrænsefladen i forbindelse med håndtering af events. I de fleste programmer sker opdateringer efter eksempelvis klik på en knap. Der er imidlertid nogle undtagelser.

 

Det gælder for eksempel appletter. Alle appletter bliver tilføjet til sider, der allerede er synlige. Derfor bør al kode, der tilføjer komponenter til en applet, ligge i den tråd, som håndterer events.

 

Også programmer, der skal opdatere skærmbilleder, når der indtræffer begivenheder, der ikke er relateret til brugergrænsefladen, har problemer. Det kan eksempelvis være serverprogrammer, der modtager forespørgsler fra klienter og viser status i et vindue. Disse forespørgsler kan komme på et hvilket som helst tidspunkt – uafhængigt af, hvad der sker på brugergrænsefladen – og derfor skal disse opdateringer også lægges i den tråd, der håndterer events.

 

For at sikre, at alle opdateringer af brugergrænsefladen bliver lagt i eventhåndteringstråden, kan man bruge to metoder i klassen SwingUtilities. Det drejer sig om invokeLater og invokeAndWait. Metoden invokeLater kan kaldes fra enhver tråd for at bede eventhåndteringen om at udføre et bestemt stykke kode. Den kode, der skal udføres, skal lægges i run-metoden af et Runnable-objekt. Metoden returnerer omgående uden at vente på, at eventhåndteringen udfører koden. Nedenstående eksempel viser, hvordan invokeLater kan bruges:

 

Runnable doit = new Runnable()

{

public void run()

{

// Det, der skal udføres

}

};

 

SwingUtilities.invokeLater(doit);

 

Metoden invokeAndWait minder meget om invokeLater bortset fra, at invokeAndWait ikke returnerer, før eventhåndteringen er fuldført.

 

Alt i alt er der en del, man skal være opmærsom på, hvis man bruger tråde sammen med Swing. Anbefalingen må derfor være at undgå dem, hvis det er muligt. Som regel kan man sagtens lave en brugergrænseflade uden anvendelse af tråde. Der er imidlertid nogle tilfælde, hvor det er nødvendigt at bruge tråde:

 

 

 

  • Programmet udfører en opgave, der tager lang tid. Hvis dette lægges i eventhåndteringstråden, vil denne være blokeret, så længe opgaven udføres.
  • Programmet skal have en funktion udført med jævne mellemrum.
  • Programmet venter på beskeder fra klienter.

 

For at tillade programmører at bruge tråde i disse tilfælde, kan man bruge en af to hjælpeklasser. Det drejer sig om Timer-klassen, som er blevet gennemgået tidligere i kapitlet, og klassen SwingWorker. SwingWorker er udviklet af Sun, men er ikke en del af Swing. Klassen er implementeret således:

 

import com.sun.java.swing.SwingUtilities;

 

abstract class SwingWorker

{

private Object value;

private Thread thread;

 

/**

* Compute the value to be returned by the <code>get</code> method.

*/

 

public abstract Object construct();

 

/**

* Called on the event dispatching thread (not on the worker thread)

* after the <code>construct</code> method has returned.

*/

 

public void finished()

{

}

 

/**

* Return the value created by the <code>construct</code> method.

*/

 

public Object get()

{

while (true)  // keep trying if we’re interrupted

{

Thread t;

synchronized (SwingWorker.this)

{

t = thread;

if (t == null)

{

return value;

}

}

 

try

{

t.join();

}

catch (InterruptedException e)

{

}

}

}

 

/**

* Start a thread that will call the <code>construct</code> method

* and then exit.

*/

 

public SwingWorker()

{

final Runnable doFinished = new Runnable()

{

public void run()

{

finished();

}

};

 

Runnable doConstruct = new Runnable()

{

public void run()

{

synchronized(SwingWorker.this)

{

value = construct();

thread = null;

}

SwingUtilities.invokeLater(doFinished);

}

};

 

thread = new Thread(doConstruct);

thread.start();

}

}

 

For at bruge SwingWorker-klassen skal man først oprette en klasse, der nedarver fra den. Denne klasse skal implementere construct-metoden. Denne metode skal indeholde den kode, der skal udføres i tråden. Når SwingWorker-objektet instantieres, bliver der automatisk oprettet en tråd, som udfører arbejdet. Når man skal bruge resultatet af funktionen, kalder man SwingWorker’s get-funktion.

 


Sammenfatning

Hvor det foregående kapitel gav en overordnet introduktion til Swing, er der i dette kapitel blevet kigget nærmere på nogle af de mere avancerede emner og komponenter. Det er blandt andet blevet gennemgåer, hvordan man anvender en lang række af de komponenter, der ikke fandtes i AWT – for eksempel indikatorer, skydere, faneblade og træer – ligesom det er blevet forklaret, hvilke emner, man skal tænke på, når man arbejder med tråde i en applikation, der benytter Swing.

 

FAQ

 

Behøver man bruge indre klasser, når man bruger tråde sammen med Swing?

 

Nej. I de eksempler, der er i vist her i kapitlet, bruges indre klasser, fordi man på den måde kan gøre programmerne mindre. Hvis man vil, kan man sagtens oprette en ny klasse, instantiere den og sende en reference til den som parameter til eksempelvis invokeLater-metoden. Så kan programstumpen komme til at se således ud:

 

class myThread implements Runnable

{

public void run()

{

// Udfør beregningen

}

}

 

public class myClass

{

public static final void main (String args[])

{

// …

 

invokeLater (new myThread());

 

// …

 

}

}

 

 

Hvorfor bliver min menu vist bag de andre Swing-komponenter?

 

Nogle Swing-komponenter er lette – light weight – mens andre er tunge – heavy weight. Lette komponenter vil som standard ikke bliver vist foran tunge komponenter. For at ændre dette, så menuen bliver synlig, kan man indsætte nedenstående programlinie, inden man opretter menuer.

 

com.sun.java.swing.JPopupMenu.setDefaultLightWeightPopupEnabled(false);

 

Denne fremgangsmåde virker også, selvom man bruger andre menuer end JPopupMenu.

 

 

 

Hvorfor ser rammerne omkring min Swing-komponent anderledes ud, efter jeg har redefineret paint-metoden?

 

I Swing er tegningen af et komponent opdelt i flere forskellige rutiner:

 

  • paintComponent (Graphics)
  • paintBorder (Graphics)
  • paintChildren (Graphics)

 

Hvis man redefinerer paint-metoden, skal man huske at kalde disse metoder, da de ellers ikke vil blive udført.

Du læser en gammel bog
Den bog, du læser her, er fra 1998, og mange ting kan have ændret sig siden da.
Vi håber, at du stadig kan finde relevant information i den.
Hvis du vil læse aktuelle oplysninger om de avancerede dele af Java, anbefaler vi
bogen Core Java – Advanced Features

Comments are closed.