10 Grafiske brugergrænseflader

Indhold:



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.


10.1 Generering 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).


  1. 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).

  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).

  3. 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).

  4. 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?".

  5. 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.


10.1.1 Interaktive programmer

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å:


10.2 Komponenter

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.


10.2.1 Button


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()


10.2.2 Checkbox


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()


10.2.3 Choice


En valgliste. Brug metoden add(String elementnavn) til at tilføje indgange.

Med getSelectedItem() undersøger man, hvad brugeren har valgt.


10.2.4 TextField


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()



10.2.5 TextArea


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).


10.2.6 Label


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()


10.2.7 List


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()


10.2.8 Canvas

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.


10.3 Eksempel

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);
  }
}


10.4 Containere

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);

10.4.1 Panel

Et panel er den simpleste og oftest brugte container. Den indeholder simpelt hen komponenterne (i henhold til layoutmanageren).

10.4.2 Applet

En applet er et udvidet panel, der er beregnet til at vise i en browser. Læs kapitlet om appletter for mere information.

10.4.3 Window

Window repræsenterer et vindue på skærmen. Det bruges meget sjældent direkte, man bruger i stedet arvningerne Frame og Dialog.

10.4.4 Frame

En Frame er den simpleste og oftest brugte måde at definere et "normalt" vindue med en titel.

10.4.5 Dialog

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().


10.5 Relationer mellem klasserne

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.


10.6 Layout-managere

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.

10.6.1 FlowLayout

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.

10.6.2 BorderLayout

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.

10.6.3 GridBagLayout

GridBagLayout lægger komponenterne efter et usynligt gitter.



10.7 Test dig selv (fjernet)

Dette afsnit findes i den trykte bog


10.8 Resumé (fjernet)

Dette afsnit findes i den trykte bog


10.9 Opgaver

10.9.1 Matadorspillet grafisk

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.

Vink

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.


  1. 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.

  2. 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).

  3. Lav en tur-knap, som spiller en runde.

  4. 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).

  5. Løb igennem alle felter, og tegn dem i paint().

  6. Udbyg derefter spillet efter egen smag.


Flere vink

Det er bedst at du bruger hovedet og kun ser på dem hvis du er gået i stå.


  1. 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.

  2. 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()!

  1. 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).

  2. 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)) 
      ); 
    }


  1. 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.


  1. Find selv på flere ting:


10.10 Avanceret (fjernet)

Dette afsnit findes i den trykte bog



Jacob Nordfalk - Objektorienteret programmering i Java - http://javabog.dk