Forrige avsnitt Neste avsnitt  Start på kapittel om spesialiserte komponenter for grafiske brukergrensesnitt

Hvordan programmere tabeller av type JTable

JTable-objekter brukes til å vise data i tabellform. Om det ønskes, kan også brukeren tillates å editere de viste data. Dataene blir ikke lagret av selve JTable-objektet. Men dette får sine data fra en såkalt tabellmodell: et TableModel-objekt.

Som regel bør en, for å få størst mulig fleksibilitet, implementere sin egen tabellmodell. Det finnes imidlertid to JTable-konstruktører som på grunnlag av mottatte data oppretter en tabellmodell automatisk. Disse konstruktørene ser slik ut:

  JTable( Object[][] raddata, Object[] kolonnenavn )
  JTable( Vector raddata, Vector kolonnenavn )

En tabell opprettet med en av disse konstruktørene har imidlertid følgende begrensninger:

For å utstyre en tabell med skrollefelter, må den på vanlig måte puttes inn i en JScrollPane. Ved skrolling nedover i tabellen vil da tabellhodet hele tida forbli synlig. Det er mulig å bestemme hvor stort vi ønsker at skrollevinduet skal være ved bruk av instruksjonen

  tabell.setPreferredScrollableViewportSize(new Dimension(bredde, høyde));

Med skrollevindu menes det som endrer seg ved skrolling. For en tabell vil altså tabellhodet stå ovenfor skrollevinduet, slik at det ved skrolling hele tida er synlig. (I instruksjonen ovenfor er det underforstått at tabell er vedkommende JTable-objekt, mens bredde og høyde er ønsket antall piksler for bredde og høyde.)

Kolonnebredde

Dersom vi ikke har bestemt noe annet, vil alle kolonnene i tabellen i utgangspunktet ha samme bredde og kolonnene vil automatisk utfylle hele tabellens bredde. Endring av kolonnebredde kan utføres under programkjøring på tilsvarende måte som vi kjenner fra andre programmer, for eksempel Excel, ved bruk av musa.

Tilpassing av kolonnenes startbredde kan gjøres med metoden setPreferredWidth når vi har JTable-objektet tabell:

  TableColumn kolonne = tabell.getColumnModel().getColumn( i );
           //kolonnene er indeksert fra 0 og oppover
  kolonne.setPreferredWidth( antPixler );

I tillegg til på denne måten å sette preferert bredde, er det på tilsvarende måte mulig å sette minimums- og maksimumsbredde for kolonnene ved bruk av metodene setMinWidth og setMaxWidth. Det finnes også get-metoder for å hente ut verdiene til disse.

Dersom brukeren eksplisitt endrer kolonnebredde (med musa, som forklart ovenfor), vil de nye breddene automatisk ikke bare bli kolonnenes nye aktuelle bredde, men også deres nye prefererte bredde. Imidlertid, dersom kolonnene endrer bredde ved at brukeren endrer bredden til hele tabellen (ved bruk av musa), vil ikke kolonnenes prefererte bredde endre seg.

Ved endring av kolonnebredde vil, som default, kolonnene til høyre for den vi endrer bredde på, automatisk endre bredde slik at de deler imellom seg den plassen de nå får til disposisjon. Denne virkemåten kan vi endre ved kall på

  tabell.setAutoResizeMode( type );

der type kan ha en av følgende verdier:

  JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS  //default-verdi
  JTable.AUTO_RESIZE_NEXT_COLUMN  //endrer bare de to nabo-kolonnene
  JTable.AUTO_RESIZE_LAST_COLUMN  //endrer bare siste kolonne
  JTable.AUTO_RESIZE_ALL_COLUMNS  //endrer alle kolonner
  JTable.AUTO_RESIZE_OFF //endrer ikke andre kolonner, men isteden tabellbredden

Velge rader og celler

En bruker kan velge en rad i tabellen ved å klikke med musa hvor som helst i raden. Raden blir da markert. En celle kan velges for editering ved å klikke i den, eventuelt dobbeltklikke. Default er at det i tabellen kan velges multiple rader, akkurat som for en JList. Velgemodus kan endres akkurat som for en JList:

  tabell.setSelectionMode( type );

der type kan ha en av følgende verdier:

  ListSelectionModel.SINGLE_SELECTION
  ListSelectionModel.SINGLE_INTERVAL_SELECTION
  ListSelectionModel.MULTIPLE_INTERVAL_SELECTION  //default

Bruk av metodene

  tabell.setRowSelectionAllowed( ønske )
  tabell.setColumnSelectionAllowed( ønske )
  tabell.setCellSelectionEnabled( ønske )

med parameter ønske lik true eller false gjør det mulig å bestemme hva som skal kunne velges. Default er altså å velge rader. Dersom både radvelging og kolonnevelging er tillatt, vil museklikk på en celle velge både den rad og den kolonne som denne celle ligger i. Metodene getSelectedRows og getSelectedColumns returnerer int-arrayer som inneholder indeksene for valgte rader og kolonner.

Valg av individuelle celler, uten samtidig å velge den rad eller kolonne som cellen ligger i, gjøres mulig ved å skrive

  tabell.setCellSelectionEnabled( true );

Settingene for rader og kolonner blir da ignorert. Cellevalg gjør det mulig å velge enkeltceller ved å klikke på dem, eller multiple celler ved å merke dem med musa på vanlig måte.

Skjuling og visning av kolonner

Det er mulig å skjule kolonner i tabellen, men ikke rader. Skjulte kolonner vil fortsatt befinne seg i tabellmodellen, men de vises bare ikke. For å skjule en kolonne, kan følgende instruksjoner brukes:

  TableColumnModel kolonnemodell = tabell.getColumnModel();
  TableColumn kolonne = kolonnemodell.getColumn( i );
  tabell.removeColumn( kolonne );

For å få tak i indeksen i til kolonnen som skal skjules, kan en gjøre bruk av metoden getSelectedColumn, eller getSelectedColumns i tilfelle det er flere som skal skjules.

Dersom en tar vare på TableColumn-objektet som ble skjult, kan en seinere gjøre det synlig igjen ved bruk av instruksjonen

  tabell.addColumn( kolonne );

Denne metoden vil legge inn kolonnen på slutten av tabellen. Dersom en ønsker å ha den et annet sted, må en gjøre kall på metoden

  moveColumn( int fra, int til )

som flytter kolonnen på indeksplass fra til posisjonen som kolonnen på indeksplass til for øyeblikket opptar. Denne vil bli skjøvet til venstre eller høyre for å gi plass.

Editering

I en tabell bør enten alle celler være ikke-editerbare eller håndtering av celle-editeringshendelser og oppdatering av tabellverdier bør implementeres. Ved bruk av de to konstruktørene som ble nevnt innledningsvis, vil, som det ble nevnt, alle celler bli editerbare. Men det skjer ingen oppdatering som følge av editering, med mindre dette er implementert. Hvordan man implementerer oppdatering skal vi ta for oss seinere.

Programeksempel 1: planettabell

Programmet i fila Planettabell.java som er gjengitt nedenfor, oppretter ved hjelp av én av de to konstruktørene nevnt i innledningen en enkel tabell over solsystemets planeter og noen av deres data. Fargen som er lagt inn har ingen logisk tilknytning til planeten. Den er tatt med for seinere bruk og for å vise default-rendring av tabellceller: Alle celler rendres som tekst ved kall på objektenes toString-metoder. Legg merke til at siden den todimensjonale arrayen celler for tabellens data bare kan inneholde objekter, må primitive dataverdier gjøres om til objekter ved å bruke deres tilhørende "innpakningsklasser". For øvrig merker vi oss, ved kjøring av programmet, hvordan vi kan velge rader, endre kolonnebredde og editere. Legg også merke til at det ved hjelp av musa er mulig å endre rekkefølge for kolonnene!

 1 import java.awt.*;
 2 import javax.swing.*;
 3
 4 public class Planettabell
 5 {
 6   public static void main(String[] args)
 7   {
 8     EventQueue.invokeLater(new Runnable()
 9     {
10       public void run()
11       {
12         JFrame vindu = new PlanettabellFrame();
13         vindu.setVisible(true);
14       }
15     });
16   }
17 }
18 //Demonstrasjon av enkel tabell
19 class PlanettabellFrame extends JFrame
20 {
21   private String[] kolonnenavn =
22   {
23     "Planet", "Radius", "Antall måner", "Gassplanet", "Fargekode"
24   };
25   private Object[][] celler =
26   {
27     {
28       "Merkur", new Double(2440), new Integer(0),
29       Boolean.FALSE, Color.yellow
30     },
31     {
32       "Venus", new Double(6052), new Integer(0),
33       Boolean.FALSE, Color.yellow
34     },
35     {
36       "Jorda", new Double(6378), new Integer(1),
37       Boolean.FALSE, Color.blue
38     },
39     {
40       "Mars", new Double(3397), new Integer(2),
41       Boolean.FALSE, Color.red
42     },
43     {
44       "Jupiter", new Double(71492), new Integer(16),
45       Boolean.TRUE, Color.orange
46     },
47     {
48       "Saturn", new Double(60268), new Integer(18),
49       Boolean.TRUE, Color.orange
50     },
51     {
52       "Uranus", new Double(25559), new Integer(17),
53       Boolean.TRUE, Color.blue
54     },
55     {
56       "Neptun", new Double(24766), new Integer(8),
57       Boolean.TRUE, Color.blue
58     },
59     {
60       "Pluto", new Double(1137), new Integer(1),
61       Boolean.FALSE, Color.black
62     }
63   };
64
65   public PlanettabellFrame()
66   {
67     setTitle("Planettabell");
68     setSize(500, 210);
69     setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
70
71     JTable tabell = new JTable(celler, kolonnenavn);
72
73     Container c = getContentPane();
74     c.add(new JScrollPane(tabell), BorderLayout.CENTER);
75   }
76 }

Nedenfor kan du se et bilde av det vinduet som kommer opp på skjermen når programmet blir kjørt.

Merknad

Pluto har etter at dette programmet ble laget mistet sin status som en ordinær planet. Den regnes nå som en dvergplanet. Du kan lese mer om dens status på Wikipedia.

Tabellmodeller

Alle tabeller får sine data fra et objekt som implementerer interface TableModel. Som regel bør vi, istedenfor å bruke de nevnte konstruktører for å opprette en tabell, selv implementere en tabellmodell.

For å implementere en tabellmodell er det enklest å definere en subklasse til klassen AbstractTableModel. Denne klassen implementerer de fleste av de nødvendige metodene. Vi trenger bare å supplere den med disse tre metodene:

  public int getRowCount() { ... }
  public int getColumnCount() { ... }
  public Object getValueAt( int rad, int kolonne ) { ... }

Den siste av disse kan enten beregne selv den verdien den skal returnere, eller hente den fra en eller annen kilde, for eksempel en database.

Dersom vi ikke også implementerer metoden

  public String getColumnName( int kolonne ) { ... }

så vil kolonnene i tabellen automatisk få navnene A, B, C, ... .

Visning og editering av celler

Som tidligere nevnt, vil tabellverdiene, dersom vi ikke har bestemt noe annet, bli vist i tabellen ved at det gjøres kall på objektenes toString-metoder. Dette kan vi forbedre ved at vi informerer tabellmodellen om hvilken datatype som befinner seg i de forskjellige kolonnene. Vi får gjort dette ved å implementere tabellmodellens metode getColumnClass:

  public Class getColumnClass( int k )
  {
    return getValueAt( 0, k ).getClass();
  }

der 0 er en radindeks og k er en kolonneindeks.

Når en tabell blir tegnet ut, blir det brukt en cellerendrer til å tegne ut alle cellene i en kolonne. Denne blir ofte brukt som en fellesressurs for flere kolonner som inneholder samme datatype. For å velge hvilken rendrer som skal brukes til å tegne ut cellene i en kolonne, sjekker tabellen først om vi har spesifisert en egendefinert rendrer for denne kolonnen. Dersom det ikke er gjort, gjøres kall på getColumnClass-metoden for å sjekke hvilken datatype som befinner seg i kolonnen. Denne datatypen blir sjekket mot en liste av typer som det er registrert rendrer for. Følgende typer er det forhåndsdefinert rendrer for:

For å opprette tabell på grunnlag av egendefinert tabellmodell, må vi altså gjøre følgende:

Programeksempel 2

Vi oppretter tabellmodell for planettabellen fra forrige eksempel. Det nye programmet er inneholdt i fila Tabellmodell.java som er gjengitt nedenfor:

  1 import java.awt.*;
  2 import javax.swing.*;
  3 import javax.swing.table.*;
  4
  5 public class Tabellmodell
  6 {
  7   public static void main(String[] args)
  8   {
  9     EventQueue.invokeLater(new Runnable()
 10     {
 11       public void run()
 12       {
 13         JFrame vindu = new TabellmodellFrame();
 14         vindu.setVisible(true);
 15       }
 16     });
 17   }
 18 }
 19
 20 //Planettabellmodellen spesifiserer verdiene, rendring og
 21 //editeringsegenskapene for planetdataene.
 22 class Planettabellmodell extends AbstractTableModel
 23 {
 24   private String[] kolonnenavn =
 25   {
 26     "Planet", "Radius", "Antall måner", "Gassplanet", "Fargekode"
 27   };
 28   private Object[][] celler =
 29   {
 30     {
 31       "Merkur", new Double(2440), new Integer(0),
 32       Boolean.FALSE, Color.yellow
 33     },
 34     {
 35       "Venus", new Double(6052), new Integer(0),
 36       Boolean.FALSE, Color.yellow
 37     },
 38     {
 39       "Jorda", new Double(6378), new Integer(1),
 40       Boolean.FALSE, Color.blue
 41     },
 42     {
 43       "Mars", new Double(3397), new Integer(2),
 44       Boolean.FALSE, Color.red
 45     },
 46     {
 47       "Jupiter", new Double(71492), new Integer(16),
 48       Boolean.TRUE, Color.orange
 49     },
 50     {
 51       "Saturn", new Double(60268), new Integer(18),
 52       Boolean.TRUE, Color.orange
 53     },
 54     {
 55       "Uranus", new Double(25559), new Integer(17),
 56       Boolean.TRUE, Color.blue
 57     },
 58     {
 59       "Neptun", new Double(24766), new Integer(8),
 60       Boolean.TRUE, Color.blue
 61     },
 62     {
 63       "Pluto", new Double(1137), new Integer(1),
 64       Boolean.FALSE, Color.black
 65     }
 66   };
 67
 68   //For å få satt vårt eget kolonnenavn.
 69   public String getColumnName(int kolonne)
 70   {
 71     return kolonnenavn[ kolonne];
 72   }
 73
 74   //For å informere tabellmodellen om kolonnenes datatyper.
 75   public Class getColumnClass(int kolonne)
 76   {
 77     return celler[ 0][ kolonne].getClass();
 78   }
 79
 80   public int getColumnCount()
 81   {
 82     return celler[ 0].length;
 83   }
 84
 85   public int getRowCount()
 86   {
 87     return celler.length;
 88   }
 89
 90   public Object getValueAt(int rad, int kolonne)
 91   {
 92     return celler[ rad][ kolonne];
 93   }
 94 }
 95
 96 class TabellmodellFrame extends JFrame
 97 {
 98
 99   public TabellmodellFrame()
100   {
101     setTitle("Planettabell med egen tabellmodell");
102     setSize(500, 210);
103     setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
104
105     Planettabellmodell modell = new Planettabellmodell();
106     JTable tabell = new JTable(modell);
107
108     Container c = getContentPane();
109     c.add(new JScrollPane(tabell), BorderLayout.CENTER);
110   }
111 }

Ved kjøring av programmet får vi nå følgende vindu:

Vi ser at tallverdiene i tabellen nå blir høyrejustert og formatert (de blir rendret som høyrejusterte labeler) og de logiske verdiene blir rendret som avkryssingsbokser. Fargeobjektene blir rendret som før (ved kall på toString-metode). Ellers kan vi merke oss at ingen celle nå er editerbar.

Egendefinert cellerendrer

For å definere en cellerendrer må vi implementere interface TableCellRenderer.

Eksempel

Vi tenker oss at vi har en tabell med en kolonne som inneholder farger, det vil si Color-objekter. Vi ønsker å vise vedkommende farge i tabellen. En cellerendrer for denne kolonnen kan da defineres slik:

class Fargetabellcellerendrer implements TableCellRenderer
{
  private JPanel panel = new JPanel();

  public Component getTableCellRendererComponent(
                    JTable tabell, Object verdi, boolean erValgt,
                    boolean harFokus, int rad, int kolonne )
  {
    panel.setBackground( (Color) verdi );
    return panel;
  }
}

Når tabellen skal tegnes ut, vil det bli gjort kall på paint-metoden til den returnerte komponenten for å tegne tabell-cellen. Det er imidlertid nødvendig å fortelle tabellen at denne egendefinerte rendreren skal brukes for alle objekter av type Color. Det får vi gjort ved kall på tabellens setDefaultRenderer-metode:

  tabell.setDefaultRenderer( Color.class, new Fargetabellcellerendrer() );

Editering av celler

For å bestemme hvilke celler som skal være editerbare, må vi implementere tabellmodellens metode isCellEditable. Som regel ønsker vi at noen kolonner skal være editerbare, andre ikke:

  public boolean isCellEditable( int rad, int kolonne )
  {
    < skal returnere true for editerbare kolonner, false for de andre>
    // Merk at kolonnenes indekser ikke endrer seg ved at kolonnene
    // eventuelt bytter rekkefølge på skjermen. Indeksene tilhører
    // tabellmodellen, ikke visningen på skjermen.
  }

(metodens innhold er her skrevet i pseudo-kode).

Obs:

Klassen AbstractTableModel definerer isCellEditable til alltid å returnere false. Subklassen DefaultTableModel, som brukes av de to JTable-konstruktørene som ble nevnt innledningsvis, redefinerer isCellEditable til å returnere true for alle kolonner.

Dersom brukeren klikker (eventuelt dobbeltklikker) med musa i en editerbar celle, vil en celleeditor bli aktivert og er klar til å motta editering. Celleeditor velges i samsvar med det som tidligere er sagt om rendring. For data som rendres som tekst, vil det være et tekstfelt som er celleeditor. For logiske data er det en avkryssingsboks. Ønsker vi annet enn disse default-valgene, kan vi implementere vår egen celleeditor. Som eksempel på dette skal vi implementere en komboboks som celleeditor.

  JComboBox<Integer> verdivelger = new JComboBox<>();
  for ( int i = minverdi; i <= maxverdi; i++ )
    verdivelger.addItem( new Integer( i ) );
  TableCellEditor verdieditor = new DefaultCellEditor( verdivelger );

TableCellEditor er det interface som må implementeres av en klasse som skal definere editor for komponenter av type JComboBox, JTree, eller JTable. Som konstruktørparameter for det DefaultCellEditor-objekt som blir opprettet kan vi ha et tekstfelt (JTextField), en avkryssingsboks (JCheckBox), eller en komboboks (JComboBox).

For å installere editoren for en bestemt kolonne skriver vi:

  TableColumnModel kolonnemodell = tabell.getColumnModel();
  TableColumn verdikolonne = kolonnemodell.getColumn( <indeks til kolonnen> );
  verdikolonne.setCellEditor( verdieditor );

Når brukeren har foretatt en editering, vil tabellobjektet gjøre kall på tabellmodellens metode

  public void setValueAt( Object verdi, int rad, int kolonne )

NB! Denne må redefineres (av klassen som definerer tabellmodellen) for at den nye verdien skal bli lagret. Parameteren verdi er objektet som ble returnert av celleeditoren. Det vil være en Boolean-verdi dersom celleeditoren er en avkryssingsboks og en String-verdi dersom celleeditoren er et tekstfelt. Dersom celleeditoren er en komboboks, vil det være det objekt som brukeren valgte. I tilfelle den returnerte verdi ikke er kompatibel med den type som vedkommende kolonne inneholder, er det nødvendig med typekonvertering.

Programeksempel 3

I fila Tabellrendring.java som er gjengitt nedenfor, er planettabellen fra tidligere eksempler modifisert slik at fargene gjengis som paneler med vedkommende farge. Kolonnene for planetnavn, antall måner, og om det er gassplanet eller ikke, er gjort editerbare. For navn og indikasjon for gassplanet blir det brukt default-editor: tekstfelt for navn og avkryssingsboks for å indikere om det er gassplanet. For kolonnen for antall måner er det definert en komboboks som celleeditor. I denne versjonen av programmet er det dessuten lagt inn en ekstra kolonne som for hver planet viser et bilde (tegning) av planeten. Dette er objekter av type ImageIcon. Som forklart tidligere, vil hvert av disse bli rendret som en sentrert label. Følgende bildefiler er brukt: Mercury.gif, Venus.gif, Earth.gif, Mars.gif, Jupiter.gif, Saturn.gif, Uranus.gif, Neptune.gif, Pluto.gif. Slik programmet er skrevet, må disse ligge i en underkatalog bilder til den katalogen som programmets class-filer ligger i.

  1 import java.awt.*;
  2 import javax.swing.*;
  3 import javax.swing.table.*;
  4
  5 public class Tabellrendring
  6 {
  7   public static void main(String[] args)
  8   {
  9     EventQueue.invokeLater(new Runnable()
 10     {
 11       public void run()
 12       {
 13         JFrame vindu = new TabellrendringsFrame();
 14         vindu.setVisible(true);
 15       }
 16     });
 17   }
 18 }
 19
 20 //Planettabellmodellen spesifiserer verdiene, rendring og 
 21 //editeringsegenskapene for planetdataene.
 22 class Planettabellmodell2 extends AbstractTableModel
 23 {
 24   //Konstanter som brukes for å spesifisere hvilke kolonner som 
 25   //er editerbare.
 26   public static final int NAVNEKOLONNE = 0;
 27   public static final int MÅNEKOLONNE = 2;
 28   public static final int GASSKOLONNE = 3;
 29   private String[] kolonnenavn =
 30   {
 31     "Planet", "Radius", "Antall måner",
 32     "Gassplanet", "Fargekode", "Bilde"
 33   };
 34   private Object[][] celler =
 35   {
 36     {
 37       "Merkur", new Double(2440), new Integer(0),
 38       Boolean.FALSE, Color.yellow, new ImageIcon(
 39       getClass().getResource("bilder/Mercury.gif"))
 40     },
 41     {
 42       "Venus", new Double(6052), new Integer(0),
 43       Boolean.FALSE, Color.yellow, new ImageIcon(
 44       getClass().getResource("bilder/Venus.gif"))
 45     },
 46     {
 47       "Jorda", new Double(6378), new Integer(1),
 48       Boolean.FALSE, Color.blue, new ImageIcon(
 49       getClass().getResource("bilder/Earth.gif"))
 50     },
 51     {
 52       "Mars", new Double(3397), new Integer(2),
 53       Boolean.FALSE, Color.red, new ImageIcon(
 54       getClass().getResource("bilder/Mars.gif"))
 55     },
 56     {
 57       "Jupiter", new Double(71492), new Integer(16),
 58       Boolean.TRUE, Color.orange, new ImageIcon(
 59       getClass().getResource("bilder/Jupiter.gif"))
 60     },
 61     {
 62       "Saturn", new Double(60268), new Integer(18),
 63       Boolean.TRUE, Color.orange, new ImageIcon(
 64       getClass().getResource("bilder/Saturn.gif"))
 65     },
 66     {
 67       "Uranus", new Double(25559), new Integer(17),
 68       Boolean.TRUE, Color.blue, new ImageIcon(
 69       getClass().getResource("bilder/Uranus.gif"))
 70     },
 71     {
 72       "Neptun", new Double(24766), new Integer(8),
 73       Boolean.TRUE, Color.blue, new ImageIcon(
 74       getClass().getResource("bilder/Neptune.gif"))
 75     },
 76     {
 77       "Pluto", new Double(1137), new Integer(1),
 78       Boolean.FALSE, Color.black, new ImageIcon(
 79       getClass().getResource("bilder/Pluto.gif"))
 80     }
 81   };
 82
 83   //For å få satt vårt eget kolonnenavn.
 84   public String getColumnName(int kolonne)
 85   {
 86     return kolonnenavn[ kolonne];
 87   }
 88
 89   //For å informere tabellmodellen om kolonnenes datatyper.
 90   public Class getColumnClass(int kolonne)
 91   {
 92     return celler[ 0][ kolonne].getClass();
 93   }
 94
 95   public int getColumnCount()
 96   {
 97     return celler[ 0].length;
 98   }
 99
100   public int getRowCount()
101   {
102     return celler.length;
103   }
104
105   public Object getValueAt(int rad, int kolonne)
106   {
107     return celler[ rad][ kolonne];
108   }
109
110   //Spesifiserer hvilke celler som er editerbare.
111   public boolean isCellEditable(int rad, int kolonne)
112   {
113     return kolonne == NAVNEKOLONNE || kolonne == MÅNEKOLONNE
114             || kolonne == GASSKOLONNE;
115   }
116
117   //Nødvendig å implementere når tabellen er editerbar.
118   public void setValueAt(Object nyVerdi, int rad, int kolonne)
119   {
120     celler[ rad][ kolonne] = nyVerdi;
121   }
122 }
123
124 //Rendrer fargeceller som paneler i vedkommende farge.
125 class Fargecellerendrer implements TableCellRenderer
126 {
127   private JPanel panel = new JPanel();
128
129   public Component getTableCellRendererComponent(JTable tabell,
130           Object verdi, boolean erValgt, boolean harFokus, int rad,
131           int kolonne)
132   {
133     panel.setBackground((Color) verdi);
134     return panel;
135   }
136 }
137
138 class TabellrendringsFrame extends JFrame
139 {
140   public TabellrendringsFrame()
141   {
142     setTitle("Planettabell med egen tabellmodell");
143     setSize(500, 500);
144     setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
145
146     Planettabellmodell2 modell = new Planettabellmodell2();
147     JTable tabell = new JTable(modell);
148     tabell.setDefaultRenderer(Color.class, new Fargecellerendrer());
149     tabell.setRowHeight(100);
150
151     //Definerer en komboboks som celle-editor for antall måner.
152     JComboBox<Integer> månevelger = new JComboBox<>();
153     int min = 0, maks = 60;
154     for (int i = min; i <= maks; i++)
155     {
156       månevelger.addItem(new Integer(i));
157     }
158
159     TableCellEditor måneeditor = new DefaultCellEditor(månevelger);
160     //Installerer editoren for månekolonnen.
161     TableColumnModel kolonnemodell = tabell.getColumnModel();
162     TableColumn månekolonne =
163             kolonnemodell.getColumn(Planettabellmodell2.MÅNEKOLONNE);
164     månekolonne.setCellEditor(måneeditor);
165
166     Container c = getContentPane();
167     c.add(new JScrollPane(tabell), BorderLayout.CENTER);
168   }
169 }

Kjøring av programmet gir følgende vindu:

Oppdatering av tabelldata og tabellvisning

Dersom vi for vår tabell har definert en tabellmodell, så vil denne automatisk oppdage at vi foretar en editering av data i en tabellcelle. Editeringen resulterer i at tabellmodellen automatisk blir oppdatert med den nye verdien vi har lagt inn i vedkommende tabellcelle. Oppdateringen gjøres ved at det foretas et kall på tabellmodellens metode setValueAt, med den nye verdien som parameter, i tillegg til parametre for cellens rad- og kolonneindeks. (Som nevnt foran, er det nødvendig at vi har definert metoden setValueAt i tabellmodellen vår.) I tillegg vil automatisk visningen av tabellen bli oppdatert, slik at visningen er i samsvar med den nye verdien som er blitt lagret i tabellmodellen. Oppdatering av visningen skjer som følge av at tabellmodellen gjør kall på sin metode fireTableCellUpdated(rad, kolonne), med aktuelle parametre lik rad- og kolonneindeks til den celle som er blitt editert. Men hva om metoden setValueAt også endret verdien til en eller flere andre celler i tabellmodellen, fordi deres verdier er avhengige av verdien i den cellen vi har editert? De nye verdiene vil da ikke bli vist på skjermen, uten at vi gir et eget varsel til tabellmodellen om at cellene har endret verdi. Et slikt varsel gir vi ved å gjøre kall på metoden fireTableCellUpdated for disse cellene. (Vi skriver altså selv den nødvendige kode for å foreta kallene. For cellen som ble editert blir det automatisk foretatt et slikt kall.)

Programeksempel 4

Programmet Tabelloppdatering.java er en videreutvikling av programmet i forrige eksempel. Alle endringene er gjort i tabellmodellen. Hva endringene går ut på, er forklart i det følgende. (For at programmet skal virke riktig, behøves de samme bildefiler som i det foregående programeksempel. De må fortsatt ligge i en underkatalog bilder.)

Planetens fargekode er gjort avhengig av planetens antall måner. De forskjellige fargekodene som brukes er lagt inn i følgende array:

 34   private static Color[] fargekoder =
 35   {
 36     Color.red, Color.orange, Color.yellow, Color.green, Color.cyan,
 37     Color.blue, new Color(255, 0, 255)
 38   };

Hvordan fargekode avhenger av antall måner blir bestemt av følgende metode:

141   public void setFargekode(int rad)
142   {
143     int antMåner = ((Integer) celler[ rad][ MÅNEKOLONNE]).intValue();
144     if (antMåner <= 2)
145     {
146       celler[ rad][ FARGEKOLONNE] = fargekoder[ antMåner];
147     }
148     else if (antMåner > 2 && antMåner <= 7)
149     {
150       celler[ rad][ FARGEKOLONNE] = fargekoder[ 3];
151     }
152     else if (antMåner > 7 && antMåner <= 15)
153     {
154       celler[ rad][ FARGEKOLONNE] = fargekoder[ 4];
155     }
156     else if (antMåner > 15 && antMåner <= 20)
157     {
158       celler[ rad][ FARGEKOLONNE] = fargekoder[ 5];
159     }
160     else
161     {
162       celler[ rad][ FARGEKOLONNE] = fargekoder[ 6];
163     }
164   }

For å få satt riktig fargekode ved starten av programmet, er det definert følgende konstruktør for tabellmodellen:

 88   public Planettabellmodell3()
 89   {
 90     for (int i = 0; i < celler.length; i++)
 91     {
 92       setFargekode(i);
 93     }
 94   }

Antall måner er spesifisert i en editerbar celle. Vi må derfor sørge for at det skjer nødvendig oppdatering av fargekoden i tilfelle cellen for antall måner blir editert. Dette blir gjort ved at det er definert en konstant for fargekolonnens kolonneindeks, slik at vi kan referere til den på en forståelig måte, og ved at metoden setValueAt er redefinert til å foreta både nødvendig oppdatering av fargekode og oppdatering av fargekodens visning på skjermen, i tilfelle det er en celle for antall måner som blir editert:

 28   public static final int FARGEKOLONNE = 4;

  ...

130   //Nødvendig å implementere når tabellen er editerbar.
131   public void setValueAt(Object nyVerdi, int rad, int kolonne)
132   {
133     celler[ rad][ kolonne] = nyVerdi;
134     if (kolonne == MÅNEKOLONNE)
135     {
136       setFargekode(rad);
137       fireTableCellUpdated(rad, FARGEKOLONNE);
138     }
139   }

Lytting på brukervalg og håndtering av dette

Det er tidligere forklart hvordan vi kan bestemme mulighetene for brukeren til å velge rader, kolonner eller celler, flere om gangen, eller enkeltvis. Som for en JList kan vi lytte på brukervalg ved hjelp av et lytteobjekt av type ListSelectionListener. For å definere en slik, må vi skrive en klasse som implementerer interface ListSelectionListener ved at klassen inneholder en implementasjon av metoden

  public void valueChanged( ListSelectionEvent e )

For en JList registrerer vi lytteobjektet direkte ved bruk av dens metode addListSelectionListener. (Se beskrivelsen av dette i notatet Listebokser.) For en tabell kan vi ikke sette lytteobjekt av denne type direkte på tabellen. Isteden må vi ha tak i tabellens ListSelectionModel og registrere lytteobjektet for denne:

  ListSelectionModel lsm = tabell.getSelectionModel();
  lsm.addListSelectionListener( lytteobjekt );

I lytteobjektet kan det være behov for å kommunisere med vinduet (eller panelet) som viser tabellen, og det kan være behov for å hente ut data fra tabellens tabellmodell. For å få til den nødvendige kommunikasjonen, kan vi tilføre lytteobjektet en referanse til vinduet og eventuelt tabellmodellen via konstruktørparametre. Denne teknikken er brukt i følgende programeksempel.

Programeksempel 5

Programmet Tabellutvalg.java er en utvidelse av programmet i foregående eksempel. Det er lagt til funksjonalitet som virker på den måten at når brukeren velger en planet i tabellen (ved å klikke med musa på tabellraden som planetnavnet står i), så blir det i et tekstområde nederst i vinduet skrevet ut en tekst. Teksten inneholder data om den valgte planeten, data som er hentet fra tabellmodellen.

For å få til reaksjon på brukerens valg av tabellrad, er det definert en ListSelectionListener. Den er definert av følgende klasse.

170 class Utvalgslytter implements ListSelectionListener
171 {
172   private TableModel tabellmodell;
173   private TabellrendringsFrame3 vindu;
174
175   Utvalgslytter(TableModel m, TabellrendringsFrame3 v)
176   {
177     tabellmodell = m;
178     vindu = v;
179   }
180
181   public void valueChanged(ListSelectionEvent e)
182   {
183     // Venter med å gjøre noe til valget er avsluttet:
184     if (e.getValueIsAdjusting())
185     {
186       return;
187     }
188
189     ListSelectionModel lsm = (ListSelectionModel) e.getSource();
190     if (!lsm.isSelectionEmpty())
191     {
192       int valgtRad = lsm.getMinSelectionIndex();
193       String planetdata = "";
194       planetdata += "Planet: " + (String) tabellmodell.getValueAt(
195               valgtRad, Planettabellmodell4.NAVNEKOLONNE);
196       planetdata += "\nRadius: "
197               + (Double) tabellmodell.getValueAt(valgtRad,
198               Planettabellmodell4.RADIUSKOLONNE) + " km\n";
199       planetdata += "Antall måner: "
200               + (Integer) tabellmodell.getValueAt(valgtRad,
201               Planettabellmodell4.MÅNEKOLONNE);
202       planetdata += "\nEr ";
203       if (!((Boolean) tabellmodell.getValueAt(valgtRad,
204               Planettabellmodell4.GASSKOLONNE)))
205       {
206         planetdata += "ikke ";
207       }
208       planetdata += "gassplanet.";
209       vindu.visPlanetdata(planetdata);
210     }
211   }
212 }

I vindusklassen TabellrendringsFrame3 er det gjort følgende tilføyelser:

class TabellrendringsFrame3 extends JFrame
{
  private JTextArea utskriftsområde;
                           //tekstområde for utskrift av data

  public TabellrendringsFrame3()
  {
    ...  // som tidligere

    // Ønsker at brukeren bare kan velge én rad om gangen:
    tabell.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
    // Registrerer lytteobjekt for brukerens valg:
    ListSelectionModel lsm = tabell.getSelectionModel();
    lsm.addListSelectionListener( new Utvalgslytter( modell, this ) );

    ... // som tidligere

    // Oppretter utskriftsområdet og legger det inn i vinduet
    utskriftsområde = new JTextArea( 5, 30 );
    utskriftsområde.setEditable( false );

    Container c = getContentPane();
    c.add(new JScrollPane(tabell), BorderLayout.CENTER);
    c.add(new JScrollPane(utskriftsområde), BorderLayout.PAGE_END);
  }

  // Kalles opp fra lytteobjektet for brukervalg
  public void visPlanetdata(String data)
  {
    utskriftsområde.setText(data);
  }
}

Sortering av tabellrader

Fra og med Java-versjon 6 er det lagt inn funksjonalitet som gjør det svært enkelt å sortere tabellrader. For brukeren virker det på den måten at når det blir klikket på overskriften til en tabellkolonne, så blir tabellradene sortert i voksende rekkefølge for dataene i vedkommende tabellkolonne. Dersom det blir klikket én gang til, blir radene sortert i motsatt rekkefølge.

Den enkleste måten å aktivere radsortering på, er å tilføye følgende instruksjon for tabellen:

  tabell.setAutoCreateRowSorter(true);

der tabell er JTable-objektet. Da vil det for alle kolonnene bli brukt en standardsorterer for å bestemme rekkefølgen for radene. I noen tilfelle kan det imidlertid være ønskelig å få bedre kontroll over sorteringen. Det kan tenkes at for noen kolonner, for eksempel for slike som inneholder bilder, ønsker vi ingen sortering, eller det kan være at vi ønsker å definere selv hvordan sorteringen skal foretas. I slike tilfelle kan vi gå fram på følgende måte: Når tabellmodellen til tabellen er definert på vanlig måte i en klasse Tabellmodell, kan vi få til sorteringsfunksjonalitet ved å skrive følgende instruksjoner:

  Tabellmodell modell = new Tabellmodell();
  JTable tabell = new JTable( modell );
  TableRowSorter<Tabellmodell> sorterer = new TableRowSorter<>( modell );
  tabell.setRowSorter( sorterer );

Dersom vi for kolonne med kolonneindeks lik unntakskolonne ikke ønsker sortering, kan vi skrive

  sorterer.setSortable( unntakskolonne, false );

Som vanlig, så refererer kolonneindeks til tabellmodellen. Ønsker vi derimot selv å definere hvordan sorteringen skal foretas, må vi programmere noe mer. I første omgang skal vi se nærmere på hvordan sorteringen blir foretatt når vi ikke definerer den selv.

Hvordan blir sortering foretatt?

Det er lett å tenke seg hvordan sortering av numeriske data blir foretatt. Vi kan også lett tenke oss hvordan sammenlikning av tekstlig data blir foretatt for å avgjøre rekkefølge. Men hva med andre datatyper? Generelt gjelder at det brukes en Comparator for å avgjøre rekkefølge. Følgende regler blir brukt til å avgjøre hvilken Comparator som skal brukes for den kolonne som er valgt:

  1. Dersom det er blitt spesifisert en Comparator for vedkommende kolonne ved bruk av metoden setComparator, så brukes denne.
  2. Dersom klassen som blir returnert av metoden getColumnClass er av type String, så brukes den Comparator som blir returnert av Collator.getInstance().
  3. Dersom klassen som blir returnert av metoden getColumnClass implementerer interface Comparable, så brukes en Comparator som gjør kall på den tilhørende compareTo-metode.
  4. Dersom det er blitt spesifisert en TableStringConverter, så bruk den til å konvertere tabellverdiene til type String og bruk så den Comparator som blir returnert av Collator.getInstance().
  5. Ellers: Bruk den Comparator som blir returnert av Collator.getInstance() på resultatene fra tabellobjektenes toString-metode.

Programeksempel 6

Programeksempel 4 ovenfor brukte klassene Tabelloppdatering, Planettabellmodell3, Fargecellerendrer og TabellrendringsFrame2 for å vise en tabell. For å få radsortering til å virke på denne tabellen, kan vi bruke klassene Planettabellmodell3 og Fargecellerendrer om igjen uendret. Vindusklassen TabellrendringsFrame2 der tabellmodell og tabell blir opprettet, kan vi også beholde som den er, men vi må tilføye instruksjonene

    TableRowSorter<Planettabellmodell3> sorterer = new TableRowSorter<>(modell);
    tabell.setRowSorter(sorterer);
    sorterer.setSortable(Planettabellmodell3.BILDEKOLONNE, false);

I den siste av instruksjonene, der vi setter bildekolonnen ikke-sorterbar, får vi bruk for konstanten BILDEKOLONNE som ble lagt inn i tabellmodellen tidligere, men som det ikke har vært bruk for før nå.

Instruksjonene er lagt inn i den nye vindusklassen Radsorteringsvindu som er gjengitt nedenfor. I driverklassen Tabelloppdatering må vi opprette et vindusobjekt av den nye type Radsorteringsvindu. Dette er gjort i den nye driverklassen Radsortering.

 1 import java.awt.*;
 2 import javax.swing.*;
 3 import javax.swing.table.*;
 4
 5 public class Radsortering
 6 {
 7   public static void main(String[] args)
 8   {
 9     EventQueue.invokeLater(new Runnable()
10     {
11       public void run()
12       {
13         JFrame vindu = new Radsorteringsvindu();
14         vindu.setVisible(true);
15       }
16     });
17   }
18 }
19
20 class Radsorteringsvindu extends JFrame
21 {
22   public Radsorteringsvindu()
23   {
24     setTitle("Planettabell med radsortering");
25     setSize(500, 500);
26     setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
27
28     Planettabellmodell3 modell = new Planettabellmodell3();
29     JTable tabell = new JTable(modell);
30     tabell.setDefaultRenderer(Color.class, new Fargecellerendrer());
31     tabell.setRowHeight(100);
32     TableRowSorter<Planettabellmodell3> sorterer = new TableRowSorter<>(modell);
33     tabell.setRowSorter(sorterer);
34     sorterer.setSortable(Planettabellmodell3.BILDEKOLONNE, false);
35
36     //Definerer en komboboks som celle-editor for antall måner.
37     JComboBox<Integer> månevelger = new JComboBox<>();
38     int min = 0, maks = 60;
39     for (int i = min; i <= maks; i++)
40     {
41       månevelger.addItem(new Integer(i));
42     }
43
44     TableCellEditor måneeditor = new DefaultCellEditor(månevelger);
45     //Installerer editoren for månekolonnen.
46     TableColumnModel kolonnemodell = tabell.getColumnModel();
47     TableColumn månekolonne =
48             kolonnemodell.getColumn(Planettabellmodell3.MÅNEKOLONNE);
49     månekolonne.setCellEditor(måneeditor);
50
51     Container c = getContentPane();
52     c.add(new JScrollPane(tabell), BorderLayout.CENTER);
53   }
54 }

Følgende bilde viser tabellen etter at radene er blitt sortert slik at radiene kommer i voksende rekkefølge.

Egendefinert sorteringsrekkefølge

Dersom vi selv ønsker å definere hvordan verdiene i en tabellkolonne skal sorteres, må vi for verdiene i kolonnen definere en Comparator. Mer konkret betyr det at vi må definere en klasse som implementerer interface Comparator. Dette er definert som en såkalt generisk eller parametrisert datatype. Det er da en typeparameter som spesifiserer hvilken datatype som skal sammenliknes. (Bruk av generiske datatyper er nærmere omtalt i notatet Collections — bruk av generiske datatyper.) Vi skal definere sammenlikning av farger, altså Color-objekter. Det vil vi gjøre på den måten at dersom to farger har forskjellig blå-komponent, så skal den med minst blå-komponent komme først. For farger med lik blå-komponent skal den med minst grønn-komponent komme først. Ellers skal den med minst rød-komponent komme først. Vår Comparator kan vi da definere på følgende måte:

21 class Fargesorterer implements Comparator<Color>
22 {
23   public int compare(Color farge1, Color farge2)
24   {
25     int d = farge1.getBlue() - farge2.getBlue();
26     if (d != 0)
27     {
28       return d;
29     }
30     d = farge1.getGreen() - farge2.getGreen();
31     if (d != 0)
32     {
33       return d;
34     }
35     return farge1.getRed() - farge2.getRed();
36   }
37 }

Klassen TableRowSorter er også en generisk datatype. For å bruke den på den måten for tabellen vår og installere fargesorterer som definert av klassen ovenfor, kan vi for tabellen vår skrive følgende kode:

47     Planettabellmodell3 modell = new Planettabellmodell3();
48     JTable tabell = new JTable(modell);
49     tabell.setDefaultRenderer(Color.class, new Fargecellerendrer());
50     tabell.setRowHeight(100);
51     TableRowSorter<Planettabellmodell3> sorterer =
52             new TableRowSorter<>(modell);
53     tabell.setRowSorter(sorterer);
54     sorterer.setSortable(Planettabellmodell3.BILDEKOLONNE, false);
55     sorterer.setComparator(Planettabellmodell3.FARGEKOLONNE,
56             new Fargesorterer());

Den koden som er beskrevet ovenfor er lagt inn i programmet Fargekolonnesortering.java. Bildet nedenfor viser programvinduet etter at fargekolonnen er sortert.

Filtrering av tabellrader

En TableRowSorter kan også brukes til å filtrere bort rader som vi ikke ønsker å ta med i tabellvisningen. Til filtrering brukes objekter av type RowFilter. For å implementere filtrering på en tabell, må vi først opprette en radsorterer, slik det er beskrevet ovenfor. Så må vi gjøre kall på radsortererens metode setRowFilter, med ønsket RowFilter-objekt som parameter.

RowFilter-klassen inneholder noen static-metoder for opprettelse av diverse standardfiltre. Metoden numberFilter oppretter et filter som kan brukes til å plukke ut tabellrader med celler som oppfyller de numeriske betingelser vi bestemmer. Metoden dateFilter gir et filter som kan brukes til å filtrere ut tabellrader med celler som oppfyller bestemte tidsbetingelser, mens metoden regexFilter gir et filter som velger ut tabellradene med celler som passer med det regulære uttrykket vi bestemmer.

Som eksempel skal vi lage et filter som plukker ut de planetene i planettabellen vår som har minst én måne. Med samme tabellmodell som ovenfor kan vi da skrive følgende kode for å opprette tabell, radsorterer og radfilter:

    Planettabellmodell3 modell = new Planettabellmodell3();
    JTable tabell = new JTable( modell );
    tabell.setDefaultRenderer( Color.class, new Fargecellerendrer() );
    tabell.setRowHeight( 100 );
    TableRowSorter<Planettabellmodell3> sorterer =
    	new TableRowSorter<>(modell);
    tabell.setRowSorter(sorterer);
    sorterer.setSortable(Planettabellmodell3.BILDEKOLONNE, false);
    sorterer.setRowFilter(RowFilter.numberFilter(ComparisonType.AFTER, 0,
    		Planettabellmodell3.MÅNEKOLONNE));

Som første parameter i kallet på metoden numberFilter som oppretter filteret, er det her brukt konstanten ComparisonType.AFTER. Andre alternativer er ComparisonType.EQUAL, ComparisonType.NOT_EQUAL og ComparisonType.BEFORE. Som andre parameter skal det være et Number-objekt, men der er det tillatt å bruke en vanlig tallverdi.

Fullstendig program som bruker dette er gitt i fila Maanefiltrering.java. Følgende bilde viser startvinduet for programmet. Vi ser at planetene Merkur og Venus, som ikke har noen måner, er filtrert bort.

Egendefinert filter

Som alternativ til å bruke de filtrene vi kan få av verktøymetodene i RowFilter-klassen, kan vi definere våre egne filtre. Det gjør vi ved å definere en subklasse til den abstrakte klassen RowFilter<M,I>, der vi implementerer metoden

  public boolean include(RowFilter.Entry<? extends M, ? extends I> entry)

Som eksempel på dette skal vi definere et filter som for planetene våre filtrerer ut alle gassplanetene, det vil si bare viser gassplanetene i planettabellen. Det ønskede filteret kan defineres på følgende måte:

20 class Gassfilter extends RowFilter<Planettabellmodell3, Integer>
21 {
22   public boolean include(RowFilter.Entry<? extends Planettabellmodell3,
23           ? extends Integer> element)
24   {
25     Planettabellmodell3 modell = element.getModel();
26     Integer rad = element.getIdentifier();
27     return ((Boolean) modell.getValueAt(rad,
28             Planettabellmodell3.GASSKOLONNE)).booleanValue();
29   }
30 }

Koden for å opprette tabell, tilordne radsorterer og filter for denne, kan vi da skrive på følgende måte:

41     Planettabellmodell3 modell = new Planettabellmodell3();
42     JTable tabell = new JTable(modell);
43     tabell.setDefaultRenderer(Color.class, new Fargecellerendrer());
44     tabell.setRowHeight(100);
45     TableRowSorter<Planettabellmodell3> sorterer =
46             new TableRowSorter<>(modell);
47     tabell.setRowSorter(sorterer);
48     sorterer.setSortable(Planettabellmodell3.BILDEKOLONNE, false);
49     sorterer.setRowFilter(new Gassfilter());

Fullstendig program er inneholdt i fila Gassfiltrering.java. Følgende bilde viser startvinduet for programmet. Vi ser at det bare er gassplanetene som er tatt med i tabellen.

Problem: Forskjell i radindeksering mellom tabellmodell og visning

Sortering av tabellrader som forklart ovenfor vil ikke påvirke den indekseringen som tabellradene har i tabellmodellen. Etter sortering vil derfor rekkefølgen til tabellradene slik de vises på skjermen (vanligvis) ikke stemme overens med den rekkefølgen som radene har i tabellmodellen. Dersom vi på visningen velger (markerer) en rad ved å klikke på den med musa, vil raden som regel ha en annen radindeks i tabellmodellen enn den har i visningen på skjermen. JTable-metodene convertRowIndexToModel(indeks) og convertRowIndexToView(indeks) foretar konvertering mellom de to indeksene. Denne teknikken får vi bruk for dersom vi vil implementere radsortering i den tabellen som vises i Programeksempel 5 ovenfor. Tabellmodellen Planettabellmodell4 kan vi bruke som den er. Men i lytteklassen Utvalgslytter trenger vi en referanse til tabellobjektet for å få gjort kall på de nevnte konverteringsmetodene for radindekser. (I vårt tilfelle er det bare metoden convertRowIndexToModel vi har bruk for.) De nødvendige endringene er lagt inn i den nye lytteklassen Planetvalgslytter som er gjengitt nedenfor. Vindusklassen TabellrendringsFrame3 må endres slik at det til tabellen blir tilordnet et lytteobjekt av den nye type Planetvalgslytter, og slik at det til tabellen blir tilordnet en radsorterer. Dette er gjort i den nye vindusklassen TabellrendringsFrame4 som er gjengitt nedenfor. Den nye driverklassen Tabellsortering må opprette et vindu av den nye typen. Dette er gjort i koden som er gjengitt nedenfor.

  1 import java.awt.*;
  2 import javax.swing.*;
  3 import javax.swing.event.*;
  4 import javax.swing.table.*;
  5
  6 public class Tabellsortering
  7 {
  8   public static void main(String[] args)
  9   {
 10     EventQueue.invokeLater(new Runnable()
 11     {
 12       public void run()
 13       {
 14         JFrame vindu = new TabellrendringsFrame4();
 15         vindu.setVisible(true);
 16       }
 17     });
 18   }
 19 }
 20
 21 class Planetvalgslytter implements ListSelectionListener
 22 {
 23   private TableModel tabellmodell;
 24   private TabellrendringsFrame4 vindu;
 25   private JTable tabell;
 26
 27   Planetvalgslytter(TableModel m, TabellrendringsFrame4 v, JTable t)
 28   {
 29     tabellmodell = m;
 30     vindu = v;
 31     tabell = t;
 32   }
 33
 34   public void valueChanged(ListSelectionEvent e)
 35   {
 36     // Venter med å gjøre noe til valget er avsluttet:
 37     if (e.getValueIsAdjusting())
 38     {
 39       return;
 40     }
 41
 42     ListSelectionModel lsm = (ListSelectionModel) e.getSource();
 43     if (!lsm.isSelectionEmpty())
 44     {
 45       int valgtRad = lsm.getMinSelectionIndex();
 46       valgtRad = tabell.convertRowIndexToModel(valgtRad);
 47       String planetdata = "";
 48       planetdata += "Planet: " + (String) tabellmodell.getValueAt(
 49               valgtRad, Planettabellmodell4.NAVNEKOLONNE);
 50       planetdata += "\nRadius: "
 51               + (Double) tabellmodell.getValueAt(
 52               valgtRad, Planettabellmodell4.RADIUSKOLONNE) + " km\n";
 53       planetdata += "Antall måner: "
 54               + (Integer) tabellmodell.getValueAt(valgtRad,
 55               Planettabellmodell4.MÅNEKOLONNE);
 56       planetdata += "\nEr ";
 57       if (!((Boolean) tabellmodell.getValueAt(valgtRad,
 58               Planettabellmodell4.GASSKOLONNE)))
 59       {
 60         planetdata += "ikke ";
 61       }
 62       planetdata += "gassplanet.";
 63       vindu.visPlanetdata(planetdata);
 64     }
 65   }
 66 }
 67
 68 class TabellrendringsFrame4 extends JFrame
 69 {
 70   private JTextArea utskriftsområde;
 71
 72   public TabellrendringsFrame4()
 73   {
 74     setTitle("Planettabell med egen tabellmodell");
 75     setSize(500, 500);
 76     setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 77
 78     Planettabellmodell4 modell = new Planettabellmodell4();
 79     JTable tabell = new JTable(modell);
 80     tabell.setDefaultRenderer(Color.class, new Fargecellerendrer());
 81     tabell.setRowHeight(100);
 82     tabell.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
 83     ListSelectionModel lsm = tabell.getSelectionModel();
 84     lsm.addListSelectionListener(
 85             new Planetvalgslytter(modell, this, tabell));
 86
 87     RowSorter<Planettabellmodell4> sorterer =
 88             new TableRowSorter<>(modell);
 89     tabell.setRowSorter(sorterer);
 90
 91     //Definerer en komboboks som celle-editor for antall måner.
 92     JComboBox<Integer> månevelger = new JComboBox<>();
 93     int min = 0, maks = 60;
 94     for (int i = min; i <= maks; i++)
 95     {
 96       månevelger.addItem(new Integer(i));
 97     }
 98
 99     TableCellEditor måneeditor = new DefaultCellEditor(månevelger);
100     //Installerer editoren for månekolonnen.
101     TableColumnModel kolonnemodell = tabell.getColumnModel();
102     TableColumn månekolonne =
103             kolonnemodell.getColumn(Planettabellmodell4.MÅNEKOLONNE);
104     månekolonne.setCellEditor(måneeditor);
105
106     utskriftsområde = new JTextArea(5, 30);
107     utskriftsområde.setEditable(false);
108
109     Container c = getContentPane();
110     c.add(new JScrollPane(tabell), BorderLayout.CENTER);
111     c.add(new JScrollPane(utskriftsområde), BorderLayout.PAGE_END);
112   }
113
114   public void visPlanetdata(String data)
115   {
116     utskriftsområde.setText(data);
117   }
118 }

Følgende bilde viser tabellen etter at radene først er blitt sortert slik at planetnavnene kommer i alfabetisk rekkefølge og deretter raden for Mars er blitt valgt.

Hvordan printe ut tabeller

Fra og med javaversjon 5 er det tilføyd print-metoder til JTable-klassen som gjør det enkelt å printe ut tabeller på printer. Vi skal se på noen av mulighetene som finnes.

Printeksempel 1

I dette eksemplet skal vi legge inn nødvendig funksjonalitet for å få printet ut tabellen i det siste eksemplet ovenfor om sortering av tabellrader. Det eneste som kreves for å få printet ut tabellen, er å gjøre kall på dens print-metode. Det vil da komme opp en dialogboks på vanlig måte for å velge printer og utskriftsalternativer, tilsvarende som vist på bildet under.

Vi må imidlertid velge en måte for brukeren til å kunne kalle opp print-metoden, slik at print-dialogen blir vist. Her har vi selvsagt mange muligheter. I eksemplet er det valgt å opprette en printmeny med "Print tabell" som eneste alternativ. Til dette er det også tilordnet tastaturalternativet Ctrl-P som vi er vant med fra programmer ellers for å foreta utskrift. Koden for dette ser ut som følger:

 70     JMenu meny = new JMenu("Print");
 71     JMenuItem utskrift = new JMenuItem("Print tabell");
 72     utskrift.addActionListener(new Printlytter());
 73     meny.add(utskrift);
 74     utskrift.setAccelerator(KeyStroke.getKeyStroke(
 75             KeyEvent.VK_P, ActionEvent.CTRL_MASK));
 76     JMenuBar menylinje = new JMenuBar();
 77     menylinje.add(meny);
 78     setJMenuBar(menylinje);

Lytteobjektet er programmert på følgende måte:

 90   class Printlytter implements ActionListener
 91   {
 92     public void actionPerformed(ActionEvent e)
 93     {
 94       try
 95       {
 96         tabell.print();
 97       }
 98       catch (PrinterException pe)
 99       {
100         pe.printStackTrace();
101       }
102     }
103   }

Fullstendig program finnes i fila Tabellprinting.java.

Merknad Et alternativ til å bruke vanlig meny som det er gjort ovenfor, kan være å bruke en popp-opp-meny. For slike vil imidlertid ikke hurtigvalget Ctrl-P virke uten at menyen er synlig på skjermen.

Printeksempel 2: Topp og bunntekst, alternerende bakgrunnsfarge på radene

For å øke lesbarheten av en tabell, kan vi gi tabellradene alternerende bakgrunnsfarge, som følgende bilde viser et eksempel på:

Programmet bruker den samme tabellmodell som foregående eksempel, men de to siste kolonnene er tatt bort. Alternerende bakgrunnsfarge kan vi selvsagt legge inn uavhengig av om vi vil ha funksjonalitet for å printe ut tabellen eller ikke. For å få det til, må vi definere en subklasse til JTable der vi redefinerer metoden prepareRenderer, tilsvarende som det er vist i kodeeksemplet nedenfor, hentet fra programmet som gir det viste bildet. Den vekslende bakgrunnsfargen kan vi selvsagt velge fritt. Programmet bruker et tabellobjekt av den definerte subklassetypen.

178 class Radalterneringstabell extends JTable
179 {
180   public Radalterneringstabell(TableModel modell)
181   {
182     super(modell);
183   }
184
185   public Component prepareRenderer(
186           TableCellRenderer rendrer, int rad, int kolonne)
187   {
188     Component c = super.prepareRenderer(rendrer, rad, kolonne);
189     if (rad % 2 == 0 && !isCellSelected(rad, kolonne))
190     {
191       c.setBackground(Color.LIGHT_GRAY);
192     }
193     else
194     {
195       c.setBackground(getBackground());
196     }
197     return c;
198   }
199 }

Dersom vi printer ut en tabell med alternerende bakgrunnsfarge, vil vi normalt få en utskrift der det også er alternerende bakgrunnsfarge. Neste eksempel viser imidlertid hvordan vi kan få utskrift uten denne alterneringen. I inneværende eksempel skal vi utstyre utskriften med topptekst og bunntekst. Det får vi til ved å bruke print-metoden som har følgende format:

  public boolean print(JTable.PrintMode printMode,
                     MessageFormat headerFormat,
                     MessageFormat footerFormat)
              throws PrinterException

I programmet Radalternering.java ser print-kallet ut som følger:

166         tabell.print(JTable.PrintMode.NORMAL,
167                 new MessageFormat("Planettabell"),
168                 new MessageFormat("side{0,number}"));

Toppteksten "Planettabell" blir plassert sentrert over tabellen og vises med fet skrift. Bunnteksten gir sidenummerering side 1, side 2, ... . I dette eksemplet får vi dessuten, som nevnt, utskrift med alternerende bakgrunnsfarge for radene. Parameterverdien JTable.PrintMode.NORMAL har som virkning at tabellen blir skrevet ut med sin aktuelle størrelse. Om nødvendig blir rader og kolonner fordelt over flere sider. Den alternative parameterverdien JTable.PrintMode.FIT_WIDTH skalerer om nødvendig tabellen til en smalere bredde, slik at alle kolonnene får plass innenfor papirbredden. Radene fordeles om nødvendig over flere sider.

Kjør programmet og se hvordan radenes bakgrunnsfarge skifter etter som du velger (klikker på) vedkommende rad eller ikke!

Printeksempel 3: Forskjellig bakgrunnsfarge på skjermvisning og utskrift

I programmet PrintUtenRadalternering.java blir det på skjermen vist samme tabell som i forrige eksempel. Det som er annerledes, er at utskrift av tabellen blir gjort uten alternerende bakgrunnsfarge på radene. Alle sammen har hvit bakgrunnsfarge. For å få til dette, er det definert en subklasse til JTable. Den har følgende innhold:

 25 class FancyRadalterneringstabell extends JTable
 26 {
 27   private boolean printerUt = false;
 28
 29   public FancyRadalterneringstabell(TableModel modell)
 30   {
 31     super(modell);
 32   }
 33
 34   public void print(Graphics g)
 35   {
 36     printerUt = true;
 37     try
 38     {
 39       super.print(g);
 40     }
 41     finally
 42     {
 43       printerUt = false;
 44     }
 45   }
 46
 47   public Component prepareRenderer(
 48           TableCellRenderer rendrer, int rad, int kolonne)
 49   {
 50     Component c = super.prepareRenderer(rendrer, rad, kolonne);
 51     //bruk vanlig bakgrunn i tilfelle tabellen blir printet ut
 52     if (printerUt)
 53     {
 54       c.setBackground(getBackground());
 55     }
 56     else if (rad % 2 == 0 && !isCellSelected(rad, kolonne))
 57     {
 58       c.setBackground(Color.LIGHT_GRAY);
 59     }
 60     else if (isCellSelected(rad, kolonne))
 61     {
 62       c.setBackground(getSelectionBackground());
 63     }
 64     else
 65     {
 66       c.setBackground(getBackground());
 67     }
 68     return c;
 69   }
 70 }

Programmet bruker et objekt av den definerte subklassetypen. Kallet på print-metoden er nøyaktig som i foregående program. Kjør også dette programmet og se på hvordan radenes bakgrunnsfarge skifter etter som du velger vedkommende rad eller ikke!

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

Forrige avsnitt Neste avsnitt  Start på kapittel om spesialiserte komponenter for grafiske brukergrensesnitt