Forrige avsnitt Neste avsnitt  Start på kapittel om grafiske brukergrensesnitt

Layout-managere

Layout-managere bruker vi i og på containere for å plassere komponenter på ønsket måte. Som container har vi hittil bare brukt vinduets contentPane, men mer vanlig er det å gruppere komponenter i andre containere, og så til slutt plassere disse i contentPane. En mye brukt containertype er panel, det vil si objekt av type JPanel. Vinduets forhåndssatte contentPane er også av denne typen.

Når det gjelder layout-managere, finnes det i klassebibliotekets pakke java.awt følgende fem klasser som definerer slike:

  FlowLayout
  BorderLayout
  CardLayout
  GridLayout
  GridBagLayout

I tillegg finnes i pakken javax.swing klassene

  BoxLayout
  SpringLayout
  GroupLayout

Det å skrive for hånd kode for å lage en god og fleksibel layout kan være ganske krevende. Programutviklingsverktøy som Eclipse og NetBeans har innebygget designverktøy som kan brukes for å lage layout. Javakode for layout'en blir da generert automatisk av det verktøyet en bruker. Layouttypen GroupLayout er spesielt laget for slike designverktøy, men det går også an å bruke den når en skriver layoutkode selv. Dersom en skriver layoutkode selv og ikke bruker GroupLayout, er det typen GridBagLayout som er den kraftigste og mest fleksible, men den er også, ved sida av GroupLayout, den som er mest komplisert å sette seg inn i og skrive kode for. Mye av de effektene og den fleksibiliteten som en kan få til ved å bruke GroupLayout eller GridBagLayout, kan en imidlertid få til på en enklere måte ved å bruke BoxLayout på flere forskjellige paneler og bygge et hierarki av slike paneler ved å legge dem inni hverandre.

Vi skal i denne omgang se litt nærmere på hvordan vi kan skrive kode for å bruke de enkle typene FlowLayout, BorderLayout og GridLayout.

FlowLayout

FlowLayout er default-layout for paneler. Det er den enkleste typen layout og er den eneste vi har brukt hittil. Komponentene blir plassert etter hverandre på rader i den rekkefølge vi gjør kall på containerens add-metode for å plassere komponentene. Når en rad er full, blir neste komponent automatisk plassert på en ny rad. Hvor mange komponenter det er plass til på en rad er bestemt av komponentenes bredde og containerens bredde. Når vi oppretter layout-objektet ved å bruke default-konstruktøren (uten parametre), vil vi få default-visning. I denne er hver komponentrad horisontalt sentrert innenfor containeren og det er en avstand på 5 piksler mellom komponentene.

Vi har imidlertid mulighet for å tilpasse default-visningen. Den horisontale plasseringen innenfor hver rad kan vi bestemme ved hjelp av de tre konstantene FlowLayout.LEFT, FlowLayout.CENTER og FlowLayout.RIGHT som gir henholdsvis venstrejustering, sentrering (default-plassering) og høyrejustering. Konstantene kan brukes enten som konstruktørparametre når vi oppretter FlowLayout-objektet, eller de kan brukes som parametre i kall på layout-objektets metode setAlignment. Avstanden mellom komponentene kan vi bestemme enten ved å bruke to ekstra konstruktørparametre (for horisontal og vertikal avstand i antall piksler) i tillegg til parameteren for horisontal justering, eller ved kall på layout-objektets to metoder setHgap og setVgap med ønsket avstand som parameter.

Eksempel

Programmet FlowLayoutFrame.java som er gjengitt nedenfor er en omarbeiding av eksemplet som finnes i Fig. 14.39 i 9. utgave av læreboka til Deitel & Deitel. I programvinduet er det lagt inn tre knapper ved bruk av FlowLayout. Når man klikker på de tre knappene, veksler layouten mellom venstrejustering, sentrering og høyrejustering som vist på bildene nedenfor, der det første bildet viser sentrering. For at vinduet skal bli tegnet ut på nytt med annen horisontal justering som følge av at man klikker på en av disse knappene, er det nødvendig å gjøre kall på layout-objektets metode layoutContainer med vedkommende container som parameter, i vårt tilfelle variabelen container som er en referanse til contentPane. Driverklasse for programmet finnes i fila FlowLayoutDemo.java.

 1 import java.awt.FlowLayout;
 2 import java.awt.Container;
 3 import java.awt.event.ActionListener;
 4 import java.awt.event.ActionEvent;
 5 import javax.swing.JFrame;
 6 import javax.swing.JButton;
 7
 8 //Demonstrating FlowLayout alignments.
 9 public class FlowLayoutFrame extends JFrame
10 {
11    private JButton leftJButton; // button to set alignment left
12    private JButton centerJButton; // button to set alignment center
13    private JButton rightJButton; // button to set alignment right
14    private FlowLayout layout; // layout object
15    private Container container; // container to set layout
16
17    // set up GUI and register button listeners
18    public FlowLayoutFrame()
19    {
20       super( "FlowLayout Demo" );
21
22       layout = new FlowLayout();
23       container = getContentPane();
24       setLayout( layout );
25       Knappelytter lytter = new Knappelytter();
26
27       leftJButton = new JButton( "Left" );
28       add( leftJButton );
29       leftJButton.addActionListener( lytter );
30
31       centerJButton = new JButton( "Center" );
32       add( centerJButton );
33       centerJButton.addActionListener( lytter );
34
35       rightJButton = new JButton( "Right" );
36       add( rightJButton );
37       rightJButton.addActionListener( lytter );
38    } // end FlowLayoutFrame constructor
39
40    private class Knappelytter implements ActionListener
41    {
42      public void actionPerformed( ActionEvent event )
43      {
44        if ( event.getSource() == leftJButton )
45          layout.setAlignment( FlowLayout.LEFT );
46        else if ( event.getSource() == centerJButton )
47          layout.setAlignment( FlowLayout.CENTER );
48        else if ( event.getSource() == rightJButton )
49          layout.setAlignment( FlowLayout.RIGHT );
50
51        // realign attached components
52        layout.layoutContainer( container );
53      }
54    } // end Knappelytter
55 } // end class FlowLayoutFrame

BorderLayout

BorderLayout er default-layout for vinduers forhåndssatte contentPane. Den deler inn container-feltet i fem posisjoner som vist på følgende bilde der det er plassert en knapp i hver posisjon:

Posisjonene angis med de fem BorderLayout-konstantene som er skrevet på knappene ovenfor. I java-versjoner før versjon 1.4 ble posisjonene angitt med konstantene NORTH, SOUTH, WEST, CENTER, EAST. Disse kan fortsatt brukes, men de som er vist på knappene blir nå anbefalt fordi de er standard. De blir dessuten av java-systemet brukt slik at de tilpasser seg språkområder som har andre regler for hva som er starten og slutten på sider og linjer.

Når vi bruker BorderLayout, trenger vi ikke ta i bruk alle de fem posisjonene. Vi kan velge hvor mange vi vil bruke, men har altså maksimalt fem tilgjengelige posisjoner, og vi kan bare plassere én komponent i hver posisjon. Det er imidlertid ingen ting i veien for å plassere paneler (eller andre typer containere) i en eller flere av posisjonene, og i disse panelene kan vi legge inn mange komponenter.

På det viste bildet ser vi at de fem knappene fyller ut hele containeren som de er lagt inn i. Dette er alltid tilfelle når vi bruker BorderLayout, uavhengig av hvor mange av de fem posisjonene vi tar i bruk, med unntak av ett tilfelle: Dersom CENTER-posisjonen er den eneste som ikke er i bruk, så kan det i denne bli et tomt felt. Dersom vi endrer størrelsen på vinduet (eller på containeren som bruker BorderLayout), så vil de plasserte komponentene strekke seg slik at tilgjengelig plass igjen blir fylt opp. Nord- og sør-komponentene kan strekke seg horisontalt, vest- og øst-komponentene kan strekke seg vertikalt, mens senter-komponenten kan strekke seg både horisontalt og vertikalt.

Dersom vi ikke spesifiserer noe annet, vil komponentene bli lagt tett inntil hverandre. Men det er anledning til å spesifisere horisontal og vertikal avstand mellom komponentene, målt i antall piksler. Vi kan enten gjøre det ved hjelp av konstruktørparametre, eller vi kan på layout-objektet bruke metodene setHgap, setVgap. For layouten på det viste bildet er det spesifisert en avstand på 5 piksler mellom komponentene, både horisontalt og vertikalt. Men vi ser at komponentene ikke blir plassert med denne avstanden fra kanten av vinduet, den gjelder bare for avstanden mellom komponentene. (Avstand mot kanten av vinduet er det mulig å få til ved å legge inn en ramme, se notatet Litt om bruk av rammer.)

Når vi skal legge inn en komponent i en container som har BorderLayout, må vi ved kall på containerens add-metode angi hvilken posisjon komponenten skal plasseres i. Det gjør vi ved at vi i tillegg til parameteren for vedkommende komponent har en parameter for posisjonen. Det vil være BorderLayout.CENTER i tilfelle senter-posisjon, og tilsvarende for de andre posisjonene.

Bildet som er vist ovenfor, er hentet fra programmet BorderLayoutFrame.java som er gjengitt nedenfor. Knappene er programmert til å virke på den måten at når man klikker på en knapp, så blir vinduet tegnet ut på nytt, og den knappen som man klikket på blir ikke vist i den nye uttegningen, slik at det bare er fire av de fem plassene i vinduets BorderLayout som er i bruk. Man kan derfor få sett hvordan vinduets utseende endrer seg med hvilken posisjon som ikke er i bruk. Dersom man for eksempel klikker på knappen i posisjon LINE_START, så får man et vindu som ser ut slik:

Programmet er en modifikasjon av det som finnes i Fig. 14.42 i 9. utgave av læreboka til Deitel & Deitel. Driverklasse for programmet finnes i fila BorderLayoutDemo.java.

 1 import java.awt.BorderLayout;
 2 import java.awt.event.ActionListener;
 3 import java.awt.event.ActionEvent;
 4 import javax.swing.JFrame;
 5 import javax.swing.JButton;
 6 import java.awt.Container;
 7
 8 //Demonstrating BorderLayout.
 9 public class BorderLayoutFrame extends JFrame implements ActionListener
10 {
11    private JButton buttons[];
12    private final String names[] = { "Skjul posisjon PAGE_START",
13        "Skjul posisjon PAGE_END", "Skjul posisjon LINE_END",
14        "Skjul posisjon LINE_START", "Skjul posisjon CENTER" };
15    private BorderLayout layout;
16    private Container c;
17
18    // set up GUI and event handling
19    public BorderLayoutFrame()
20    {
21       super( "BorderLayout Demo" );
22
23       layout = new BorderLayout( 5, 5 ); // 5 pixel gaps
24       c = getContentPane();
25       c.setLayout( layout );
26       buttons = new JButton[ names.length ];
27
28       // create JButtons and register listeners for them
29       for ( int count = 0; count < names.length; count++ )
30       {
31          buttons[ count ] = new JButton( names[ count ] );
32          buttons[ count ].addActionListener( this );
33       }
34
35       c.add( buttons[ 0 ], BorderLayout.PAGE_START );
36       c.add( buttons[ 1 ], BorderLayout.PAGE_END );
37       c.add( buttons[ 2 ], BorderLayout.LINE_END );
38       c.add( buttons[ 3 ], BorderLayout.LINE_START );
39       c.add( buttons[ 4 ], BorderLayout.CENTER );
40    } // end BorderLayoutFrame constructor
41
42    // handle button events
43    public void actionPerformed( ActionEvent event )
44    {
45       // check event source and layout content pane correspondingly
46       for ( int i = 0; i < buttons.length; i++ )
47       {
48          if ( event.getSource() == buttons[ i ] )
49             buttons[ i ].setVisible( false ); // hide button clicked
50          else
51             buttons[ i ].setVisible( true ); // show other buttons
52       }
53
54       layout.layoutContainer( c ); // layout content pane
55    } // end method actionPerformed
56 } // end class BorderLayoutFrame

Merknad 1

Et vindus contentPane er som allerede nevnt en container av type JPanel, som altså har FlowLayout som default-layout. Den metoden (createContentPane i klassen JRootPane) som oppretter contentPane, setter BorderLayout på den. For øvrig står vi fritt i selv å bestemme hvilken container som skal være contentPane (ved kall på vinduets metode setContentPane). Det er krav om at contentPane må være en ugjennomsiktig (opaque) JComponent.

Merknad 2

Som vi så av programeksemplet ovenfor, vil knapper som blir lagt direkte inn i en posisjon i en BorderLayout strekke seg slik at de fyller ut hele plassen for vedkommende posisjon i layouten. I mange tilfelle vil knappene da få en form og størrelse som ikke tar seg så godt ut. En bedre løsning vil det da være å legge knappen (eventuelt flere knapper) inn i et panel som vi så plasserer i vedkommende posisjon i BorderLayout'en. Da vil det være panelet som strekker seg, og ikke knappen. På panelet kan vi sette akkurat den layout vi ønsker. I utgangspunktet har det FlowLayout.

GridLayout

GridLayout plasserer komponentene i rader og kolonner som i en tabell. Det vanlige er at vi i konstruktøren spesifiserer hvor mange rader og kolonner vi vil ha:

  new GridLayout( antRader, antKolonner );

Det virker imidlertid på den måten at dersom begge parametre har fått verdier forskjellig fra 0, så blir det spesifiserte antall kolonner ignorert. Antall kolonner vil da bli bestemt av antall rader og det totale antall komponenter som blir lagt inn i layouten. Dersom det for eksempel er spesifisert 3 rader og 2 kolonner, mens 9 komponenter blir lagt inn, så vil disse bli lagt ut i 3 rader og 3 kolonner. Dersom vi ønsker et bestemt antall kolonner, så må parameteren for antall rader settes til 0.

Alle cellene (og dermed alle komponentene som blir lagt inn) i en GridLayout blir gitt samme størrelse. Når vi legger inn komponenter ved bruk av containerens add-metode, vil radene bli fylt opp fra venstre mot høyre rad for rad nedover. Det er tillatt å ha ledige plasser på slutten. Det er også mulig å få tomme plasser innimellom de plasserte komponentene. For å få til det, legger vi inn "fyllkomponenter" som vi gjør usynlige ved en instruksjon av type

  komponent.setVisible( false );

Som i tilfelle BorderLayout vil komponentene ligge tett inntil hverandre, med mindre vi bestemmer noe annet. Det kan vi gjøre enten ved å bruke to ekstra konstruktørparametre for horisontal og vertikal avstand, eller ved bruk av layout-objektets metoder setHgap, setVgap.

Eksempel

Programmet GridLayoutFrame.java som er gjengitt nedenfor, er som eksemplet i Fig. 14.43 i 9. utgave av læreboka til Deitel & Deitel, bortsett fra at noen kommentarer er fjernet. Det blir for vinduet opprettet to forskjellige layouter av type GridLayout, den ene med to rader og tre kolonner, mens det for den andre er omvendt. For den første er det dessuten bestemt en avstand på 5 piksler mellom komponentene, både horisontalt og vertikalt, mens det for den andre brukes default-visning med ingen avstand mellom komponentene. I vinduet blir det lagt inn seks knapper. Knappene er programmert til å virke på den måten at hver gang man klikker på en knapp, så blir vinduet tilordnet den layouten som ikke brukes for øyeblikket, og så blir vinduet tegnet ut på nytt. Det oppnås ved kall på containerens (i dette tilfelle contentPane) metode validate. (Legg merke til at denne metoden er en container-metode, mens metoden layoutContainer som ble brukt i eksemplene på FlowLayout og BorderLayout er en layout-metode.) Veksling mellom de to layoutene får man til ved å bruke den logiske variabelen toggle som en av/på-bryter: Hver gang det blir klikket på en knapp blir variabelens verdi brukt til å avgjøre hvilken layout som nå skal tilordnes og vises, og så blir variabelen gitt sin motsatte verdi, slik at det neste gang blir den andre layouten. De to layoutene er vist i vindusbildene nedenfor. Driverklasse for programmet finnes i fila GridLayoutDemo.java.

 1 import java.awt.GridLayout;
 2 import java.awt.Container;
 3 import java.awt.event.ActionListener;
 4 import java.awt.event.ActionEvent;
 5 import javax.swing.JFrame;
 6 import javax.swing.JButton;
 7
 8 //Demonstrating GridLayout.
 9 public class GridLayoutFrame extends JFrame implements ActionListener
10 {
11    private JButton buttons[];
12    private final String names[] =
13       { "one", "two", "three", "four", "five", "six" };
14    private boolean toggle = true; // toggle between two layouts
15    private Container container;
16    private GridLayout gridLayout1; // first gridlayout
17    private GridLayout gridLayout2; // second gridlayout
18
19    public GridLayoutFrame()
20    {
21       super( "GridLayout Demo" );
22       gridLayout1 = new GridLayout( 2, 3, 5, 5 ); // 2 by 3; gaps of 5
23       gridLayout2 = new GridLayout( 3, 2 ); // 3 by 2; no gaps
24       container = getContentPane();
25       container.setLayout( gridLayout1 );
26       buttons = new JButton[ names.length ];
27
28       for ( int count = 0; count < names.length; count++ )
29       {
30          buttons[ count ] = new JButton( names[ count ] );
31          buttons[ count ].addActionListener( this );
32          container.add( buttons[ count ] );
33       }
34    } // end GridLayoutFrame constructor
35
36    // handle button events by toggling between layouts
37    public void actionPerformed( ActionEvent event )
38    {
39       if ( toggle )
40          container.setLayout( gridLayout2 ); // set layout to second
41       else
42          container.setLayout( gridLayout1 ); // set layout to first
43
44       toggle = !toggle; // set toggle to opposite value
45       container.validate(); // re-layout container
46    } // end method actionPerformed
47 } // end class GridLayoutFrame

Gruppering av komponenter ved hjelp av paneler

Et panel er definert av klassen JPanel, som er subklasse til JComponent. Den er i sin tur subklasse til Container. Det betyr at et panel både er en container og en komponent. Det er derfor tillatt å plassere paneler inni paneler. På paneler kan vi sette både de tre layoutene som vi har sett nærmere på ovenfor, og flere andre. Og på de forskjellige panelene som vi legger inni hverandre kan vi bruke forskjellige layouter og ha forskjellig antall komponenter i hvert panel. Dette gir stor fleksibilitet for plassering av komponenter.

Som allerede nevnt, så har paneler FlowLayout som default-layout. Det vil si at når vi oppretter et panel ved å bruke default-konstruktør:

  JPanel panel = new JPanel();

så vil panelet ha FlowLayout. Men vi kan lett sette en hvilken som helst annen layout på panelet ved å bruke metoden setLayout med ønsket layout-objekt som aktuell parameter, eller ved å bruke ønsket layout som konstruktørparameter, som i eksemplet

  JPanel panel = new JPanel(new BorderLayout()); //å foretrekke!

Det siste alternativet er av effektivitetshensyn absolutt å foretrekke, for da unngår vi at det blir opprettet et FlowLayout-objekt som likevel ikke skal brukes.

Dette opplegget fungerer imidlertid ikke dersom vi ønsker å sette BoxLayout, for bruk av den forutsetter at det allerede eksisterer en container, se notat om bruk av BoxLayout.

Paneler kan også brukes som egne tegneflater, slik at vi kan unngå at uttegning av bilder og annen grafikk kommer i konflikt med andre komponenter. Hvordan dette gjøres, har vi sett på tidligere, se Panel brukt som tegneflate.

Plassere komponenter uten å bruke layout-manager: absolutt posisjonering

Det er mulig å plassere komponenter i en container uten å gjøre bruk av layout-manager, men dette blir ikke anbefalt. Grunnen er at en layout-manager gjør det lettere å tilpasse størrelse og utseende på komponenter til forskjellige plattformer og omgivelser, til forskjellige fontstørrelser, til varierende størrelse og oppløsning på containere og vinduer, og til forskjellige lokaliteter. Dessuten kan layout-managere brukes om igjen av andre containere, og også av andre programmer.

Dersom du ønsker å bruke absolutt posisjonering så finnes det nærmere beskrivelse av framgangsmåten for dette i The Java Tutorials.

Copyright © Kjetil Grønning, revidert av Eva Hadler Vihovde 2014

Forrige avsnitt Neste avsnitt  Start på kapittel om grafiske brukergrensesnitt