Forrige avsnitt Neste avsnitt Start på kapittel om grafiske brukergrensesnitt

Listebokser

En listeboks - JList<E>-objekt - kan brukes til å velge ett eller flere alternativer fra en gruppe. Det vanligste er at alternativene vises i listeboksen i form av tekststrenger, men en kan ha lister av hvilke som helst typer objekter. Det mest aktuelle utenom tekststrenger, er ikoner. Blir det brukt andre typer objekter, kreves det ekstra programmering for å bestemme hvordan objektene skal vises på skjermen. (Tilsvarende gjelder for kombobokser.) Følgende bilde viser hva vi får dersom det blir brukt en listeboks istedenfor en komboboks til å velge bilde med i programeksemplet som ble brukt i notatet om kombobokser. Programkoden for det nye vinduet kommer vi tilbake til etter å ha tatt for oss en del generelt om bruken av listebokser.

Fra og med javaversjon 7 er JList<E>-klassen definert som en parametrisert klasse. Fordelen med dette er stort sett at man slipper litt typekonvertering i forbindelse med programmering av lytteobjekt, samt at det foretas mer typekontroll under kompilering. Det går fortsatt an å bruke klassen uten typeparameter, men en metode som da er aktuell å bruke for flervalgslistebokser, er nå markert som foreldet. Som erstatning er det kommet til en ny metode. Dette blir kommentert nærmere under omtalen av slike listebokser nedenfor. I eksemplene i det følgende blir det brukt typeparameter.

For å opprette en listeboks med String-alternativer, kan vi gjøre slik (uttrykt i pseudo-kode):

  String[] alternativer = { ... };
  JList<String> listeboks = new JList<>(alternativer);

eller

  JList<String> listeboks = new JList<>();
  listeboks.setListData( alternativer );

Som det ble beskrevet under omtalen av MVC-arkitektur, er det slik at listeinnholdet (og innholdet til komponenter generelt) blir lagret i en såkalt modell, i dette tilfelle en listemodell. For de enkle komponentene som vi har brukt hittil har det ikke vært nødvendig å jobbe direkte på modellen, men det må vi faktisk gjøre for å kunne programmere listebokser, iallfall dersom det dreier seg om noe som går utover det aller enkleste. Første eksemplet på det får vi når vi skal ta for oss flervalgslistebokser nedenfor. Når vi oppretter listebokser som forklart ovenfor, så vil det automatisk bli generert en listemodell som det seinere ikke går an å endre på. Dersom vi ønsker å kunne endre innholdet, må vi opprette vår egen listemodell. Det blir gjort i eksemplet om flervalgslistebokser nedenfor.

Siden vi for listebokser må jobbe direkte mot modellen når det gjelder innholdet, så finnes det for listebokser heller ingen metode addItem, slik som det ble nevnt for kombobokser.

Vi kan bestemme hvor mange alternativer som skal være synlige samtidig i en listeboks:

  listeboks.setVisibleRowCount( antall );

der int-parameteren antall angir antallet. Default-verdi er 8.

Dersom det er flere alternativer enn antall synlige samtidig, bør listeboksen være utstyrt med skrollefelt. Det blir ikke gjort automatisk, slik det er tilfelle med kombobokser. Enklest er det å legge listeboksen inn i en JScrollPane:

  JScrollPane skrolleliste = new JScrollPane( listeboks );

Det vil i så fall være skrollelista vi må add'e til containeren som skal inneholde listeboksen.

Metoden setSelectionMode brukes til å bestemme om lista av alternativer i listeboksen skal være envalgsliste eller flervalgsliste:

  listeboks.setSelectionMode( type );

der parameteren type kan ha én av følgende verdier:

  ListSelectionModel.SINGLE_SELECTION
  ListSelectionModel.SINGLE_INTERVAL_SELECTION
  ListSelectionModel.MULTIPLE_INTERVAL_SELECTION

Default er flervalgsliste av den sist nevnte typen. Den midterste verdien gir en listeboks der flere alternativer kan velges i et sammenhengende intervall. Brukes den siste verdien, får vi en listeboks der de valgte alternativene kan ligge vilkårlig. Nærmere detaljer angående bruk av flervalgslister skal vi se på seinere.

Hendelseshåndtering

JList-objekter genererer hendelser av type ListSelectionEvent. Disse kan fanges opp av et lytteobjekt av type ListSelectionListener. Dette vil gjøre kall på metoden

  public void valueChanged( ListSelectionEvent e ) { ... }

For å definere et slikt lytteobjekt må vi derfor skrive en klasse som implementerer interface ListSelectionListener ved at den gir en definisjon av den nevnte metoden. Lytteobjektet knyttes til JList-objektet ved kall på metoden

  addListSelectionListener( < lytteobjekt > );

Museklikk på et alternativ i listeboksen genererer faktisk tre hendelser av type ListSelectionEvent: Når museknappen går ned, er det én hendelse for å indikere at det gamle alternativet ble av-valgt og én for å indikere at det nye ble valgt. På begge disse vil

  e.getValueIsAdjusting()

(der e er parameteren i valueChanged) returnere true som indikasjon på at valget ikke er fullført ennå. Når museknappen slippes opp, genereres den tredje hendelsen. Da vil e.getValueIsAdjusting() returnere false for å indikere at valget er fullført.

JList-klassen har ingen forhåndsprogrammert funksjonalitet for behandling av doble museklikk. Men det er ikke så vanskelig å programmere dette på egen hånd. Et eksempel finnes under dokumentasjonen av JList-klassen. Vi skal også se på egne eksempler på dette seinere.

I tilfelle énvalgsliste kan vi finne ut hvilket listealternativ som ble valgt ved å gjøre kall på metoden getSelectedValue. Returen fra denne er av den typen som er brukt som aktuell typeparameter, i tilfelle det blir brukt typeparameter. Dersom det ikke er brukt typeparameter, vil returen være av type Object og må derfor i så fall konverteres til riktig type. I tilfelle String-alternativer og String er brukt som aktuell typeparameter kan vi derfor skrive, når e er ListSelectionEvent-objektet:

  JList<String> liste = (JList) e.getSource();
  String alternativ = liste.getSelectedValue();
  ...

Som i kombobokser er alternativene indeksert fra 0 og oppover, slik at vi også kan få returnert indeksen for det valgte alternativ ved kall på listeobjektets metode getSelectedIndex. For JList-klassen finnes det imidlertid ingen metode svarende til metoden getItemAt(indeks) som vi har for kombobokser. Og grunnen til dette er igjen at vi for listebokser må jobbe direkte mot listemodellen når det gjelder innholdet.

Programeksempel, énvalgs listeboks

Programmet med vindusklasse Listeboksdemo.java, som er gjengitt nedenfor, er en omarbeidet versjon av komboboks-programmet for valg av hvilken bilde-label som skal vises. Et bilde av programvinduet ble vist ovenfor. Merk deg hvordan listeboksen og lytteobjektet for denne er programmert. Instruksjonen

  dyrevelger.setFixedCellWidth( 50 );

sikrer at listeboksen i utgangspunktet skal bli synlig på skjermen. Uten denne blir cellenes bredde beregnet på grunnlag av hva de inneholder. Når de ikke inneholder noe, vil dette resultere i 0. Det finnes en tilsvarende metode for å sette høyde på cellene i listeboksen. Den heter setFixedCellHeight. For begge metodene blir vedkommende dimensjon satt til det antall pixler som parameteren angir.

Driverklasse for programmet finnes i fila Listebokstest.java. Når du kjører programmet, bør du spesielt legge merke til hva som skjer og ikke skjer når museknappen blir trykket ned og når den blir sluppet opp igjen.

 1 import java.awt.*;
 2 import javax.swing.*;
 3 import javax.swing.event.*;
 4
 5 public class Listeboksdemo extends JFrame
 6 {
 7   JLabel bilde;
 8
 9   public Listeboksdemo()
10   {
11     super( "Listedemo" );
12     String[] dyrenavn = { "Fugl", "Katt", "Hund", "Kanin", "Gris" };
13
14     // Oppretter liste, velger gris som startbilde
15     JList<String> dyrevelger = new JList<>( dyrenavn );
16     dyrevelger.setVisibleRowCount( 4 );
17     dyrevelger.setFixedCellWidth( 50 );
18     dyrevelger.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
19     dyrevelger.setSelectedIndex(4);
20     dyrevelger.addListSelectionListener( new ListSelectionListener() {
21       public void valueChanged( ListSelectionEvent e)
22       {
23         if ( !e.getValueIsAdjusting() )
24         {
25           JList<String> liste = (JList) e.getSource();
26           String dyrenavn = liste.getSelectedValue();
27           bilde.setIcon(new ImageIcon(
28                   getClass().getResource("bilder/" +
29                   dyrenavn + ".gif")));
30         }
31       }
32     });
33
34     // Oppretter bilde-label
35     bilde = new JLabel(new ImageIcon(
36             getClass().getResource("bilder/" +
37             dyrenavn[dyrevelger.getSelectedIndex()] + ".gif")));
38     Container c = getContentPane();
39     c.setLayout( new FlowLayout() );
40     c.add( new JScrollPane( dyrevelger ) );
41     c.add( bilde );
42   }
43 }

Bruk av flervalgs listebokser

Som nevnt ovenfor, finnes det to typer av flervalgs listebokser. For typen ListSelectionModel.SINGLE_INTERVAL_SELECTION må alternativene velges i et sammenhengende intervall. For å velge, klikker vi da først på alternatavet som skal være i den ene enden av intervallet. Så holder vi skift-tasten nede mens vi klikker på alternativet som skal være i den andre enden av intervallet.

For å velge alternativene vi ønsker valgt når vi har en listeboks av type ListSelectionModel.MULTIPLE_INTERVAL_SELECTION, så holder vi ctrl-tasten nede mens vi klikker på de forskjellige alternativene.

For begge de nevnte typer listebokser kan vi få tak i de valgte alternativene enten i form av en array av indekser, ved kall på metoden getSelectedIndices, eller i form av en List<E> ved kall på metoden getSelectedValuesList. Datatypen List<E> er den typen som er definert i Javas Collection-rammeverk, det vil si den som ligger i pakken java.util, se beskrivelse av denne typen under kapitlet om dette rammeverket. For å gjennomløpe en List<E> kan vi bruke en utvidet for-løkke:

  List<E> utvalg = liste.getSelectedValuesList();
  for (E e : utvalg)
    < gjør et eller annet med e >

Før javaversjon 7 kunne en få tak i de valgte alternativene i form av en array av Object-objekter, ved kall på metoden getSelectedValues. Men denne metoden er i javaversjon 7 markert som foreldet. (Metoden blir brukt i Fig. 14.25 i 9. utgave av læreboka til Deitel & Deitel, men den er altså i JDK7 markert som foreldet, se getSelectedValues.)

Samme enten vi velger å hente ut valgte listealternativer i form av en array av indekser, ved bruk av metoden getSelectedIndices, eller i form av en List<E>, ved bruk av metoden getSelectedValuesList, så er det ikke mulig å bruke det vi får returnert på en direkte måte til å endre innhold i listeboksen, eller å få brukt det direkte til å endre innhold i en annen listeboks. Vil vi endre innholdet, så må vi som før nevnt få tak i listemodellen og jobbe mot denne. Framgangsmåten for dette blir nærmere forklart i det følgende.

Sette inn og fjerne elementer i en listeboks

Når vi oppretter en listeboks på den måten som ble forklart ovenfor, vil javasystemet opprette en listemodell som det seinere ikke er mulig å endre innholdet i. Dersom vi ønsker en listeboks med dynamisk innhold, altså som det skal være mulig å endre under kjøring, så må vi opprette vår egen listemodell. Den enkleste måten å gå fram på da, er å opprette et objekt av type DefaultListModel<E>, som er en subklasse til AbstractListModel<E>. Det er dette alternativet som blir brukt i eksemplet nedenfor. Det er også mulig å definere sin egen subklasse til AbstractListModel<E>, men da kreves det mye mer programmering for å få det til å virke riktig.

For å opprette egen listemodell som skal inneholde String-alternativer, sette inn elementer i modellen, samt opprette listeboks på grunnlag av modellen, kan vi bruke følgende instruksjoner:

  DefaultListModel<String> modell = new DefaultListModel<>();
  modell.addElement( "..." );  // setter inn element
  ...                          // setter inn så mange elementer vi ønsker
  JList<String> listeboks = new JList<>( modell );

For å få tak i listemodellen til en eksisterende listeboks, kan vi bruke JList-metoden getModel. Returen fra denne er imidlertid av type ListModel<E>, som er navnet på det interface som blir implementert av listemodellklassene. Dette interface spesifiserer ingen metoder for å legge til eller fjerne elementer fra listemodellen. Derfor må vi konvertere returen til den typen vi vet at det i praksis er, nemlig DefaultListModel. Metoden for å sette inn element, addElement, er allerede nevnt ovenfor. Tilsvarende metode for å fjerne element er

  modell.removeElement( "..." );  // fjerner element

Denne framgangsmåten er brukt i følgende programeksempel.

Eksempel

Vindusklassen Flervalgsliste er gjengitt nedenfor. Den inneholder to listebokser, den første av default-typen ListSelectionModel.MULTIPLE_INTERVAL_SELECTION, mens den andre er av typen ListSelectionModel.SINGLE_INTERVAL_SELECTION. Ved å klikke på knappen mellom listeboksene, kan de valgte elementene i den første listeboksen bli tilføyd til de eksisterende elementene i den andre listeboksen, forutsatt at de ikke finnes fra før. For å sjekke om et element finnes fra før, brukes metoden finnes:

 57    public boolean finnes( String e, JList<String> liste )
 58    {
 59      ListModel<String> listemodell = liste.getModel();
 60      for ( int i = 0; i < listemodell.getSize(); i++ )
 61        if ( listemodell.getElementAt( i ).equals( e ) )
 62          return true;
 63      return false;
 64    }

Metoden settInnElementer gjennomløper lista av elementer som brukeren har valgt, sjekker om de finnes fra før av i den listeboksen de skal settes inn i, og setter dem inn i tilfelle de ikke finnes:

 66    public void settInnElementer(List<String> elementer,
 67            JList<String> liste)
 68    {
 69      DefaultListModel<String> listemodell =
 70              (DefaultListModel) liste.getModel();
 71      for ( String s : elementer )
 72        if ( !finnes( s, liste ) )
 73          listemodell.addElement( s );
 74    }

Elementer som merkes i den andre listeboksen, kan fjernes fra denne ved å klikke på knappen for dette. Metoden fjernElementer fjerner fra listeboksen de elementene brukeren har markert.

Videre er det for den andre listeboksen definert to listemodeller. En kan skifte mellom de to listemodellene ved å klikke på knappen for dette. Da blir metoden skiftModell kalt opp. Den modellen som programmet starter opp med, er i utgangspunktet tom, mens det i den andre er lagt inn tre elementer. Følgende bilde viser programvinduet etter at det er valgt to elementer i den første listeboksen og disse er blitt tilføyd i den andre. Driverklasse for programmet finnes i fila Flervalgslistetest.java.

  1 import javax.swing.*;
  2 import java.awt.*;
  3 import java.awt.event.*;
  4 import java.util.List;
  5
  6 public class Flervalgsliste extends JFrame
  7 {
  8    private JList<String> fargeliste, kopiliste;
  9    DefaultListModel<String> modell, nymodell; //skal inneholde  
 10                                       //alternativene til kopiliste
 11    private JButton kopier, skift, fjern;
 12    private String fargenavn[] =
 13       { "Svart", "Blå", "Cyan", "Mørk grå", "Grå",
 14         "Grønn", "Lys grå", "Magenta", "Oransje", "Rosa",
 15         "Rød", "Hvit", "Gul" };
 16
 17    public Flervalgsliste()
 18    {
 19       super( "Flervalgslister" );
 20       Container c = getContentPane();
 21       c.setLayout( new FlowLayout() );
 22       fargeliste = new JList<>( fargenavn );
 23       fargeliste.setVisibleRowCount( 5 );
 24       fargeliste.setFixedCellHeight( 15 );
 25       c.add( new JScrollPane( fargeliste ) );
 26       kopier = new JButton( "Legg til i liste >>" );
 27       c.add( kopier );
 28
 29       modell = new DefaultListModel<>();
 30       kopiliste = new JList<>( modell );
 31       kopiliste.setVisibleRowCount( 5 );
 32       kopiliste.setFixedCellWidth( 100 );
 33       kopiliste.setFixedCellHeight( 15 );
 34       kopiliste.setSelectionMode(
 35          ListSelectionModel.SINGLE_INTERVAL_SELECTION );
 36       c.add( new JScrollPane( kopiliste ) );
 37
 38       fjern = new JButton( "Fjern fra kopiliste" );
 39       c.add( fjern );
 40
 41       nymodell = new DefaultListModel<>();
 42       nymodell.addElement("Grønn");
 43       nymodell.addElement("Blå");
 44       nymodell.addElement("Rød");
 45
 46       skift = new JButton( "Skift modell" );
 47       c.add( skift );
 48
 49       Knappelytter lytter = new Knappelytter();
 50       kopier.addActionListener( lytter );
 51       skift.addActionListener( lytter );
 52       fjern.addActionListener( lytter );
 53       setSize( 600, 120 );
 54       setVisible( true );
 55    }
 56
 57    public boolean finnes( String e, JList<String> liste )
 58    {
 59      ListModel<String> listemodell = liste.getModel();
 60      for ( int i = 0; i < listemodell.getSize(); i++ )
 61        if ( listemodell.getElementAt( i ).equals( e ) )
 62          return true;
 63      return false;
 64    }
 65
 66    public void settInnElementer(List<String> elementer,
 67            JList<String> liste)
 68    {
 69      DefaultListModel<String> listemodell =
 70              (DefaultListModel) liste.getModel();
 71      for ( String s : elementer )
 72        if ( !finnes( s, liste ) )
 73          listemodell.addElement( s );
 74    }
 75
 76    public void fjernElementer( List<String> elementer,
 77            JList<String> liste )
 78    {
 79      DefaultListModel<String> listemodell =
 80              (DefaultListModel) liste.getModel();
 81      for (String s : elementer)
 82        listemodell.removeElement( s );
 83    }
 84
 85    public void skiftModell( JList<String> liste )
 86    {
 87      if ( liste.getModel() != nymodell )
 88        liste.setModel( nymodell );
 89      else
 90        liste.setModel( modell );
 91    }
 92
 93    private class Knappelytter implements ActionListener
 94    {
 95      public void actionPerformed( ActionEvent e )
 96      {
 97        if ( e.getSource() == kopier )
 98        {
 99          // adderer valgte verdier til kopiliste
100          List<String> utvalg = fargeliste.getSelectedValuesList();
101          settInnElementer( utvalg, kopiliste );
102        }
103        else if ( e.getSource() == fjern )
104        {
105          // fjerner valgte verdier fra kopiliste
106          List<String> utvalg = kopiliste.getSelectedValuesList();
107          fjernElementer( utvalg, kopiliste );
108        }
109        else if ( e.getSource() == skift )
110          skiftModell( kopiliste );
111      }
112    }
113 }

Copyright © Kjetil Grønning, 2011

Forrige avsnitt Neste avsnitt Start på kapittel om grafiske brukergrensesnitt