![]() |
![]() |
Start på kapittel om spesialiserte komponenter for grafiske brukergrensesnitt |
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:
toString
-metode.Vector
.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.)
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
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.
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.
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.
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.
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.
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, ... .
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:
Boolean
: rendres som avkryssingsboks.Number
(felles superklasse for
Double, Integer
etc.): rendres som høyrejustert
label.Double
og Float
brukes det dessuten et NumberFormat
-objekt
tilpasset standarden på vedkommende lokalitet.Date
: rendres på en label ved at det brukes et
DateFormat
-objekt som gir kortform for dato og tid, tilpasset
vedkommende lokalitet.ImageIcon
: rendres som sentrert label.Object
: rendres som label som viser
objektets string-verdi (gitt av toString
-metoden).For å opprette tabell på grunnlag av egendefinert tabellmodell, må vi altså gjøre følgende:
class Tabellmodell extends AbstractTableModel { public int getRowCount() { ... } public int getColumnCount() { ... } public Object getValueAt( int rad, int kolonne ) { ... } //eventuelt også public String getColumnName( int kolonne ) { ... } public Class getColumnClass( int kolonne ) { ... } . . . }
Tabellmodell modell = new Tabellmodell(); JTable tabell = new JTable( modell );
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.
For å definere en cellerendrer må vi implementere
interface
TableCellRenderer
.
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() );
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).
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.
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:
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.)
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 }
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.
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); } }
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.
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:
Comparator
for vedkommende
kolonne ved bruk av metoden setComparator
, så brukes denne.getColumnClass
er av type String
, så brukes den Comparator
som
blir returnert av Collator.getInstance()
.
getColumnClass
implementerer interface Comparable
, så brukes en
Comparator
som gjør kall på den tilhørende
compareTo
-metode.TableStringConverter
,
så bruk den til å konvertere tabellverdiene til type String
og
bruk så den Comparator
som
blir returnert av Collator.getInstance()
.Comparator
som
blir returnert av Collator.getInstance()
på resultatene fra
tabellobjektenes toString
-metode.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.
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.
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.
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.
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.
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.
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.
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!
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
![]() |
![]() |
Start på kapittel om spesialiserte komponenter for grafiske brukergrensesnitt |