Indhold:
Design af en grafisk brugergrænseflade med et værktøj
De vigtigste grafiske komponenter og deres egenskaber
Containere og layout-managers
Større opgave: Matador-spillet som en applet
Forudsættes af kapitel 12, Hændelser.
Kapitlet forudsætter kapitel 9, Appletter og grafik, og at du har adgang til et værktøj, der kan udvikle grafiske brugergrænseflader (f.eks. Borland JBuilder, Oracle JDeveloper, Sun Forte eller IBM VisualAge for Java). Den større opgave forudsætter kapitel 5, Nedarvning.
Når man skal lave en grafisk brugergrænseflade, gøres det ofte ved at anvende standardkomponenter. Vi vil starte med at se på, hvordan det gøres i praksis med et værktøj.
Med moderne udviklingsværktøjer kan man udarbejde en grafisk brugergrænseflade ud fra standardkomponenter på ret kort tid. Herunder er beskrevet, hvordan man gør i Borland JBuilder. JDeveloper har helt de samme muligheder og udseende. Hvis du bruger et andet værktøj, må du prøve dig lidt frem. Ideerne er de samme, og koden, der genereres, ligner også nogenlunde, men menuerne og knapperne varierer selvfølgelig noget.
Tag en eksisterende applet, f.eks. MinApplet fra kapitlet om appletter, og føj den til et projekt. Hvis du i stedet vil oprette en ny, så vælg "New.." og Applet. Fjern pakkenavnet, skriv et navn på din klasse, vælg superklasse ("base class") Applet, og klik "Finish". Definér evt. en paint()-metode, der tegner noget (hvis du bruger et andet værktøj end JBuilder, så find menuerne til at oprette en ny applet, og gør det).
Gå over på Design-fanen (ved punkt 1 nederst).
Den er delt op i en del, hvor du designer din brugergrænseflade
til venstre, og en tabel af egenskaber til højre (punkt 2).
Her skal du først ændre layout fra "<default
layout>" til "null" (punkt 2 til højre;
måske skal du klikke på den grå flade i designeren
først).
Nu kan du gå i gang med at lægge komponenter ind
på grænsefladen.
Vælg i første omgang
at arbejde med AWT-komponenter (punkt 3).
Vælg først en Label (det store A ved punkt 4),
og klik på grænsefladen. Der dukker en mærkat med
en tekst op. På egenskabstabellen til højre kan du
ændre dens variabelnavn (name
øverst) til f.eks. labelHvadErDitNavn.
Længere nede
er egenskaben text,
der bestemmer, hvad der skal stå på mærkaten. Ret
den til f.eks. "Hvad er dit navn?".
Indsæt derefter et TextField (et indtastningsfelt
-punkt 5).
Ret variabelnavnet til textFieldNavn og teksten til
f.eks. "Jacob".
Gå tilbage til Source-fanen. Nu ser kildeteksten således ud:
import java.awt.*; import java.applet.*; public class MinApplet extends Applet { Label labelHvadErDitNavn = new Label(); TextField textFieldNavn = new TextField(); public void paint(Graphics g) { // Herunder referer g til et Graphics-objekt man kan tegne med. g.drawLine(10,10,50,70); g.fillOval(5,5,300,50); g.setColor(Color.green); g.drawString("Hej grafiske verden!",100,30); } public MinApplet() { try { jbInit(); } catch(Exception e) { e.printStackTrace(); } } private void jbInit() throws Exception { labelHvadErDitNavn.setText("Hvad er dit navn?"); labelHvadErDitNavn.setBounds(new Rectangle(15, 69, 108, 15)); textFieldNavn.setText("Jacob"); textFieldNavn.setBounds(new Rectangle(141, 61, 112, 29)); this.setLayout(null); this.add(textFieldNavn, null); this.add(labelHvadErDitNavn, null); } }
De to objekter, vi satte på i designeren, er erklæret og oprettet øverst uden for metoderne:
Label labelHvadErDitNavn = new Label(); TextField textFieldNavn = new TextField();
Nedenunder står vores gamle paint() uændret. Herunder er der oprettet en konstruktør, der kalder metoden jbInit(). Den andet kode, 'try{ ... } catch (Exception e) {...}', er beregnet til at håndtere undtagelser, og vil blive forklaret senere i kapitel 13, Undtagelser.
I metoden jbInit() nedenunder lægger JBuilder (og JDeveloper) al sin programkode. Man ser her, hvordan både Label og TextField har metoden setText(), og begge objekter får kaldt denne metode (svarende til, at vi ændrede egenskaben text).
labelHvadErDitNavn.setText("Hvad er dit navn?"); textFieldNavn.setText("Jacob");
De andre kommandoer i jbInit() sørger for at placere komponenterne korrekt på appletten.
"Design"- og "Source"-fanen i JBuilder (og JDeveloper) er to måder at se programmet på, og man kan frit skifte mellem dem. Laver man en designændring, vil det blive afspejlet i koden og omvendt. Prøv selv.
Lad os nu tilføje en knap og et indtastningsfelt på flere linier (TextArea). Jeg kalder dem for buttonOpdater og textAreaHilsen. Knappen skal selvfølgelig gøre noget. Fra Design-fanen, dobbeltklik på knappen, og vupti! Der genereres automatisk en metode til at håndtere et klik:
void buttonOpdater_actionPerformed(ActionEvent e) { }
Hvis du kigger i jbInit(), kan du se, at JBuilder har indsat følgende kode:
buttonOpdater.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { buttonOpdater_actionPerformed(e); } });
Det er disse linier, der sørger for at "lytte efter hændelser" på knappen, sådan at når man klikker på buttonOpdater, så kaldes metoden buttonOpdater_actionPerformed(). Det vil vi komme tilbage til i kapitel 12, Hændelser.
Nu kan du indsætte kode, der udfører en handling. Skriv f.eks. noget ud til systemoutput:
void buttonOpdater_actionPerformed(ActionEvent e) { System.out.println("Opdater!"); }
Vi kunne også lave noget sjovere, f.eks. læse den indtastede tekst fra textFieldNavn og skrive den i textAreaHilsen. JBuilder har lavet koden, der sætter teksterne for os, og ved at studere den kan man få en ide til, hvordan det skal gøres:
String navn = textFieldNavn.getText(); // aflæs navnet textAreaHilsen.setText("Hej kære "+navn); // sæt navnet
Her kommer det fulde eksempel. paint() er ændret til også at tegne navnet 5 gange.
import java.awt.*;
import java.applet.*;
import java.awt.event.*;
public class MinAppletFaerdig extends Applet
{
Label labelHvadErDitNavn = new Label();
TextField textFieldNavn = new TextField();
Button buttonOpdater = new Button();
TextArea textAreaHilsen = new TextArea();
public MinAppletFaerdig()
{
try {
jbInit();
}
catch(Exception e) {
e.printStackTrace();
}
}
public void paint(Graphics g)
{
g.drawLine(10,10,50,70);
g.fillOval(5,5,300,50);
g.setColor(Color.green);
String navn = textFieldNavn.getText();
for (int i=0; i<50; i=i+10)
g.drawString("Hej "+navn+" !",100+i,30+i);
}
private void jbInit() throws Exception {
labelHvadErDitNavn.setText("Hvad er dit navn?");
labelHvadErDitNavn.setBounds(new Rectangle(15, 69, 108, 15));
textFieldNavn.setText("Jacob");
textFieldNavn.setBounds(new Rectangle(129, 61, 95, 29));
buttonOpdater.setLabel("opdater!");
buttonOpdater.setBounds(new Rectangle(231, 60, 91, 32));
buttonOpdater.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(ActionEvent e) {
buttonOpdater_actionPerformed(e);
}
});
textAreaHilsen.setText("Her kommer en tekst...");
textAreaHilsen.setBounds(new Rectangle(6, 102, 316, 78));
this.setLayout(null);
this.add(labelHvadErDitNavn, null);
this.add(textAreaHilsen, null);
this.add(buttonOpdater, null);
this.add(textFieldNavn, null);
}
void buttonOpdater_actionPerformed(ActionEvent e) {
String navn = textFieldNavn.getText();
System.out.println("Opdater! navn="+navn);
textAreaHilsen.setText("Hej kære "+navn);
repaint(); // gentegn appletten
}
}
Her har vi tastet "Jacob Nordfalk" ind og trykket på "opdater!"-knappen:
Der er altså to måder at arbejde med grafik på:
I paint() tegner man "i hånden" ved at give kommandoer til et Graphics-objekt.
Ved at bruge grafiske standardkomponenter.
Komponenter er objekter, der bruges som en synlig del af en grafisk brugergrænseflade, f.eks. knapper, valglister, indtastningsfelter, mærkater.
Alle komponenter arver fra Component-klassen og har derfor dennes træk til fælles:
Metoderne setForeground(Color c) og setBackground(Color c) sætter farven hhv. baggrundsfarven for komponenten, svarende til egenskaberne foreground og background. Egenskaberne kan aflæses med getForeground() og getBackground().
En anden egenskab er font, der bestemmer skrifttypen. I tråd med de andre egenskaber sættes den med setFont(Font f) og aflæses med getFont().
Dette kan sammenfattes i en tabel over egenskaber, der er fælles for alle komponenter.
Egenskab |
Type |
Sættes med |
Læses med |
---|---|---|---|
foreground |
Color |
setForeground(Color c) |
getForeground() |
background |
Color |
setBackground(Color c) |
getBackground() |
font |
Font |
setFont(Font f) |
getFont() |
visible |
boolean |
setVisible(boolean synlig) |
isVisible() |
Nedenfor vil de mest almindelige komponenter blive beskrevet. Kun de nye egenskaber er beskrevet.
En
trykknap. Egenskaben label angiver, hvad der står på
knappen.
Egenskab |
Type |
Sættes med |
Læses med |
---|---|---|---|
label |
String |
setLabel(String t) |
getLabel() |
Et
afkrydsningsfelt. Kan både bruges til flueben og til
radioknapper. Hvis man skal have radioknapper (der gensidigt
udelukker hinanden), skal objekterne knyttes sammen af et
CheckboxGroup-objekt.
label angiver, hvad der står ved feltet. state angiver, om feltet er afkrydset.
Egenskab |
Type |
Sættes med |
Læses med |
---|---|---|---|
label |
String |
setLabel(String t) |
getLabel() |
state |
boolean |
setState (boolean afkrydset) |
getState() |
En
valgliste. Brug metoden add(String elementnavn) til at tilføje
indgange.
Med getSelectedItem() undersøger man, hvad brugeren har valgt.
Et
indtastningsfelt på en linie. Egenskaben text angiver,
hvad der står i feltet.
Mindre brugt er: columns angiver, hvor bredt feltet skal være.
editable angiver, om brugeren kan redigere teksten i indtastningsfeltet.
echoChar bruges til felter, der skal skjule det indtastede, typisk adgangskoder.
Sæt f.eks. echoChar til '*' for at få vist stjerner i stedet for det indtastede.
Egenskab |
Type |
Sættes med |
Læses med |
---|---|---|---|
text |
String |
setText(String t) |
getText() |
editable |
boolean |
setEditable(boolean rediger) |
isEditable() |
columns |
int |
setColumns(int bredde) |
getColumns() |
echoChar |
char |
setEchoChar(char tegn) |
getEchoChar() |
Et
indtastningsfelt på flere linier.
Egenskaberne text, rows og columns angiver, hvad der står i feltet, hhv. bredde og højde.
Egenskab |
Type |
Sættes med |
Læses med |
---|---|---|---|
text |
String |
setText(String t) |
getText() |
editable |
boolean |
setEditable(boolean rediger) |
isEditable() |
columns |
int |
setColumns(int bredde) |
getColumns() |
rows |
int |
setRows(int højde) |
getRows() |
TextField og TextArea har en del egenskaber til fælles, og disse fællestræk ligger i superklassen TextComponent (se klassediagrammet).
En
mærkat der viser en tekst (som brugeren ikke kan redigere i).
Egenskaben text angiver, hvad der står i feltet.
Egenskab |
Type |
Sættes med |
Læses med |
---|---|---|---|
text |
String |
setText(String t) |
getText() |
En
menu, hvor flere af indgangene kan ses samtidigt, og hvor man kan
vælge en eller flere elementer. Brug metoden add(String
elementnavn) til at tilføje indgange.
Med isIndexSelected(int index) undersøger man, om en indgang er valgt.
Egenskaberne rows og multipleMode angiver, hvad højden er, og om man kan vælge flere indgange samtidigt.
Egenskab |
Type |
Sættes med |
Læses med |
---|---|---|---|
rows |
int |
setRows(int højde) |
getRows() |
multipleMode |
boolean |
setMultipleMode(boolean m) |
getMultipleMode() |
Et tegne-område. Canvas er lidt besværlig, idet man skal nedarve fra klassen og implementere paint(Graphics g) for at kunne tegne noget.
En lettere (men ikke nødvendigvis altid smartere) måde er som sagt at definere paint()-metoden direkte i appletten/vinduet som vi har gjort tidligere.
Herunder et eksempel (genereret med JBuilder), der viser komponenterne omtalt i forrige afsnit. På billedet ses det resulterende skærmbillede under Windows (sidst i kapitlet ses, hvordan det ser ud under Linux).
import java.awt.*; import java.applet.*; public class GrafiskeKomponenter extends Applet { // opret alle komponenterne og husk dem i nogle objektvariabler Button button1 = new Button(); Checkbox checkbox1 = new Checkbox(); Checkbox checkbox2 = new Checkbox(); Checkbox checkbox3 = new Checkbox(); Checkbox checkbox4 = new Checkbox(); Checkbox checkbox5 = new Checkbox(); CheckboxGroup checkboxGroup1 = new CheckboxGroup(); Choice choice1 = new Choice(); TextField textField1 = new TextField(); TextArea textArea1 = new TextArea(); List list1 = new List(); Label label1 = new Label(); // JBuilder og JDeveloper definerer metoden jbInit() hvor de // initialiserer komponenterne. Det kunne dog lige så godt // ligge direkte i init() public void init() { try { jbInit(); } catch(Exception e) { e.printStackTrace(); } } // initialisering af komponenterne med deres startværdier private void jbInit() throws Exception { button1.setLabel("OK"); checkbox1.setLabel("En"); // Sæt afkrydsningsfelternes navne checkbox2.setLabel("To"); checkbox3.setLabel("Tre"); checkbox4.setLabel("Radio1"); // Sæt radioknappernes navne og checkbox5.setLabel("Radio2"); checkbox4.setCheckboxGroup(checkboxGroup1); // gruppen de tilhører checkbox5.setCheckboxGroup(checkboxGroup1); checkboxGroup1.setSelectedCheckbox(checkbox4); choice1.add("Choice Rød"); choice1.add("Choice Grøn"); choice1.add("Choice Blå"); textField1.setColumns(10); textField1.setText("Et TextField"); textArea1.setColumns(15); textArea1.setRows(5); textArea1.setText("Et TextArea"); label1.setText("En Label"); list1.add("List Rød"); list1.add("List Grøn"); list1.add("List Blå"); this.add(button1, null); // til sidst skal komponenterne føjes this.add(checkbox1, null); // til containeren (se senere) this.add(checkbox2, null); this.add(checkbox3, null); this.add(checkbox4, null); this.add(checkbox5, null); this.add(choice1, null); this.add(textArea1, null); this.add(textField1, null); this.add(label1, null); this.add(list1, null); } }
En container er beregnet til at indeholde komponenter. De arver alle fra Container-klassen og har alle en såkaldt layout manager tilknyttet, der bestemmer, hvor og med hvilken størrelse komponenterne skal vises i containeren.
Før en komponent bliver vist, skal den tilføjes en container. I eksemplet ovenfor er appletten den container, komponenterne bliver tilføjet, og derfor står der sidst i initialiseringen:
this.add(button1, null);
Et panel er den simpleste og oftest brugte container. Den indeholder simpelt hen komponenterne (i henhold til layoutmanageren).
En applet er et udvidet panel, der er beregnet til at vise i en browser. Læs kapitlet om appletter for mere information.
Window repræsenterer et vindue på skærmen. Det bruges meget sjældent direkte, man bruger i stedet arvningerne Frame og Dialog.
En Frame er den simpleste og oftest brugte måde at definere et "normalt" vindue med en titel.
Dialog bruges til dialog-bokse, vinduer, der dukker op med et eller andet spørgsmål, som skal besvares, før man kan gå videre. Egenskaben modal angiver, om dialog-boksen er modal, dvs. om man skal lukke den før man kan få adgang til ejer-vinduet. Den sættes med setModal(boolean m) og aflæses med isModal().
Herunder ses klassediagrammet for nogle komponenter og containere.
De hule pile repræsenterer er-en-relationer (dvs. nedarvning).
De andre pile repræsenterer har-relationer (dvs. at et objekt har en reference til et andet objekt, evt. 'ejer' objektet).
Bemærk, at Container selv arver fra Component, så en Container kan i sig selv bruges som en komponent. Det er relevant for Panel og ScrollPane, der er beregnet til at blive placeret inden i andre containere.
En layout manager styrer layoutet af komponenterne på et Panel eller en anden container. Alle containere har egenskaben layout, der kan sættes med metoden setLayout(Layout l).
Bruges et grafisk udviklingsværktøj, er det mest bekvemt at sætte layoutmanageren til null, der tillader udvikleren at sætte komponenterne, som han vil på en hvilken som helst (x,y)-position og med en hvilken som helst højde og bredde. Layoutet tager slet ikke højde for vinduets størrelse, så hvis vinduet bliver for lille, vil nogle af komponenterne ikke blive vist.
FlowLayout placerer komponenterne ligesom bogstaver: Øverst fra venstre mod højre og på en ny linie nedenunder, når der ikke er mere plads.
Angiver man ikke nogen anden layout-manager, vil FlowLayout blive brugt.
BorderLayout tager højde for vinduets størrelse og tilpasser komponenternes størrelse efter den tilgængelige plads. Komponenterne kan placeres på 5 mulige positioner, nemlig NORTH, SOUTH, EAST, WEST og CENTER.
Den mest almindelige måde at lave det grafiske layout af et skærmbillede er med BorderLayout. I de områder, hvor man ønsker at placere flere komponenter, sætter man først et Panel, og komponenterne tilføjes så panelet.
GridBagLayout lægger komponenterne efter et usynligt gitter.
Dette afsnit findes i den trykte bog
Dette afsnit findes i den trykte bog
Denne opgave kræver, at du har læst kapitel 5, Nedarvning og 9, Appletter og grafik.
Lav matadorspillet om til at kunne vises grafisk i en applet. Der skal som minimum være en knap, som spiller en omgang.
Når du skal programmere, så vær systematisk, og del opgaven op i små delopgaver. Løs en delopgave ad gangen, og afprøv, at det fungerer, før du går videre.
Hent kildeteksten til matadorspillet (version 2: Felt.java, Gade2.java, Grund2.java, Helle.java, Rederi2.java, Start.java, Spiller.java og SpilMatador.java ændret til at bruge Gade2 og Rederi2), og prøv det.
Genbrug MinApplet ovenfor. Husk at initialisering skal ske i init()-metoden eller i konstruktøren. De variabler der skal deles mellem flere metoder, skal være objektvariabler (lokale eksisterer jo kun i den metode de er defineret i).
Lav en tur-knap, som spiller en runde.
Føj en metode til Felt, der tegner feltet. Hvert felt skal også have en position (den er en del af initialiseringen, så sæt den fra init()-metoden).
Løb igennem alle felter, og tegn dem i paint().
Udbyg derefter spillet efter egen smag.
Det er bedst at du bruger hovedet og kun ser på dem hvis du er gået i stå.
Prøve programmet
Har du ikke allerede i sidste
lektion prøvet matadorspillet, så prøv at køre
programmet. Derefter er det naturligvis meget lettere at lave
en grafisk udgave! Brug trinvis gennemgang (trace into/step over),
indtil du føler, du har forstået programkoden. Først
da er du klar til at prøve i en applet.
Struktur i en applet
Opret en applet, eller genbrug
MinApplet ovenfor. Flyt initialiseringen fra SpilMatador.java
til init()-metoden eller konstruktøren. Husk at importere
java.util.* øverst for at få adgang til Vector-klassen.
Variablerne felter, sp1, sp2 skal nu være objektvariabler
(før var de lokale variabler), for at de kan ses i resten af
appletten:
import java.awt.*; import java.applet.*; import java.util.*; public class MatadorApplet extends Applet { // objektvariabler: Spiller sp1=new Spiller("Søren",50000,Color.green); // opret spiller 1 Spiller sp2=new Spiller("Gitte",50000,Color.yellow); // opret spiller 2 Vector felter=new Vector(); public MatadorApplet() // eller "public void init()" { felter.addElement(new Start(5000)); felter.addElement(new Gade2("Gade 1",10000, 400,1000)); felter.addElement(new Gade2("Gade 2",10000, 400,1000)); //... osv.
Husk, at appletten først tegner noget, når
initialiseringen er færdig, så hvis du f.eks. kører
20 runder i initialiseringen, tager det lang tid, førend
systemet når til at kalde paint()!
Definér en tur-knap
For at få spillet til at
køre kan du lave en knap. Når brugeren trykker på
knappen, så kald spillernes tur()-metode.
(Alternativ:
kald spillernes tur()-metode inde i paint() og afslut paint() med:
repaint(1000); dette får systemet til at kalde paint() igen
efter et sekund).
Hvert felt skal have en position. Føj en position (af typen Point) til Felt-klassen:
import java.awt.*; public class Felt { String navn; Point position = new Point();
og definér metoden tegn(Graphics g) på Felt, der tegner feltets navn på positionen:
public void tegn(Graphics g) { g.setColor(Color.black); g.drawString(navn,position.x,position.y); }
Husk at importere java.awt.* øverst for at få adgang til Point- og Graphics-klassen.
Løb alle felterne igennem i init(), og sæt koordinaterne på felterne:
felter.addElement(new Gade2("Gade 8",20000,1100,2000)); felter.addElement(new Gade2("Gade 9",30000,1500,2200)); for (int i=0; i<felter.size(); i++) { double v = Math.PI*2*i/felter.size(); // vinkel i radianer Felt f = (Felt) felter.elementAt(i); f.position = new Point( 100 + (int) (100*Math.cos(v)), 110 + (int) (100*Math.sin(v)) ); }
Definér applettens paint()-metode til at kalde felternes tegn() for at tegne brættet:
public void paint(Graphics g) { for (int i=0; i<felter.size(); i++) { Felt f = (Felt) felter.elementAt(i); f.tegn(g); }
En grund skal også have tegnet ejeren nedenunder, så den
skal have en anderledes tegn(). Definér tegn() i Grund2.
En
gade skal også vise antallet af huse. Definér også
tegn() i Gade2.
Find selv på flere ting:
Lav tekstfelter, der beskriver hver spillers beholdning.
Tegn spillernes biler på skærmen .
Automatisk spil
(vink: kald spillernes tur()-metode inde
i paint(). Start paint() med: repaint(1000); dette får
systemet til at kalde paint() igen efter et sekund).
Dette afsnit findes i den trykte bog