001package fi.jyu.mit.fxgui; 002 003 004import java.util.ArrayList; 005import java.util.Collection; 006import java.util.HashMap; 007 008import javax.swing.SwingConstants; 009 010import javafx.application.Platform; 011import javafx.beans.property.ListProperty; 012import javafx.beans.property.SimpleListProperty; 013import javafx.beans.property.SimpleStringProperty; 014import javafx.beans.property.StringProperty; 015import javafx.collections.FXCollections; 016import javafx.collections.ListChangeListener; 017import javafx.collections.ObservableList; 018import javafx.fxml.FXML; 019import javafx.geometry.Pos; 020import javafx.scene.control.TableCell; 021import javafx.scene.control.TableColumn; 022import javafx.scene.control.TableRow; 023import javafx.scene.control.TableView; 024import javafx.scene.control.TextField; 025import javafx.scene.input.KeyCode; 026 027/** 028 * StringGrid joka näyttää merkkijonotaulukon sisällön. 029 * Taulukkoon voidaan jokaista riviä kohti tallentaa myös jokin olio. 030 * 031 * grid.add(har,rivi); // rivillä sarakkeiden merkkijonot 032 * 033 * Kullekin solulle voidaan antaa oma css-tyyli. Ongelma: mikäli 034 * ei kuunnella hiiren klikkausta otsikossa ja tehdä refresh, niin tämä 035 * menee sekaisin jos rivit lajitellaan (jos eri riveillä eri css). 036 * TYPE saa olla ?, mikäli tallennettavia olioita ei käytetä mihinkään 037 * 038 * Soluihin pääsee käsiksi alkuperäisen (lajittelemattoman) taulukon 039 * rivi- ja sarakeindekseillä. Ellei erikseen mainita, parametreissa 040 * olevat row- ja col-indeksit ovat nimenomaan alkueräisiä indeksejä. 041 * 042 * Dataa voidaan lisätä myös ilman merkkijonoja. 043 * 044 * grid.add(jasenet); 045 * 046 * Tällöin on vähintään kerrottavat miten merkkijonot saadaan tietylle riville ja 047 * sarekkeelle, esim tyyliin: 048 * 049 * grid.setOnCellString( (g, jasen, defValue, r, c) -> jasen.anna(c+eka) ); 050 * 051 * Mikäli halutaan lajitella sarakkeita muuta kuin merkkijonojärjestyksessä, 052 * on kerrottava lajittelumerkkijono tyyliin: 053 * 054 * grid.setOnCellValue( (g, jasen, defValue, r, c) -> jasen.getAvain(c+eka) ); 055 * 056 * Katso myös: <a href="https://tim.jyu.fi/view/kurssit/tie/ohj2/tyokalut/JavaFX/fxgui/StringGrid">StringGrid TIMissä</a> 057 * 058 * @param <TYPE> minkä tyyppisiä tietoja liitetään riveihin 059 * 060 * @author vesal 061 * @version 29.12.2015 062 * @version 25.3.2015/vl - TYPE mukaan ja lisää ominaisuuksia 063 */ 064public class StringGrid<TYPE> extends TableView<StringGrid.GridRowItem<TYPE>> { 065 066 /** 067 * Alkio yhdelle riville. 068 * Pidetään yllä itse olioita, listaa merkkijoista, 069 * alkuperäisestä rivinumerosta ja solu css:stä. 070 * @param <TYPE> mikä tieto tallennetaan riviin 071 */ 072 @SuppressWarnings("javadoc") 073 public static class GridRowItem<TYPE> { 074 private ObservableList<String> rowSts; 075 private TYPE item; 076 private int rowNr; 077 private HashMap<Integer, String> styleClasses; 078 079 /** 080 * @param row mikä teksti tallennetaan 081 * @param item mikä alkio tähämn kohti 082 * @param rowNr mikä on alkuperäinen rivinumero 083 */ 084 public GridRowItem(String[] row, TYPE item, int rowNr) { 085 this.rowSts = FXCollections.observableArrayList(); 086 this.setRow(row); 087 this.setItem(item); 088 this.setRowNr(rowNr); 089 } 090 091 public void setItem(TYPE item) { 092 this.item = item; 093 } 094 095 /** 096 * @return kohdalla oleva alkio 097 */ 098 public TYPE getItem() { 099 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 */ 760 public StringGrid<TYPE> getTable() { 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}