Simpel GUI-programmering

Opgaver

 

 

I det følgende, vil en række centrale klasser i Swing blive behandlet. Det er derfor nyttigt at have et klasse-hierarki, som beskriver sammenhængen mellem disse:
Figur 1:
Centrale klasser i Swing
Under klassen JComponent er det indikeret, at der findes en lang række subklasser.
De mørkeklasser, er de gamle fra AWT og de lyse, er de nye fra Swing. Man kan også genkende dem på, at Swings klasser alle starter med stort J.

 

1. Komponenter

En grafisk brugergrænseflade opbygges med grafiske komponenter. Alle grafiske komponenter i Swing er instanser af subklasser til JComponent. JComponent har 29 direkte subklasser, og en række indirekte (jeg er ikke klar over hvor mange).
Heavyweight og lightweight komponenter Den gamle AWT-klasse Component har også en lang række subklasser, der kan bruges til at lave grafiske komponenter. Der er dog lavet tilsvarende komponenter under Swing, for langt de fleste af AWT's komponenter. Det er hensigten at man kun skal bruge subklasser af JComponent og undlade at bruge de gamle fra Component. Man kalder AWT's komponent-klasser heavyweight komponenter og Swings lightweight komponenter. Der opstår mange tekniske problemer når man blander heavyweight og leightweight komponenter - lad vær med det, brug kun lightweight komponenter!

 

2. Vinduer

Der findes flere slags vinduer, men vi vil koncentrere os om den almindelige slags, der laves som instanser af JFrame. I praksis laver man egne subklasser af JFrame for at give vinduer de egenskaber man ønsker de skal have. Lad os se et eksempel:
Dette vindue:
Figur 2:
Tomt vindue
Laves med følgende program:
Frame1.java
import javax.swing.*;

public class Frame1 extends JFrame {
  
  public Frame1( String title ) {
    super( title );
    
    //...

    setSize( 200, 100 );
    setVisible( true );
  }
}
Main.java
public class TestFrame {
  
  public static void main( String[] argv ) {

    Frame1 f1 = new Frame1( "Frame nr.1" );
  }
}
Vi importerer her den package, der hedder javax.swing. Denne package indeholder de grundlæggende klasser i Swing, f.eks. JFrame.
Vores subklasse Frame1 af JFrame indeholder kun en konstruktor, da det er et meget simpelt eksempel.
Vores konstruktor tager en tekststreng som parameter, der skal bruges som titel på vores vindue. Vinduets titel sættes ved at sende den videre til JFrame's konstruktor. Det er meget almindeligt at sætte titlen på et vindue på denne måde. Man kan evt. ændre titlen senere ved at kalde metoden:
void setTitle( String title )
På det sted hvor kommentaren er placeret vil man opbygge vinduet med grafiske komponenter. Da vi naturligvis starter med et meget simpelt eksempel, har vi ingen sådanne komponenter.
Dernæst sætter vi vinduets størrelse til 200x100 (pixels). Man vil normalt ikke selv sætte vinduets størrelse, da det indirekte vil være givet ved de grafiske komponenter man placerer i vinduet. Da vi ikke har nogen komponenter til at styrre vinduets størrelse gør vi det manuelt. Hvis vi havde udeladt denne linie, vil vinduet være kolapset, og have følgende udseende:
Figur 3:
Vindue-kolaps
I sidste linie af konstruktoren gør vi vinduet synligt, med et kald af metoden:
void setVisible( boolean b )
Der bestemmer om vinduet er synligt eller ej. Inden vi kalder denne metode er vinduet usynligt, og vi kan foretage opsætning og justering af det, uden at det kan ses på skærmen.

 

2.1 Terminering af programmer

Programmet stopper ikke! Hvis man kører eksemplet og lukker vinduet (ved at klikke på luk-knappen i øverste højre hjørne af vinduet) vil man observere noget, der måske vil overraske: Programmet stopper ikke! (Dette kan bla. ses i Task Manager)
Det skyldes at det kun er vinduet man lukker, ikke programmet. Ofte vil man ønske at knytte lukning af vinduet sammen med terminering af programmet. Dette gøres ved en indstilling af framen:
Frame2.java
import javax.swing.*;
import java.awt.event.*;

public class Frame2 extends JFrame {
  
  public Frame2( String title ) {
    super( title );
    
    //...
    
    setDefaultCloseOperation( EXIT_ON_CLOSE );
    
    setSize( 200, 100 );
    setVisible( true );
  }
}
Hvis man nu kører programmet, vil lukning af vinduet samtidig terminere programmet.

 

2.2 Tilføje komponenter

Lad os se et eksempel der føjer tre grafiske komponenter til vores vindue.
Dette vindue:
Figur 4:
Vindue med tre komponenter
laves med følgende frame-klasse:
Frame3.java
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;

public class Frame3 extends JFrame {
  
  public Frame3( String title ) {
    super( title );

    getContentPane().setLayout( new FlowLayout() );
    
    JLabel label = new JLabel( "Dette er et label" );
    getContentPane().add( label );
    
    JButton knap = new JButton( "Dette er en knap" );
    getContentPane().add( knap );
    
    JTextField tekstFelt = new JTextField( "Dette er et tekstfelt" );
    getContentPane().add( tekstFelt );
    
    setDefaultCloseOperation( EXIT_ON_CLOSE );
    
    pack();
    setVisible( true );
  }
}
Vi tilføjer vores vindue, instanser af tre forskellige subklasser til JComponent: JLabel, JButton og JTextField.
Figur 5:
Tre subklasser af JComponent
Små stykker tekst Man bruger et JLabel til små stykker tekst man vil placere i vinduet. Labels kan ændre deres tekstuelle indhold, men normalt holder man dem immutable. Labels kan være overskrifter der beskriver hvad der er vinduets formål. De kan være tilknyttet andre grafiske komponenter; hvis betydning de angiver osv.
Knap En JButton er ganske enkelt en knap. Brugeren kan aktivere knappen ved at trykke på den, og på den måde sende en event til systemet. En knap vil normalt have en tekst der giver en indikation af dens funktionalitet.
Input Et JTextField bruges til at modtage input fra brugeren i form af små stykker tekst. Man kan som vi har gjort det, lade tekstfeltet indeholde en initiel tekst, som brugeren kan vælge at ændre i, eller helt slette.
pack Vi anvender metoden pack, som indstiller vinduets størrelse efter de komponenter det indeholder. Vinduet bliver "pakket" omkring komponenterne så der ikke er spildplads udenom.
   
Prøv:
Kør eksemplet - tryk lidt på knappen og skriv i tekstfeltet!
   
ContentPane Der er en række anvendelser af getContentPane. Vi skal senere se nærmere på panes og deres betydning for et vindue, men indtil videre vil vi blot tænke på ContentPane som indholdet af vinduet. Når vi add'er grafiske komponenter til ContentPane, føjer vi dem derfor til indholdet af vinduet.
Layout-manager Den første anvendelse af getContentPane bruges til at sætte layout-manageren med et kald af setLayout. Layout-managere styrer hvordan de grafiske komponenter placeres i vinduet, og vi vil i første omgang være tilfreds med at de bliver vist, og først senere studere mulighederne for at foretage denne styring.

 

2.3 Event-håndtering

Vi kan nu lave et vindue med labels, knapper og tekstfelter. Lad bringe lidt liv i dem, og lave vores første applikation.
Livsnerven i brugergrænsefladen er event-håndtering. Vi skal have vores grafiske komponenter til at sende events som vores program kan reagere på.
Vi vil lave et moms-program, som tager et beløb som input - beregner momsen - og viser resultatet.
  Vi vælger at give vores applikations-vindue følgende udseende:
Figur 6:
Frame til moms-beregning
De grafiske komponenter er to labels, to tekstfelter og en knap.
Vi kan lave selve vinduet med følgende program:
Frame4.java
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;

public class Frame4 extends JFrame {
  
  public Frame4( String title ) {
    super( title );

    getContentPane().setLayout( new FlowLayout() );
    
    JLabel inputLabel = new JLabel( "excl. moms:" );
    getContentPane().add( inputLabel );
    
    JTextField input = new JTextField( 8 );
    getContentPane().add( input );
    
    JButton knap = new JButton( "Beregn moms" );
    getContentPane().add( knap );
    
    JLabel outputLabel = new JLabel( "incl. moms:" );
    getContentPane().add( outputLabel );
    
    JTextField output = new JTextField( 8 );
    getContentPane().add( output );
    
    setDefaultCloseOperation( EXIT_ON_CLOSE );
    
    pack();
    setVisible( true );
  }
}
Man bemærker, at vi her har anvendt en anden af JTextFields konstruktorer, nemlig den der tager en int som parameter. Parameteren er antallet af kolonner som tekstfeltet skal have i bredden (Jeg er ikke bekendt med hvordan Swing præcist omregner dette til pixels, det synes at være betydelig mere end det tilsvarende antal tegns bredde).
Det første vi vil arbejde videre med, er at hindre brugeren i at rette i det tekstfelt, som viser beløb incl. moms.
Det gøres med metoden:
void setEditable( boolean b )
JTextField-objektet.
Vi indfører derfor linien:
 
output.setEditable( false );
Tekstfeltet ændrer derved udseende, og kan ikke længere editeres af brugeren.
Figur 7:
Tekstfelt, der ikke kan editeres
Dernæst vil vi identificere de komponenter som vi er interesseret i at modtage events fra.
Ingen events fra labels De to labels skal blot være "skilte", og dermed ikke spille nogen aktiv rolle.
Events fra knappen Når brugeren trykker på knappen skal der foretages en beregning af beløb incl. moms. Vi ønsker derfor at modtage en event fra denne knap, når den aktiveres.
Ingen events fra tekstfelter Det første tekstfelt bruges til indtastning, men vi er ikke umiddelbart interesseret i at modtage events vedrørende denne indtastning. Det er først når der trykkes på knappen at der skal ske noget. Det andet tekstfelt skal kun bruges til at vise resultatet, og events vedrørende dette er derfor heller ikke interessante.
For at kunne modtage events fra knappen skal vi (framen) tilmeldes os som listener ved knappen. Der findes flere forskellige grupper af events man kan tilmelde sig. Vi er interesseret i såkaldte ActionEvents, der indbefatter at der trykkes på knappen.
Vi kalder derfor metoden addActionListener på knappen:
 
knap.addActionListener( this );
Hvis vi oversætter programmet efter at have tilføjet denne linie får vi følgende fejl:
--------------------------- Compiler Output ---------------------------
Frame4.java:19: Incompatible type for method. Explicit cast needed to
                convert Frame4 to java.awt.event.ActionListener.
    knap.addActionListener( this );
                            ^
1 error
Problemet er, at vores frame ikke er en en ActionListener. En ActionListener er en klasse der har implementeret interfacet ActionListener. Dette interface indeholder én metode:
void actionPerformed( ActionEvent e )
Vi vil derfor implementere denne metode. Selve parameteren vil vi foreløbig se bort fra, da et tryk på knappen er det eneste, der kan udløse en ActionEvent i forbindelse med en JButton (tror jeg nok).
Vi kunne for eksperimentets skyld implementere den ved:
 
public void actionPerformed( ActionEvent e ) {
  System.out.println( "Event: Der blev trykket på knappen" );
}
 
Prøv:
Kør programmet med de ændrer vi har lavet af editerbarheden af det ene tekstfelt, samt tilføjelserne vedrørende ActionListener. Prøv at trykke på knappen.
 
Metoden actionPerformed bliver kaldt af JButton når der opstår en event. Det er derfor, det er nødvendigt for os (framen) at implementere et interface JButton kan kommunikere med os, når det sker.
Metoden skal naturligvis have et andet indhold. Den skal opdatere beløb incl. moms på grundlag af beløb excl. moms. Vi har derfor brugt for at kunne hente den tekst der står i input - omforme den til et tal - foretage beregningen - opdatere teksten i output, så den viser resultatet.
Metoden får følgende udseende:
public void actionPerformed( ActionEvent e ) {
  try {
    double excl = Double.parseDouble( input.getText() );
    double incl = excl * 1.25;
    output.setText( "" + incl );
  }
  catch ( NumberFormatException nfe ) {
  }
}
Vi bruger getText-metoden på JTextField for at hente indholdet af tekstfeltet. Vi parser teksten på sædvanlig vis, for at få den double, der danner grundlag for den videre beregning.
Resultatet vises med setText-metoden på JTextField, idet vi bruger det sædvanlige trick til at omforme en primitiv type til en tekststreng.
Man bemærker at vi ignorerer fejl ved konvertringen til double. Såfremt det der står i input-feltet ikke kan konverteres til en double, kunne man vælge at udskrive en fejlmeddelelse. Da vi endnu ikke har set på dialog'er vælger vi dog at reagerere med passivitet.
Man skal også bemærke at vores anvendelse af referencerne input og output, kræver at disse bliver instansvariable, i stedet for blot at være lokale variable i konstruktoren.
Vores færdige applikation bliver følgende:
Frame4.java
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;

public class Frame4 extends JFrame implements ActionListener {
  
  private JTextField input, output;
  
  public Frame4( String title ) {
    super( title );

    getContentPane().setLayout( new FlowLayout() );
    
    JLabel inputLabel = new JLabel( "excl. moms:" );
    getContentPane().add( inputLabel );
    
    input = new JTextField( 8 );
    getContentPane().add( input );
    
    JButton knap = new JButton( "Beregn moms" );
    knap.addActionListener( this );
    getContentPane().add( knap );
    
    JLabel outputLabel = new JLabel( "incl. moms:" );
    getContentPane().add( outputLabel );
    
    output = new JTextField( 8 );
    output.setEditable( false );
    getContentPane().add( output );
    
    setDefaultCloseOperation( EXIT_ON_CLOSE );
    
    pack();
    setVisible( true );
  }
  
  public void actionPerformed( ActionEvent e ) {
    try {
      double excl = Double.parseDouble( input.getText().trim() );
      double incl = excl * 1.25;
      output.setText( "" + incl );
    }
    catch ( NumberFormatException nfe ) {
    }
  }
}
Ved en anvendelse af moms-programmet kunne vinduet få følgende udseende:
Figur 8:
Anvendelse af moms-programmet

 

3. Flere events

I foregående afsnit var der kun ét komponent der kunne sende en event til vores frame: Knappen. Normalt vil der være mange komponenter der kan sende en event, og vi har derfor behov for at kunne identificere "kilden" - hvilket komponent event'en kommer fra.
Hvis vi f.eks. er ActionListener på en lang række komponenter, har vores actionPerformed-metode et problem:
 
public void actionPerformed( ActionEvent e ) {
  // Hvilket komponent sender mig e?
}
Det er en helt grundlæggende egenskab ved metodekald, at afsenderen er anonym - en metode ved ikke hvorfra den er blevet kaldt. Når metoden returnerer, kan den sende et resultat tilbage til den der har kaldt metoden, men den vil aldrig vide hvem det er.
Her er selve e en hjælp. ActionEvent-objektet indeholder selv information om, hvem der er ophav til event'en. Ved at kalde metoden:
Object getSource()
får man en reference til det komponent som udløste event'en.
For at kunne bruge denne information til at identificere komponentet, må vi derfor gemme referencer til de komponenter som senere kan sende events.
Lad os se et eksempel med to knapper:
Figur 9:
Vindue med to knapper
Dette vindue laves med følgende program:
TwoButtonFrame.java
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;

public class TwoButtonFrame extends JFrame implements ActionListener {
  private JButton knap1, knap2;
  
  public TwoButtonFrame( String title ) {
    super( title );

    getContentPane().setLayout( new FlowLayout() );
    
    knap1 = new JButton( "Knap 1" );
    knap1.addActionListener( this );
    getContentPane().add( knap1 );
    
    knap2 = new JButton( "Knap 2" );
    knap2.addActionListener( this );
    getContentPane().add( knap2 );
    
    setDefaultCloseOperation( EXIT_ON_CLOSE );
    
    pack();
    setVisible( true );
  }
  
  public void actionPerformed( ActionEvent e ) {
    Object source = e.getSource();
    
    if ( source == knap1 )
      System.out.println( "Event: Knap nr. 1" );
    else if ( source == knap2 )
      System.out.println( "Event: Knap nr. 2" );
  }
}
 
Prøv:
Kør programmet. Tryk på knapperne og se hvad der sker.
 
Vi er nu i stand til at skelne hvorfra en event kommer, og vores reaktion kan derfor indrette sig efter dette.

 

4. Mixin-klasser

Genbrug Når man løser opgaver med GUI-programmering, og i det hele taget laver programmer med en grafisk brugergrænseflade, vil man bemærke at det efterhånden bliver meget "copy/paste"-agtigt — man laver gang på gang, den samme (slags) kode. I den forbindelse kunne man så ønske at genbruge den kode man laver.
Det "blobber" Samtidig vil man opleve at konstruktorerne i JFrame-klasserne, har det med at blive meget stor — de "blobber"! (en betegnelse jeg nogle gange bruger, som er inspireret af det Anti-Pattern der hedder "Blob", der igen er inspireret af filmen "The Blob" fra 1958). Det betyder, at man ofte lader konstruktoren kalde en række service-metoder, der tager sig af forskellige opgaver i forbindelse med opsætning af brugergrænsefladen. Det kunne f.eks. være opbygning af en menu (som vi skal se i et senere kapitel om JMenu etc.). Laver man ikke sådanne service-metoder, får man en konstruktor på mange, mange linier!
Der er mange måder hvorpå man kan (forsøge at) håndtere problemet, men vi vil her se på en konstruktion der kaldes en Mixin-klasse. En Mixin-klasse, er en klasse, man af implementations-mæssige grunde vælger at placere ind imellem to klasser ifbm. nedarvning ("placere ind imellem" = "mix in"):
Figur 10:
Mixin-klasse
Ny "super-klasse" Super-klassen kunne være en klasse man ikke har mulighed for at ændre (som det f.eks. gør sig gældende med JFrame). Man laver derfor sin egen "super-klasse", som man ønsker ens klasser skal nedarve fra. Denne klasse placeres ind under super-klassen, og specialiserer den, som man måtte ønske den skulle have været. Dernæste lader man sine egne klasser (f.eks. SomeFrame) nedarve fra dén, i stedet for super-klassen.
Sjældent begreb Normalt vil denne Mixin-klasse ikke spille nogen begrebsmæssig rolle, men blot være en implimentations-mæssig konstruktion, der kompenserer for at vi ikke kan ændre super-klassen.
Hvordan Mixin-klassens implementation skal se ud kan variere meget, men lad os se et eksempel på en mulig MixinFrame:
MixinFrame.java
public abstract class MixinFrame extends JFrame {
  
  public MixinFrame( String title ) {
    super( title );
    
    Container pane = GetContentPane();
    
    initialize( pane );
    
    setDefaultCloseOperation( EXIT_ON_CLOSE );
    
    pack();
    setVisible( true );
  }
  
  protected abstract void initalize( Container pane );
}
Forpligter subklasser Her håndterer vi den sædvanlige initialisering af framen, husker at sætte close-operation etc. Ting man gør hver gang! Den øvrige opsætning af framen, overlader vi til subklasserne, idet vi forpligter dem til at implementere en metode, der med udgangspunkt i content-pane'en tager sig af resten.
Vi kan så lave en subklasse til Mixin-klassen:
SomeFrame.java
public class SomeFrame extends MixinFrame implements ... {
  
  public SomeFrame( String title ) {
    super( title );
  }
  
  protected void initalize( Container pane ) {
    ...
  }
}
Vi er nød til at lave vores egen konstruktor, da den slags jo ikke direkte nedarves, men ellers laver vi "bare" initialize-metoden.
Begrænsninger

Der er en ting man skal være opmærksom på. initialize-metoden kaldes fra MixinFrame's konstruktor. Det betyder at den bliver kørt før en evt. resterende del af SomeFrame's konstruktor. Dette skaber lidt rod i forholdet mellem konstruktoren og metoden, idet metoden ikke kan basere sig på noget konstruktoren i SomeFrame måtte gøre (ud over at kalde mixin-klassens konstruktor). Det betyder f.eks. at parametre til SomeFrame's konstruktor ikke kan danne grundlag for hvad initialize-metoden gør!

Forskellige projekter En mere generel ulempe — der gælder for enhver form for klasser man måtte lave, og ønsker at anvende i forskellige projekter — er at man skal have mixin-klassen kopieret med rundt i de forskellige projekter, hvor man ønsker at den skal være til rådighed. Forskellige udviklings-miljøer (f.eks. Eclipse) gør det muligt at inkludere class/jar-filer i ens projekter, selvom disse ligger et andet sted, så det er trods alt et overkommeligt problem.
Design mønster

Konstruktionen med en abstrakt metode, som kaldes i super-klassen, men implementeres af subklassen, er generelt beskrevet i Template Method Pattern. I termer af dette design mønster, er Mixin-klassens konstruktor en template-metode, og initialize-metoden er en hook-metode.

 

5. Specialisering af widget-klasser

Nedarve Vi har flere gange nedarvet fra JFrame og dermed specialiseret én af de klasser vi får fra Swing. Denne mulighed begrænser sig ikke til JFrame, vi kan også lave subklasser til andre grafiske komponenter. Ovenfor har vi flere gange haft brug for at parse tekst-indholdet af en JTextField til en integer eller en double. Vi kunne lave en subklasse til JTextField, der tilføjede denne funktionalitet:
NumericalTextField.java
import javax.swing.JTextField;

public class NumericalTextField extends JTextField {

  public NumericalTextField( int width ) {
    super( width );
  }

  public int getInt() {
    try {
      return Integer.parseInt( getText() );
    }
    catch ( NumberFormatException e ) {
      return 0;
    }
  }

  public double getDouble() {
    try {
      return Double.parseDouble( getText() );
    }
    catch ( NumberFormatException e ) {
      return 0.0;
    }
  }
}
Hvis vi nu anvender instanser (og referencer) af klassen NumericalTextField, vil vi kunne spørge disse tekstfelter, hvilken numerisk værdi de indeholder, ved at kalde den af de to get-metoder som returnerer den ønskede værdi.
SomeFrame.java
import ...
                      
public class SomeFrame extends JFrame implements ActionListener {
  private NumericalTextField tekstFelt;
  
  public SomeFrame( String title ) {
    super( title );
    
    ...

    tekstFelt = new NumericalTextField( 8 );
    tekstFelt.addActionListener( this );
    getContentPane().add( tekstFelt );
    
    ...
  }
  
  public void actionPerformed( ActionEvent e ) {
    ...
    
    double input = tekstFelt.getDouble();
    
    ...
  }
}
Simpel fejlhåndtering I forbindelse med implementationen af de to get-metoder, har vi lavet en meget simpel fejlhåndtering, hvor der returneres 0, når tekst-indholdet ikke kan fortolkes som en numerisk værdi af den ønskede type. Denne implementation er måske lidt for simpel, men det illustrerer idéen med at specialisere JTextField.

 

6. Plugable Look & Feel

PLAF En af de fordele Swing giver, ved at have gjort sig uafhængig af det underlæggende operativsystem, er at kunne lave forskellige plugable look & feels (PLAFs). PLAF vil sige, hvordan GUI'en ser ud og hvordan interaktionen foregår (om return og/eller space aktiverer den knap, som har fokus osv.).
Når et program, lavet med den gamle AWT, blev afviklet på en platform, antog de forskellige GUI-elementer de udseende og den funktionalitet der var givet af den platform de kørte på. Et Java-program der kørte på en Mac, lignede et program på en Mac, et Java-program der kørte i Windows lignede et windowsprogram. Under Swing kan GUI-elementerne i et program der kører på en Mac ligne et Windows-program og omvendt.
Metal, Motif og Windows I realiteten findes der kun tre mulige PLAFs, nemlig dem der er inkluderet i JDK'en. Det drejer sig om Metal, der er en slags fælles PLAF for alle Java-programmer. Hvis man ikke gør andet vil det være den der bruges i de Java-programmer man laver. Metal er et "kunstigt" PLAF, det stammer ikke fra noget kendt operativsystem. Den anden er Motif, som stammer fra UNIX-verdenen, og den tredie er Windows som naturligvis er windows PLAF.
Personligt foretrækker jeg Motif frem for de andre, men det skyldes måske en træthed med Metal og Windows som jeg har set i flere år.
Man kan få en liste over de forskellige PLAFs vha. følgende static metode:
UIManager.LookAndFeelInfo[] UIManager.getInstalledLookAndFeels()
Metoden returnerer et array af objekter der indeholder informationer om PLAFs.
Følgende program viser en liste, som den så ud ved en kørsel på min PC.
Main.java
import javax.swing.*;

public class Main {

  public static void main( String[] argv ) {

    UIManager.LookAndFeelInfo[] list =
      UIManager.getInstalledLookAndFeels();
    
    for ( int i=0; i<list.length; i++ )
      System.out.println( list[i].getName() + ": " + list[i].getClassName() );
  }
}
Metal: javax.swing.plaf.metal.MetalLookAndFeel
CDE/Motif: com.sun.java.swing.plaf.motif.MotifLookAndFeel
Windows: com.sun.java.swing.plaf.windows.WindowsLookAndFeel
getName giver navnet på look & feel og getClassName giver navnet på den klasse, der skal bruges til at realisere den pågældende PLAF. Man ser at Metal er en standard del af Swing, mens de to andre er taget ud for sig, i Suns supplerende packages.
Når man vil bruge en PLAF gør man det ved først at sætte den aktuelle PLAF, med følgende static metode:
void UIManager.setLookAndFeel( String className )
  Som parameter skal angives en af de klassenavne vi så ovenfor.
  Når man har foretaget dette kald med f.eks. Motif, vil alle grafiske elementer der efterfølgende laves i programmet få Motif's PLAF. Hvis man senere skulle ønske at ændre f.eks. en frame og alle dens grafiske komponenter til en anden PLAF, sætter man først PLAF med setLookAndFeel; hvorefter man kalder følgende static metode:
void SwingUtilities.updateComponentTreeUI( Component c )
  Her kan man anføre ethvert grafisk komponent som parametere, f.eks. en frame, og komponentet og alle komponenter der måtte være contained i den vil skifte PLAF til den aktuelle PLAF.
Lad os se et eksempel på de tre PLAFs:
Figur 11:
De tre PLAFs
Metal:
CDE/Motif:
Windows:
Kildeteksten til framen er følgende:
PLAF_Frame.java
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;

public class PLAF_Frame extends JFrame {
  
  public PLAF_Frame( String plaf ) {
    super( "PLAF: " + plaf );
    
    try {
      UIManager.setLookAndFeel( plaf );
    } catch ( Exception e ) {
      System.out.println( "Ukendt PLAF: " + plaf );
    }
    
    getContentPane().setLayout( new FlowLayout() );
    
    JLabel label = new JLabel( "Dette er et label" );
    getContentPane().add( label );
    
    JButton knap = new JButton( "Dette er en knap" );
    getContentPane().add( knap );
    
    JTextField tekstFelt = new JTextField( "Dette er et tekstfelt" );
    getContentPane().add( tekstFelt );
    
    pack();
    setVisible( true );
  }
}
Det er testanvendelsen, som laver de tre frames, med de respektive PLAFs.
Main.java
public class Main {

  public static void main( String[] argv ) {

    new PLAF_Frame( "javax.swing.plaf.metal.MetalLookAndFeel" );
    new PLAF_Frame( "com.sun.java.swing.plaf.motif.MotifLookAndFeel" );
    new PLAF_Frame( "com.sun.java.swing.plaf.windows.WindowsLookAndFeel" );
  }
}
  Efterhånden som du lærer om forskellige grafiske komponenter, så prøv evt. de forskellige look & feels og se hvordan de tager sig ud.