| 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 ) |
|
| på 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:
|
|
|
| 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.
|