All Classes Namespaces Files Functions Variables
StringGrid.java
Go to the documentation of this file.
1 package fi.jyu.mit.fxgui;
2 
3 
4 import java.util.ArrayList;
5 import java.util.Collection;
6 import java.util.HashMap;
7 
8 import javax.swing.SwingConstants;
9 
10 import javafx.application.Platform;
11 import javafx.beans.property.ListProperty;
12 import javafx.beans.property.SimpleListProperty;
13 import javafx.beans.property.SimpleStringProperty;
14 import javafx.beans.property.StringProperty;
15 import javafx.collections.FXCollections;
16 import javafx.collections.ListChangeListener;
17 import javafx.collections.ObservableList;
18 import javafx.fxml.FXML;
19 import javafx.geometry.Pos;
20 import javafx.scene.control.TableCell;
21 import javafx.scene.control.TableColumn;
22 import javafx.scene.control.TableRow;
23 import javafx.scene.control.TableView;
24 import javafx.scene.control.TextField;
25 import javafx.scene.input.KeyCode;
26 
27 /**
28  * StringGrid joka näyttää merkkijonotaulukon sisällön.
29  * Taulukkoon voidaan jokaista riviä kohti tallentaa myös jokin olio.
30  *
31  * grid.add(har,rivi); // rivillä sarakkeiden merkkijonot
32  *
33  * Kullekin solulle voidaan antaa oma css-tyyli. Ongelma: mikäli
34  * ei kuunnella hiiren klikkausta otsikossa ja tehdä refresh, niin tämä
35  * menee sekaisin jos rivit lajitellaan (jos eri riveillä eri css).
36  * TYPE saa olla ?, mikäli tallennettavia olioita ei käytetä mihinkään
37  *
38  * Soluihin pääsee käsiksi alkuperäisen (lajittelemattoman) taulukon
39  * rivi- ja sarakeindekseillä. Ellei erikseen mainita, parametreissa
40  * olevat row- ja col-indeksit ovat nimenomaan alkueräisiä indeksejä.
41  *
42  * Dataa voidaan lisätä myös ilman merkkijonoja.
43  *
44  * grid.add(jasenet);
45  *
46  * Tällöin on vähintään kerrottavat miten merkkijonot saadaan tietylle riville ja
47  * sarekkeelle, esim tyyliin:
48  *
49  * grid.setOnCellString( (g, jasen, defValue, r, c) -> jasen.anna(c+eka) );
50  *
51  * Mikäli halutaan lajitella sarakkeita muuta kuin merkkijonojärjestyksessä,
52  * on kerrottava lajittelumerkkijono tyyliin:
53  *
54  * grid.setOnCellValue( (g, jasen, defValue, r, c) -> jasen.getAvain(c+eka) );
55  *
56  * Katso myös: <a href="https://tim.jyu.fi/view/kurssit/tie/ohj2/tyokalut/JavaFX/fxgui/StringGrid">StringGrid TIMissä</a>
57  *
58  * @param <TYPE> minkä tyyppisiä tietoja liitetään riveihin
59  *
60  * @author vesal
61  * @version 29.12.2015
62  * @version 25.3.2015/vl - TYPE mukaan ja lisää ominaisuuksia
63  */
64 public class StringGrid<TYPE> extends TableView<StringGrid.GridRowItem<TYPE>> {
65 
66  /**
67  * Alkio yhdelle riville.
68  * Pidetään yllä itse olioita, listaa merkkijoista,
69  * alkuperäisestä rivinumerosta ja solu css:stä.
70  * @param <TYPE> mikä tieto tallennetaan riviin
71  */
72  @SuppressWarnings("javadoc")
73  public static class GridRowItem<TYPE> {
74  private ObservableList<String> rowSts;
75  private TYPE item;
76  private int rowNr;
77  private HashMap<Integer, String> styleClasses;
78 
79  /**
80  * @param row mikä teksti tallennetaan
81  * @param item mikä alkio tähämn kohti
82  * @param rowNr mikä on alkuperäinen rivinumero
83  */
84  public GridRowItem(String[] row, TYPE item, int rowNr) {
85  this.rowSts = FXCollections.observableArrayList();
86  this.setRow(row);
87  this.setItem(item);
88  this.setRowNr(rowNr);
89  }
90 
91  public void setItem(TYPE item) {
92  this.item = item;
93  }
94 
95  /**
96  * @return kohdalla oleva alkio
97  */
98  public TYPE getItem() {
99  return item;
100  }
101 
102  public int getRowNr() {
103  return rowNr;
104  }
105 
106  public void setRowNr(int rowNr) {
107  this.rowNr = rowNr;
108  }
109 
110  public ObservableList<String> getRow() {
111  return rowSts;
112  }
113 
114  public void setRow(String[] row) {
115  this.rowSts.setAll(row);
116  }
117 
118  public String getCellClass(int col) {
119  if ( styleClasses == null ) return null;
120  return styleClasses.get(col);
121  }
122 
123  public void setCellClass(String cellClass, int col) {
124  if ( styleClasses == null ) styleClasses = new HashMap<>();
125  styleClasses.put(col, cellClass);
126  }
127 
128 
129  public String get(int col) {
130  if ( col < 0 || col >= rowSts.size() ) return "";
131  return rowSts.get(col);
132  }
133 
134  public void set(int col, String s) {
135  if ( col <0 || col >= rowSts.size() ) return;
136  rowSts.set(col,s);
137  }
138 
139  }
140 
141 
142  /**
143  * Rajapinta solun muokkaukselle
144  * @param <TYPE> minkätyylisiä
145  */
146  public interface OnGridCell<TYPE> {
147  /**
148  * Metodi jota kutsutaan kun tarvitaan solun sisältöä
149  * @param grid taulukko jota käytetään
150  * @param item mikä alkio liitetty tähän
151  * @param defValue solulle ehdotettu arvo
152  * @param row taulukon rivi johon liittyy
153  * @param column taulukon sarake johon liittyy
154  * @return solulle haluttu arvo, null: käytetään oletusta
155  */
156  String onGetCell(StringGrid<TYPE> grid, TYPE item, String defValue, int row, int column);
157  }
158 
159 
160  /**
161  * Rajapinta solun editoinnille
162  * @param <TYPE> minkätyylisiä
163  */
164  public interface OnGridLiveEdit<TYPE> {
165  /**
166  * Metodi jota kutsutaan kun solua muokataan
167  * @param grid taulukko jota muokattiin
168  * @param item mikä alkio liitetty tähän
169  * @param newValue solulle ehdotettu uusi arvo
170  * @param row taulukon rivi jota muokattiin
171  * @param column taulukon sarake jota mmuokattiin
172  * @param textField tekstikenttä jota muokataan
173  * @return solulle haluttu arvo, null: ei muutosta
174  */
175  String onEdit(StringGrid<TYPE> grid, TYPE item, String newValue, int row, int column, TextField textField);
176  }
177 
178 
179  private ListProperty<GridRowItem<TYPE>> rivitProp = new SimpleListProperty<>();
180  private ObservableList<GridRowItem<TYPE>> tableRows = FXCollections.observableArrayList();
181  private StringProperty rivitJono = new SimpleStringProperty();
182  private String emptyStyleClass = null;
183  private HashMap<Integer,Pos> alignments = new HashMap<>();
184 
185  /** Käsittelijä muokkauksille */
186  protected OnGridCell<TYPE> onGridEdit;
187  /** Käsittelijä reaaliaikaisille muokkauksille */
188  protected OnGridLiveEdit<TYPE> onGridLiveEdit;
189  /** Käsittelijä lajitteluarvolle */
190  protected OnGridCell<TYPE> onCellValue;
191  /** Käsittelijä solun merkkijonolle */
192  protected OnGridCell<TYPE> onCellString;
193 
194 
195  /**
196  * Alustetaan taulukko
197  */
198  public StringGrid() {
199  setItems(tableRows);
200  }
201 
202 
203  /**
204  * @return mitä kutsutaan kun halutaan lajitteluarvo
205  */
206  public OnGridCell<TYPE> getOnCellValue() {
207  return onCellValue;
208  }
209 
210 
211  /**
212  * @param onCellValue mitä kutsutaan kun halutaan näytettävä arvo
213  */
214  public void setOnCellValue(OnGridCell<TYPE> onCellValue) {
215  this.onCellValue = onCellValue;
216  }
217 
218 
219  /**
220  * @return mitä kutsutaan kun halutaan solun sisältöä merkkijonona
221  */
222  public OnGridCell<TYPE> getOnCellString() {
223  return onCellString;
224  }
225 
226 
227  /**
228  * @param onCellString mitä kutsutaan kun halutaan lajitteluarvo
229  */
230  public void setOnCellString(OnGridCell<TYPE> onCellString) {
231  this.onCellString = onCellString;
232  }
233 
234 
235  /**
236  * @return muokkauksen käsittelijä
237  */
238  public OnGridCell<TYPE> getOnGridEdit() {
239  return onGridEdit;
240  }
241 
242 
243  /**
244  * @param onGridEdit uusi käsittelijä muokkaukselle
245  */
246  public void setOnGridEdit(OnGridCell<TYPE> onGridEdit) {
247  this.onGridEdit = onGridEdit;
248  }
249 
250 
251  /**
252  * @return muokkauksen käsittelijä
253  */
254  public OnGridLiveEdit<TYPE> getOnGridLiveEdit() {
255  return onGridLiveEdit;
256  }
257 
258 
259  /**
260  * @param onGridLiveEdit uusi käsittelijä muokkaukselle
261  */
262  public void setOnGridLiveEdit(OnGridLiveEdit<TYPE> onGridLiveEdit) {
263  this.onGridLiveEdit = onGridLiveEdit;
264  }
265 
266 
267  /**
268  * Initializes the control.
269  */
270  @FXML
271  public void initialize() {
272  itemsProperty().bind(rivitProp);
273  rivitProp.set(tableRows);
274  }
275 
276 
277  /**
278  * Asetetaan taulukon sisältö.
279  * @param data monirivinen lista, jossa 1. rivi on otsikot ja muut dataa. Alkiot eroteltu |-merkillä.
280  */
281  public void setRivit(String data) {
282  this.rivitJono.set(data);
283  String[] rows = data.split("\n");
284  this.getColumns().clear();
285  tableRows.clear();
286  if ( rows.length == 0 ) return;
287  String[] headings = rows[0].split("\\|");
288  initTable(headings);
289  for (int i=1; i<rows.length; i++) {
290  add(null, rows[i].split("\\|"));
291  // rivit.add(new GridItem<TYPE>(tiedot[i].split("\\|"),null, i-1));
292  }
293  }
294 
295 
296  /**
297  * Lisätään taulukkoon otsikot-mukaisesti sarakkeet ja
298  * @param headings sarakkaiden määrä ja otsikot tästä
299  */
300  public void initTable(String... headings) {
301  tableRows.clear();
302  alignments.clear();
303  getColumns().clear();
304  for (int i = 0; i < headings.length; i++) {
305  TableColumn<GridRowItem<TYPE>, String> tc = new TableColumn<GridRowItem<TYPE>, String>(headings[i]);
306  final int origCol = i;
307 
308  tc.setCellValueFactory((rivi) -> {
309  GridRowItem<TYPE> tableRow = rivi.getValue();
310  String s = get(rivi.getValue().getRowNr(),origCol);
311  if ( onCellString != null ) s = onCellString.onGetCell(this, tableRow.getItem(), s, tableRow.getRowNr(), origCol);
312  if ( onCellValue == null )
313  return new SimpleStringProperty(s.replace(" ", "."));
314  // String onEdit(StringGrid<TYPE> grid, TYPE item, String newValue, int row, int column);
315  String val = this.onCellValue.onGetCell(this, tableRow.getItem(), s, tableRow.getRowNr(), origCol);
316  if ( val == null ) val = s;
317  return new SimpleStringProperty(val.replace(" ", "."));
318  });
319 
320  tc.setPrefWidth(90);
321  tc.setId(""+i);
322  getColumns().add(tc);
323  // tc.setCellFactory(TextFieldTableCell.forTableColumn());
324  tc.setCellFactory(column -> new StringGridCell<TYPE>(origCol));
325  tc.setOnEditCommit( (t) -> {
326  StringGrid<TYPE> table = (StringGrid<TYPE>)t.getTableView();
327  GridRowItem<TYPE> tableRow = table.getItems().get(t.getTablePosition().getRow());
328  String s = t.getNewValue();
329  if ( s == null ) return;
330  int r = table.findRowNr(tableRow);
331  if ( table.onGridEdit != null ) s = table.onGridEdit.onGetCell(table,tableRow.getItem(), s, r, origCol);
332  if ( s == null ) return;
333  tableRow.set(origCol, s);
334  table.refresh();
335  }
336  );
337  }
338  }
339 
340 
341  /**
342  * Lisätään uusi alkio taulukkoon
343  * @param obj mihin objektiin viitataan
344  * @param items lisättävät merkkijonot
345  */
346  public void add(TYPE obj, String...items) {
347  tableRows.add(new GridRowItem<TYPE>(items,obj, tableRows.size()));
348  refresh();
349  }
350 
351 
352  /**
353  * Lisätään uusi alkio taulukkoon. Jotta tämä toimisi, pitää olla
354  * tehtynä vähintään set setOnCellString
355  * @param obj mihin objektiin viitataan
356  */
357  public void add(TYPE obj) {
358  add(obj,new String[0]);
359  }
360 
361 
362  /**
363  * Lisätään uudet jonot taulukkoon
364  * @param items lisättävät merkkijonot
365  */
366  public void add(String...items) {
367  add(null,items);
368  }
369 
370 
371  /**
372  * Lisätään uudet alkiot taulukkoon. Jotta tämä toimisi, pitää olla
373  * tehtynä vähintään set setOnCellString
374  * @param objs mitkä oliot lisätään
375  */
376  public void add(Collection<TYPE> objs) {
377  for (TYPE obj: objs) add(obj);
378  }
379 
380 
381  /**
382  * Lisätään uudet alkiot taulukkoon. Jotta tämä toimisi, pitää olla
383  * tehtynä vähintään set setOnCellString
384  * @param objs mitkä oliot lisätään
385  */
386  @SuppressWarnings("unchecked")
387  public void add(TYPE... objs) {
388  for (TYPE obj: objs) add(obj);
389  }
390 
391 
392  /**
393  * Poistaa kaikki rivit;
394  */
395  public void clear() {
396  tableRows.clear();
397  refresh();
398  }
399 
400 
401  /**
402  * @return rivijonon sisältö.
403  */
404  public String getRivit() {
405  // return rivitJono.get();
406  StringBuilder tulos = new StringBuilder();
407  String erotin = "";
408  for (TableColumn<GridRowItem<TYPE>, ?> tc : getColumns() ) {
409  tulos.append(erotin + tc.getText());
410  erotin = "|";
411  }
412  tulos.append("\n");
413  int len = tableRows.size();
414  for (int i=0; i<len; i++) {
415  GridRowItem<TYPE> rivi = findTableRow(i);
416  if ( rivi == null ) continue;
417  erotin = "";
418  for (String s: rivi.getRow()) {
419  tulos.append(erotin + s);
420  erotin = "|";
421  }
422  tulos.append("\n");
423  }
424  return tulos.toString();
425  }
426 
427 
428  /**
429  * Asettaa sarakkeet lajiteltavaksi
430  * @param col mikä sarake, -1 on kaikki
431  * @param sortable lajiteltava vai ei
432  */
433  public void setSortable(int col, boolean sortable) {
434  for (TableColumn<GridRowItem<TYPE>, ?> tc : getColumns() ) {
435  if ( col == -1 || tc.getId().equals(""+col))
436  tc.setSortable(sortable);
437  }
438  }
439 
440 
441  /**
442  * Asetetaan valitulle sarakkeelle numeerinen järjestely
443  * @param col mille sarakkeelle;
444  */
445  public void setColumnSortOrderNumber(int col) {
446  if ( col < 0 || col >= getColumns().size() ) return;
447  @SuppressWarnings("unchecked")
448  TableColumn<GridRowItem<TYPE>, String> tc = (TableColumn<GridRowItem<TYPE>, String>) getColumns().get(col);
449  tc.setCellValueFactory((rivi) -> {
450  int r = rivi.getValue().getRowNr();
451  String s = get(r,col);
452  s = "00000000000"+s;
453  s = s.substring(s.length()-10, s.length());
454  return new SimpleStringProperty(s);
455  });
456  alignments.put(col, Pos.CENTER_RIGHT);
457  }
458 
459 
460  /**
461  * Asettaa sarakkeen leveyden
462  * @param col mikä sarake, -1 on kaikki
463  * @param width mikä on leveys
464  */
465  public void setColumnWidth(int col, double width) {
466  for (TableColumn<GridRowItem<TYPE>, ?> tc : getColumns() ) {
467  if ( col == -1 || tc.getId().equals(""+col)) {
468  tc.setPrefWidth(width);
469  tc.setMaxWidth(width);
470  tc.setMinWidth(width);
471  }
472  }
473  }
474 
475 
476  /**
477  * Sarakkeen solujen sijoitus
478  * @param col mistä sarakkeesta
479  * @return mihin reunaan solut sijoitetaan
480  */
481  public Pos getAlignment(int col) {
482  Pos result = alignments.get(col);
483  if ( result == null ) return Pos.CENTER_LEFT;
484  return result;
485  }
486 
487 
488  /**
489  * Sarakkeen solujen sijoitus
490  * @param col minkä sarakkeen sijoitus
491  * @param align mihin keskitetään, esim. Pos.CENTER_CENTER
492  */
493  public void setAlignment(int col, Pos align) {
494  alignments.put(col, align);
495  }
496 
497 
498  /**
499  * Sarakkeen solujen sijoitus Swing-vakioiden avulla
500  * @param col minkä sarakkeen sijoitus
501  * @param align mihin keskitetään, esim. SwingConstants.RIGHT
502  */
503  public void setAlignment(int col, int align) {
504  switch (align) {
505  case SwingConstants.RIGHT:
506  setAlignment(col, Pos.CENTER_RIGHT);
507  break;
508  case SwingConstants.CENTER:
509  setAlignment(col, Pos.CENTER);
510  break;
511  default:
512  setAlignment(col, Pos.CENTER_LEFT);
513  }
514  }
515 
516 
517  /**
518  * @return jonon sisältö ominaisuutena
519  */
520  public StringProperty getRivitProperty() {
521  return rivitJono;
522  }
523 
524 
525  /**
526  * Pakotetaan luomaan cellit uudelleen
527  public void forceRefresh() {
528  getProperties().put(TableViewSkinBase.RECREATE, Boolean.TRUE);
529  }
530  */
531 
532 
533  /**
534  * @param tableRow mitä riviä etsitään
535  * @return rivin alkuperäinen rivi-indeksi
536  */
537  protected int findRowNr(GridRowItem<TYPE> tableRow) {
538  if ( tableRow == null ) return -1;
539  return tableRow.getRowNr();
540  }
541 
542 
543  /**
544  * Valitaan taulukosta tietty rivi. Mikäli rivi liian iso, valitaan viimeinen, mikäli
545  * liian pieni, valitaan ensimmäinen (indeksi 0);
546  * @param rowvisible mikä rivi näkyvissä olevalla järjestyksellä
547  */
548  public void selectRow(int rowvisible) {
549  int newrow = rowvisible;
550  int rowcount = getItems().size();
551  if ( rowvisible >= rowcount ) newrow = rowcount -1;
552  getFocusModel().focus(newrow);
553  getSelectionModel().select(newrow);
554  }
555 
556 
557  /**
558  * @return alkuperäinen rivinumero valitulle riville
559  */
560  public int getRowNr() {
561  // int r = getSelectionModel().getSelectedIndex();
562  GridRowItem<TYPE> rivi = getSelectionModel().getSelectedItem();
563  return findRowNr(rivi);
564  }
565 
566 
567  /**
568  * @return valittu sarake alkuperäisissä koordinaateissa
569  */
570  public int getColumnNr() {
571  @SuppressWarnings("unchecked")
572  TableColumn<GridRowItem<TYPE>, String> tc = getFocusModel().getFocusedCell().getTableColumn();
573  if ( tc == null ) return -1;
574  return Integer.parseInt(tc.getId());
575  }
576 
577 
578  /**
579  * Etsitään rivi, jolla pyydetty indeksi
580  * @param row mikä rivi etsitään
581  * @return rivi tai null jos ei löydy
582  */
583  private GridRowItem<TYPE> findTableRow(int row) {
584  for (GridRowItem<TYPE> r: tableRows)
585  if ( r.getRowNr() == row ) return r;
586  return null;
587  }
588 
589 
590  /**
591  * Asetetaan solun arvo
592  * @param s uusi arvo solulle
593  * @param row rivi
594  * @param col sarake
595  */
596  public void set(String s, int row, int col) {
597  GridRowItem<TYPE> rivi = findTableRow(row);
598  if ( rivi == null ) return;
599  rivi.set(col,s);
600  refresh();
601  }
602 
603 
604  /**
605  * Palautetaan solun arvo
606  * @param row miltä riviltä alkuperäisillä indekseillä
607  * @param col mistä sarakkeesta
608  * @return solun arvo, "" jos väärät indeksit
609  */
610  public String get(int row, int col) {
611  GridRowItem<TYPE> rivi = findTableRow(row);
612  if ( rivi == null ) return "";
613  String s = rivi.get(col);
614  if ( onCellString == null ) return s;
615  String val = onCellString.onGetCell(this, rivi.getItem(), s, row, col);
616  if ( val == null ) val = s;
617  if ( val == null ) val = "";
618  return val;
619  }
620 
621 
622  /**
623  * Asetetaan solun arvo
624  * @param obj mitä olioita rivi edustaa
625  * @param row rivi
626  */
627  public void setObject(TYPE obj, int row) {
628  GridRowItem<TYPE> rivi = findTableRow(row);
629  if ( rivi == null ) return;
630  rivi.setItem(obj);
631  refresh();
632  }
633 
634 
635  /**
636  * Palautetaan riviä vastaava olio
637  * @param row miltä riviltä alkuperäisillä indekseillä
638  * @return riviä vastaava olio, null jos väärät indeksit
639  */
640  public TYPE getObject(int row) {
641  GridRowItem<TYPE> rivi = findTableRow(row);
642  if ( rivi == null ) return null;
643  return rivi.getItem();
644  }
645 
646 
647  /**
648  * Palautetaan valittua riviä vastaava olio
649  * @return riviä vastaava olio, null jos ei valittua
650  */
651  public TYPE getObject() {
652  GridRowItem<TYPE> rivi = getSelectionModel().getSelectedItem();
653  if ( rivi == null ) return null;
654  return rivi.getItem();
655  }
656 
657 
658  /**
659  * Asetetaan solun uusi tyyli
660  * @param s uusi tyylinimi solulle. Voi olla montakin pilkulla tai välilyönnille eroteltua nimeä.
661  * @param row rivi
662  * @param col sarake
663  */
664  public void setStyleClass(String s, int row, int col) {
665  GridRowItem<TYPE> rivi = findTableRow(row);
666  if ( rivi == null ) return;
667  rivi.setCellClass(s, col);
668  refresh();
669  }
670 
671 
672  /**
673  * Solun asetetut tyylit
674  * @param row rivi
675  * @param col sarake
676  * @return null jos ei tyyliä, muuten tyylin nimi tai nimet kuten annettu
677  */
678  public String getStyleClass(int row, int col) {
679  GridRowItem<TYPE> rivi = findTableRow(row);
680  if ( rivi == null ) return null;
681  return rivi.getCellClass(col);
682  }
683 
684 
685  /**
686  * Lisätään styles listaan jonosta luokat jotka erotettu pilkuilla tai välilyönneillä
687  * @param styles mihin listaan lisätään
688  * @param newClasses mitä tyylejä lisätään
689  */
690  public static void addStyleClasses(ObservableList<String> styles, String newClasses) {
691  if ( newClasses == null ) return;
692  for (String sc: newClasses.split("[, ]"))
693  if ( !sc.isEmpty() && !styles.contains(sc) )
694  styles.add(sc);
695  }
696 
697 
698  /**
699  * @return mikä luokka tyhjille soluille joissa ei ole omaa dataa
700  */
701  public String getEmptyStyleClass() {
702  return emptyStyleClass;
703  }
704 
705 
706  /**
707  * @param emptyStyleClass tyhjien solujen luokka (tai luokat eroteltuina pilkulla tai välilöynnillä)
708  */
709  public void setEmptyStyleClass(String emptyStyleClass) {
710  this.emptyStyleClass = emptyStyleClass;
711  refresh();
712  }
713 
714 
715  /**
716  * Estetään sarakkeiden järjestäminen.
717  * Surkea häck, kopioitu
718  * http://stackoverflow.com/questions/10598639/how-to-disable-column-reordering-in-a-javafx2-tableview
719  * Eli jos järjestys muuttuu, palautetaan se heti.
720  */
721  public void disableColumnReOrder() {
722  final ArrayList<TableColumn<GridRowItem<TYPE>,?>> columns = new ArrayList<>();
723  columns.addAll(getColumns());
724  getColumns().addListener(new ListChangeListener<Object>() {
725  public boolean suspended;
726 
727  @Override
728  public void onChanged(Change<?> change) {
729  change.next();
730  if (change.wasReplaced() && !suspended) {
731  this.suspended = true;
732  getColumns().setAll(columns);
733  this.suspended = false;
734  }
735  }
736  });
737  }
738 
739 
740  /**
741  * Luokka yhdelle solulle
742  * @param <TYPE> minkä tyyppisiin alkioihin viitataan
743  */
744  protected static class StringGridCell<TYPE> extends TableCell<GridRowItem<TYPE>, String> {
745 
746  private TextField textField;
747  private int origCol;
748 
749  /**
750  * @param col mitä saraketta solu vastaa
751  */
752  public StringGridCell(int col) {
753  origCol = col;
754  }
755 
756 
757  /**
758  * @return taulukko, johon kuulutaan
759  */
761  return (StringGrid<TYPE>)getTableView();
762  }
763 
764 
765  /**
766  * @return antaa kohdalla olevan tietueen
767  */
768  protected GridRowItem<TYPE> getRowItem() {
769  @SuppressWarnings("unchecked")
770  TableRow<GridRowItem<TYPE>> row = getTableRow();
771  if ( row == null ) return null;
772  return row.getItem();
773  }
774 
775 
776  /**
777  * @return antaa kohdalla olevan tietueen kentän sisällön merkkijonona
778  */
779  protected String getCellString() {
780  GridRowItem<TYPE> tableRow = getRowItem();
781  if ( tableRow == null ) return getItem();
782  StringGrid<TYPE> table = getTable();
783  return table.get(tableRow.getRowNr(), origCol);
784  }
785 
786 
787  @Override
788  public void startEdit() {
789  if ( isEmpty()) return;
790  super.startEdit();
791  createTextField();
792  setText(null);
793  setGraphic(textField);
794  textField.setText(getCellString());
795  Platform.runLater(() -> textField.requestFocus());
796  textField.selectAll();
797  }
798 
799 
800  @Override
801  public void cancelEdit() {
802  super.cancelEdit();
803  setText(getCellString());
804  setGraphic(null);
805  }
806 
807 
808  @Override
809  protected void updateItem(String s, boolean empty) {
810  setAlignment(getTable().getAlignment(origCol));
811 
812  super.updateItem(getCellString(), empty);
813  if (isEditing()) {
814  if (textField != null) textField.setText(getCellString());
815  setText(null);
816  setGraphic(textField);
817  return;
818  }
819 
820  if ( empty ) {
821  addStyleClasses(getStyleClass(),getTable().getEmptyStyleClass());
822  return;
823  }
824 
825  @SuppressWarnings("unchecked")
826  TableRow<GridRowItem<TYPE>> row = getTableRow();
827  if ( row != null && row.getItem() != null) {
828  String styleClass = row.getItem().getCellClass(origCol);
829  addStyleClasses(getStyleClass(),styleClass);
830  setText(getCellString());
831  }
832  // setStyle("-fx-background-color: yellow;-fx-text-fill:blue");
833  }
834 
835  @SuppressWarnings("unchecked")
836  private void createTextField() {
837  if ( textField != null ) return;
838  textField = new TextField();
839  textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
840  textField.focusedProperty().addListener( (arg0, arg1,arg2) -> {
841  if (!arg2) {
842  commitEdit(textField.getText());
843  }
844  });
845  textField.setOnAction(e -> commitEdit(textField.getText()));
846  textField.setOnKeyReleased(e -> {
847  if ( e == null ) return;
848  if ( textField == null ) return;
849  GridRowItem<TYPE> tietue = getRowItem();
850  String s = textField.getText();
851  StringGrid<TYPE> table = getTable();
852  if ( table.getOnGridLiveEdit() != null ) {
853  int r = table.findRowNr(tietue);
854  s = table.getOnGridLiveEdit().onEdit(table, tietue.getItem(), s, r, origCol, textField);
855  }
856  if ( s != null ) tietue.set(origCol, s);
857  });
858  textField.setOnKeyPressed(t -> {
859  if (t.getCode() == KeyCode.ENTER) {
860  commitEdit(textField.getText());
861  cancelEdit();
862  t.consume();
863  } else if (t.getCode() == KeyCode.ESCAPE) {
864  cancelEdit();
865  t.consume();
866  } else if (t.getCode() == KeyCode.TAB) {
867  // commitEdit(textField.getText()); // ei tarvii kun tallennetetaan muutenkin
868  cancelEdit();
869  t.consume();
870  StringGrid<TYPE> table = getTable();
871  if ( t.isShiftDown() )
872  table.getFocusModel().focusLeftCell();
873  else
874  table.getFocusModel().focusRightCell();
875 
876  table.edit(getTableRow().getIndex(), table.getFocusModel().getFocusedCell().getTableColumn());
877  }
878  });
879  }
880 
881  }
882 
883 }