© 1999-2003, Flemming Koch Jensen
Alle rettigheder forbeholdt
Lister

Opgaver

 

 

1. JList

Ønsker man at brugeren skal vælge en af flere muligheder, der er opstillet i form af en liste, kan man anvende en JList.
Hvis vi f.eks. ønsker, at brugeren skal vælge en af ugedagene, kan man anvende følgende JList:
Figur 1:
Ugens dage i en JList
Vinduet er lavet med følgende frame:
Kildetekst 1:
JList der kun tillader valg af ét element
ListFrame.java
class ListFrame extends JFrame {
  
  public ListFrame( String title ) {
    super( title );
    
    getContentPane().setLayout( new FlowLayout() );
    
    String[] muligheder = { "mandag", "tirsdag", "onsdag", "torsdag",
                            "fredag", "lørdag", "søndag" };
    
    JList list = new JList( muligheder );
    getContentPane().add( list );
    
    setDefaultCloseOperation( EXIT_ON_CLOSE );
    
    setSize( 200, 200 );
    setVisible( true );
  }
}
Som udgangspunkt er ingen af mulighederne valgt. Der findes følgende set/get-metoder vedrørende det valgte element:
void setSelectedIndex( int index )
int  getSelectedIndex()
I det følgende skal vi se hvordan man kan vælge mere end ét element af gangen, i disse situationer vil getSelectedIndex-metoden returnere index for det første af de valgte; hvis ingen er valgt returnerer metoden -1. setSelectedIndex-metoden er ikke så ofte anvendt, men den giver mulighed for, at man fra programmets side kan sætte et element i listen som valgt.

JList gør det muligt at vælge flere punkter samtidig. Det indstilles ved at kalde metoden:

void setSelectionMode( int mode )
Med en af følgende tre konstanter som parameter:
ListSelectionModel.SINGLE_SELECTION
ListSelectionModel.SINGLE_INTERVAL_SELECTION
ListSelectionModel.MULTIPLE_INTERVAL_SELECTION
SINGLE_SELECTION er default; hvor kun et element kan være valgt.
Med SINGLE_INTERVAL_SELECTION er det muligt at vælge flere elementer, men de skal være sammenhængende, dvs. være på hinanden følgende.
Figur 2:
Interval af elementer fra listen
Man kan vælge et interval ved at holde Shift-knappen nede.
I forbindelse med denne mode kan man bruge følgende set/get-metoder:
void  setSelectionInterval( int first, int last )
int[] getSelectedIndices()
Object[] getSelectedValues()
Med setSelectedInterval vælges elementerne der svarer til index-intervallet: [first:last].
getSelectedIndices returnerer et array af de index der er valgt, mens getSelectedValues returnerer et array af referencer til de elementer, der er valgt i listen.
MULTIPLE_INTERVAL_SELECTION åbner mulighed for, at de valgte elementer ikke er sammenhængende, dvs. at de kan være spredt ud over index-mængden.
Figur 3:
Flere elementer fra listen
Man skal holde Ctrl-knappen nede når man vælger flere elementer.
I forbindelse med denne mode anvender man stadig getSelectedIndices, og man har en tilsvarende set-metode:
void setSelectionInterval( int[] index )

 

Det er muligt selv at indstille højde/bredde på cellerne. En celle er det område, der bruges til at vise et element fra listen. Det gøres med følgende metoder:

void setFixedCellHeight( int height )
void setFixedCellWidth( int width )
I følgende eksempel har vi valgt højden 30 og bredden 80:
Figur 4:
Direkte sat højde/bredde

 

1.1 Event-håndtering

Vedrørende event-håndtering, tilmelder man sig en JList som ListSelectionListener. ListSelectionListener-interfacet kræver at man implementerer følgende metode:

void valueChanged( ListSelectionEvent e )
Man tilmelder sig som listener ved at kalde addListSelectionListenerJList'en.
Når der sker ændringer i listen vil valueChanged-metoden blive kaldt. Det ListSelectionEvent-objekt man modtager har følgende metoder:
int getFirstIndex()
int getLastIndex()
boolean getValueIsAdjusting()
De to første metoder fortæller i hvilken del af listen der er sket ændringer, mens den sidste metode fortæller om eventen blot er et udtryk for en af flere igangværende ændringer. Man vil derfor ofte vælge at ignorere events; hvor denne metode returnerer true.

 

1.2 DefaultListModel

Anvender man en JList ved at give dens konstruktor et array med som parameter, indeholdende det der skal være i listen, laves der en immutable ListModel; hvilket bevirker at man ikke efterfølgende kan tilføje eller fjerne elementer fra listen. Ønsker man at kunne gøre dette, må man i stedet lave sin egen ListModel, eller som vi skal se i det følgende anvende en DefaultListModel til formålet - den er nemlig mutable, og kan dermed ændre sit indhold!
I det følgende eksempel vil vi implementere følgende frame:
Figur 5:
To lister, der kan udveksle elementer
Som man ser er selve GUI'en lidt spartansk. Det skyldes at vi i stedet vil koncentrere os om listerne, og hvordan vi kan flytte elementer frem og tilbage mellem dem. Vælger man en eller flere elementer i en af listerne, og trykker på den knap der "peger" væk fra listen, vil elementerne blive flyttet over i den anden liste.
Kildeteksten til ovenstående frame er som følger:
Kildetekst 2:
Anvendelse af DefaultListModel
SelectFrame.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;

public class SelectFrame extends JFrame implements ActionListener {
  private JList leftList, rightList;
  private DefaultListModel leftListModel, rightListModel;
  private JButton fraButton, tilButton;

  public SelectFrame(String title) {
    super( title );

    /*
     * Listerne
     */
    leftListModel = createListModel( new String[] { "mandag", "tirsdag" } );
    rightListModel = createListModel(
      new String[] { "onsdag", "torsdag", "fredag", "lørdag", "søndag" } );
    
    leftList = new JList( leftListModel );
    leftList.setBorder( new TitledBorder( new EtchedBorder(), "Fra" ) );
    
    rightList = new JList( rightListModel );
    rightList.setBorder( new TitledBorder( new EtchedBorder(), "Til" ) );
    
    /*
     * Til/fra knapperne
     */
    fraButton = new JButton( "<<" );
    fraButton.addActionListener( this );
    
    tilButton = new JButton( ">>" );
    tilButton.addActionListener( this );
    
    JPanel centerPanel = new JPanel();
    centerPanel.add( fraButton );
    centerPanel.add( tilButton );

    /*
     * Layoutet
     */
    getContentPane().setLayout( new BorderLayout() );
    getContentPane().add( leftList, BorderLayout.WEST );
    getContentPane().add( centerPanel, BorderLayout.CENTER );
    getContentPane().add( rightList, BorderLayout.EAST );
    
    setDefaultCloseOperation( EXIT_ON_CLOSE );

    setSize( 300, 200 );
    setVisible( true );
  }
  
  private DefaultListModel createListModel( String[] items ) {
    DefaultListModel listModel = new DefaultListModel();
    
    for ( int i=0; i<items.length; i++ )
      listModel.addElement( items[i] );
    
    return listModel;
  }

  public void actionPerformed( ActionEvent e ) {
    Object source = e.getSource();

    if ( source == fraButton )
      flytItems( rightList.getSelectedValues(), rightListModel, leftListModel );
    else if ( source == tilButton )
      flytItems( leftList.getSelectedValues(), leftListModel, rightListModel );
  }
  
  private void flytItems( Object[] items, DefaultListModel fraListe,
                                          DefaultListModel tilListe )
  {
    for ( int i=0; i<items.length; i++ ) {
      fraListe.removeElement( items[i] );
      tilListe.addElement( items[i] );
    }
  }
}
De to centrale elementer i ovenstående kode, er createListModel-metoden, og eventhåndteringen hvor der flyttes elementer mellem de to modeller på baggrund af en liste der fås fra den JList der skal afgive elementer til den anden (kaldet af getSelectedValues-metoden). Bemærk at der anvendes en array af elementer, og ikke et array af index, da disse ikke er velegnede til at fjerne flere elementer på en gang (index vil ændre sig hver gang der fjerens et element).

 

1.3 ListModel

[Dette afsnit er under udarbejdelse!]
Object getElementAt( int index )
int getSize()
void addListDataListener( ListDataListener listener )
void removeListDataListener( ListDataListener listener )
 
void contentsChanged( ListDataEvent e )
void intervalAdded( ListDataEvent e )
void intervalRemoved( ListDataEvent e )
 
ListDataEvent( Object source, int type, int start, int end )
 
ListDataEvent.CONTENTS_CHANGED
ListDataEvent.INTERVAL_ADDED
ListDataEvent.INTERVAL_REMOVED
 

 

2. JComboBox

Hvis man ønsker, at brugeren skal vælge én af flere muligheder, uden man behøver vise alle mulighederne på en gang, bruger man typisk en JComboBox.

En af mulighederne er altid valgt, og det er den eneste der vises:
Figur 6:
JComboBox med fuld liste
Når man aktiverer comboboxen åbner den sig og viser listen af mulige valg; hvoraf man kan vælge en.
Ovenstående frame laves med:
Kildetekst 3:
JComboBox med fuld liste
ComboFrame.java
class ComboFrame extends JFrame {
  
  public ComboFrame( String title ) {
    super( title );
    
    getContentPane().setLayout( new FlowLayout() );
    
    String[] list = { "mandag", "tirsdag", "onsdag", "torsdag",
                      "fredag", "lørdag", "søndag" };
    
    JComboBox box = new JComboBox( list );
    getContentPane().add( box );
    
    setDefaultCloseOperation( EXIT_ON_CLOSE );
    
    setSize( 200, 100 );
    setVisible( true );
  }
}
Default vises hele listen, men det er muligt kun at vise en del af listen. I så fald skal brugeren scrolle mellem mulighederne og træffe sit valg.
Figur 7:
setMaximum-RowCount til at begrænse antallet af viste valg
Dette gøres ved at kalde metoden:
void setMaximumRowCount( int max )
I vores tilfælde bliver det kaldet:
box.setMaximumRowCount( 3 );

 

Det er også muligt at tillade brugeren selv at indtaste et valg. Det kan være et valg fra listen, men det kan også være et valg der ikke findes på listen.

Figur 8:
setEditable gør det muligt selv at indtaste valg
Det gøres ved at kalde metoden:
void setEditable( boolean editable )
I vores tilfælde bliver det kaldet:
box.setEditable( true );

 

Default vil en combobox starte med den første mulighed som valgt. Man kan dog vælge en anden med metoden:

void setSelectedItem( Object item )
I vores eksempel kunn vi f.eks. bruge kaldet:
box.setSelectedItem( "fredag" );
Der ville give følgende startbillede:
Figur 9:
isSelectedItem til valg af start-valg

 

2.1 Event-håndtering

Man tilmelder sig en JComboBox som ItemListener. ItemListener-interfacet kræver at man implementerer følgende metode:

void itemStateChanged( ItemEvent e )
Når denne metode bliver kaldt hos en ItemListener, har denne mulighed for at kalde følgende metoder på ItemEvent'en:
Object getItem()
int getStateChange()
Den første fortæller hvilket element i listen der er berørt af ændringen, og den sidste fortæller hvilken ændring der er tale om. getStateChange-metoden returnerer en af følgende konstanter:
ItemEvent.SELECTED
ItemEvent.DESELECTED
der fortæller, at det pågældende element fra listen enten er blevet valgt eller ikke længere er valgt.
Lad os se et eksempel:
Kildetekst 4:
Eksempel på en ItemListener på en JComboBox
ComboFrame.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class ComboFrame extends JFrame implements ItemListener {
  
  public ComboFrame() {
    super( "JComboBox eksempel" );
    
    getContentPane().setLayout( new FlowLayout() );
    
    String[] entries = {
      "mandag", "tirsdag", "onsdag", "torsdag", "fredag"
    };
    
    JComboBox liste = new JComboBox( entries );
    liste.addItemListener( this );
    
    getContentPane().add( liste );
    
    setDefaultCloseOperation( EXIT_ON_CLOSE );
    
    setSize( 250, 100 );
    setVisible( true );
  }
  
  public void itemStateChanged( ItemEvent e ) {
    if ( e.getStateChange() == ItemEvent.DESELECTED )
      System.out.println( e.getItem() + " er ikke længere valgt" );
    else
      System.out.println( e.getItem() + " er valgt" );
  }
}
mandag er ikke længere valgt
onsdag er valgt
onsdag er ikke længere valgt
torsdag er valgt
torsdag er ikke længere valgt
fredag er valgt
Testanvendelsen, der gav udskriften ovenfor, forløb som følger:
Som udgangspunkt var mandag valgt, da den er det første element i listen. Dernæst valgte vi onsdag, så torsdag og til sidst fredag.

 

2.2 ComboBoxModel

[Dette afsnit er under udarbejdelse!]
Ønsker man have større kontrol over modellen - dvs. den container der indeholder elementerne, der bliver vist i listen - må man lave et model-objekt, en: ComboBoxModel.
Dette gøres ved at lave en container-klasse, der implementerer ComboBoxModel-interfacet:
Object getSelectedItem()
void setSelectedItem( Object item )
Object getElementAt( int index )
int getSize()
void addListDataListener( ListDataListener listener )
void removeListDataListener( ListDataListener listener )
Man genkender her de fire sidste metoder fra ListModel-interfacet, som vi så på tidligere i dette kapitel, og det skyldes netop at ComboBoxModel nedarver fra ListModel.
Der findes en simpel implementation af dette interface, klassen: DefaultComboBoxModel som man kan anvende til langt de fleste formål, men vi vil i det følgende demonstrere hvordan man selv kan lave en sådan realisering, der dog er endnu mere simpel end DefaultComboBoxModel!
Kildetekst 5:
S
ComboModel.java
import javax.swing.*;
import javax.swing.event.*;
import java.util.LinkedList;
import java.util.Iterator;

public class ComboModel implements ComboBoxModel {
  private Object selectedItem;
  private Object[] items;
  
  public ComboModel( Object[] items ) {
    setItems( items );
    
    selectedItem = items[0];
  }
  
  public void setItems( Object[] items ) {
    this.items = items;
    
    fireListeners();
  }
  
  public Object getElementAt( int index ) {
    return items[ index ];
  }
  
  public int getSize() {
    return items.length;
  }
  
  public Object getSelectedItem() {
    return selectedItem;
  }
  
  public void setSelectedItem( Object item ) {
    selectedItem = item;

    fireListeners();
  }
  
  /*
   * Listeners
   */
  
  private LinkedList listeners=new LinkedList();
  
  public void addListDataListener( ListDataListener listener ) {
    listeners.add( listener );
  }
  
  public void removeListDataListener( ListDataListener listener ) {
    listeners.remove( listener );
  }
  
  private void fireListeners() {
    Iterator it = listeners.iterator();
    
    while ( it.hasNext() ) {
      ListDataListener listener = (ListDataListener) it.next();
      listener.contentsChanged(
        new ListDataEvent( this,
                           ListDataEvent.CONTENTS_CHANGED,
                           0,
                           items.length-1 )
                         );
    }
  }
}