StringGrid

StringGrid on TableView-komponentista peritty komponentti, jonka tarkoitus on helpottaa taulukkomuotoisen aineiston käsittelyä.

Merkittävä ero TableView-komponenttiin on se, että alkioihin pääsee helposti käsiksi rivi- ja sarakeindeksin avulla. Rivi- ja sarakeindeksi ovat indeksejä alkueräiseen lajittelemattomaan taulukkoon nähden. Lisäksi kunkin solun ulkoasua voi säätää helposti erikseen.

Jar-tiedoston hakeminen: fxgui.jar.

1. Käyttö suunnitteluaikana

Ohjelman ulkoasun suunnitteluaikana voi StringGrid-komponenttiin lisätä malliaineistoa laittamalla Rivit-ominaisuuteen tekstiä tyyliin:

ala|aloitusvuosi|h/vko
kalastus|1955|20
laiskottelu|1950|20
työn pakoilu|1952|40

Katso tarkemmin SceneBuilderin esimerkistä.

2. Yleisimpiä toimintoja

Kaikki (tai ainakin suurin osa) TableView-komponentin ominaisuuksista toimii.

Merkintä (SC) alla tarkoittaa että ominaisuuden voi valita myös SceneBuilderissä. Esimerkeissä taulukon nimi vaihtelee sen mukaan mistä ohjelmasta koodinpala on kopioitu.

2.1 Sarakkeiden luominen

Mikäli sarakemäärä on saatu oikeaksi ja otsikot ovat oikeita jo suunnitteluaikana, ei tässä kohti tarvitse tehdä muuta.

Mikäli sarakemäärää halutaan muuttaa, täytyy koko talukko luoda (toistaiseki) uudelleen.

Luodaan esimerkiksi RistiNollaa varten sarakkeet, joissa 1. otsikko on tyhjä ja muissa juokseva numero:

        String[] headings = new String[koko+1];
        headings[0] = "";
        for (int i=1; i<=koko; i++) headings[i] = ""+i;
        grid.initTable(headings); 

Tämä tyhjentää kaikki asetukset, eli on tehtävä ennen muita rivi-/sarakeasetuksia.

Koska metodi initTable on esitelty vapaan parametrilistan muodossa:

public void initTable(String... headings) {

voidaan sarakkeet alustaa myös antamalla pilkkueroteltu lista otsikoita:

grid.initTable("nimi","syntymävuosi","osoite"); 
# otsikko

2.2 Yleisiä asetuksia

Järjestetään sarake 1 numeerisesti:

        tableHarrastukset.setColumnSortOrderNumber(1);

Sarakkeen 2 leveys:

        tableHarrastukset.setColumnWidth(2, 60);

Kaikkien sarakkeiden leveys kerralla:

        grid.setColumnWidth(-1,30);   

Estetään kaikkien sarakkeiden lajittelu (vastaavasti yksittäisen sarakkeen kun laitetaan vastaava sarakeindeksi):

grid.setSortable(-1, false); 

Estetään sarakkeiden järjestyksen vaihtaminen:

grid.disableColumnReOrder();

Muokkaus päälle/pois (SC):

        tableHarrastukset.setEditable(false);

Yksittäisiä soluja voidaan valita koko rivin sijaan

        grid.getSelectionModel().setCellSelectionEnabled(true); 

Menu, josta voi valita sarakkeet

grid.setTableMenuButtonVisible(true); 
# asetuksia

2.3 Rivien lisääminen

Rivin lisääminen ilman riviin kuuluvata olioita joko

        grid.add("Matti", ""+vuosi);

tai

        String[] rivi = {"Matti", "1970"};
        grid.add(rivi);
# rivinlisaaminen

Alkion ja sen näkyvien tietojen lisääminen:

    private void naytaHarrastus(Harrastus har) {
        int kenttia = har.getKenttia();
        String[] rivi = new String[kenttia-har.ekaKentta()];
        for (int i=0, k=har.ekaKentta(); k < kenttia; i++, k++)
            rivi[i] = har.anna(k);
        tableHarrastukset.add(har,rivi);
    }

Tällöin itse taulukko pitää olla esiteltynä tallentamaan vastaavia olioita:

    @FXML private StringGrid<Harrastus> tableHarrastukset;
# oliohjarivi

2.3.1 Pelkkien olioiden lisääminen

Voidaan lisätä myös olioita ilman vastaavia merkkijonoja:

grid.add(jasenet);

Tällöin ei enää toimi grid.get(r,c). Ja jotta mitään näkyisi, pitää taulukolle kertoa mistä saadaan kutakin alkiota (rivi, r, sarake c) kohti olion vastaavan kentän sisältö merkkijonona.

Tämä tehdään lisäämällä tapahtumankäsittelijolio, joka palautta tiedon siitä, mitä sisältöä soluun pitää näyttää. Usein tässä oikaistaan tekemällä tätä varten lambda-lauseke tyyliin:

grid.setOnCellString( (g, jasen, defValue, r, c) -> jasen.anna(c+eka) );

Tätä funktiota StringGrid-olio kutsuu jokaiselle taulukon solulle erikseen. Solun näyttämistapahtumalle tulee siis parametrina (edellisillä nimillä):

  • g = grid jonka solun arvo tarvitaan
  • jasen = olio joka liitetty ko riville
  • defValue = oletus solun sisällölle
  • r = row, eli rivin indeksi, solulle jonka sisältö halutaan
  • c = column, eli sarakkeen indeksi solule, jonka sisältö halutaan

Aina tapahtuman käsitelyssä ei tarvitse käyttää kaikkia parametreja. Esimerkiksi jos tieto saadaan oliosta itsestään, kuten edellä, riittää pyytää oliolta tieto, että mikä arvo laitetaan oliota vastaavan rivin soluun paikassa c.

Olioita voi lisätä kerrallaan yhden tai kokonaisen tietorakenteen:

# vainolio

Mikäli tämän lisäksi halutaan lajitella sarakkeita muuhun järjestykseen kuin merkkijonojärjestykseen (esimerkiksi hetujen vuoden perusteella), täytyy myös sanoa miten saadaan lajittelussa käytettävä merkkijono. Numeerisen järjestyksen saa sillä että ilmoittaa sarakkeen numeeriseksi.
Esimerkiksi:

 grid.setOnCellValue( (g, jasen, defValue, r, c) -> jasen.getAvain(c+eka) );

2.4 Solun arvon muuttaminen ja katsominen

Solun tekstiä voidaan vaihtaa:

        grid.set(jono, r, c);

Vastaavasti solussa oleva teksti voidaan hakea:

        String jono = grid.get(r, c);

Otsikko-solun arvoa voidaan muuttaa:

        grid.getColumns().get(0).setText(uusiOtsikko); // muutetaan saraketta 0

2.5 Tyhjät solut

Piilotetaan ylimääräinen sarake oikealta (SC)

    grid.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);

Rivien alle jäävien tyhjien solujen tyyli (SC):

    grid.setEmptyStyleClass("tyhja")

Tämä sama voidaan tehdä myös suoraan .css-tiedoston avulla laittamalla sinne tyyliin:

.table-row-cell:empty {
   -fx-background-color: white;
}

.table-row-cell:empty .table-cell { 
   -fx-border-width:     0px;
}

2.6 Solujen värit

Solujen väreihin voidaan vaikuttaa .css-tyyleillä. Halutut tyylit tehdään .css-tiedostoon ja itse koodiin voidaan sitten laittaa esimerkiksi:

        grid.setStyleClass("virhe,sininen", 1, 1);
        grid.setStyleClass("s1", 0, 0);
        grid.setStyleClass("s2", 1, 0);
        grid.setStyleClass("s3", 2, 0);

Rivi- ja sarakeindeksit ovat solujen alkuperäisen sijainnin mukaan riippumatta onko rivejä tai sarakkeita järjestelty uudelleen.

Mikäli solujen värejä muutetaan ja rivejä järjestellään, tulee tästä ongelmia, jotka voidaan kiertää pakottamalla taulukon uudelleenpiirto mikäli otsikoihin kosketaan:

        grid.setOnMouseClicked( e -> {
            int r = grid.getRowNr();
            int c = grid.getColumnNr();
            if ( r < 0 || c < 0 ) grid.refresh();
          });

2.7 Solun klikkauksen kuunteleminen

Solun klikkauksen kuuntelemiseksi lisätään kuuntelija:

        grid.setOnMouseClicked( e -> {...} );

Tapahtumasta e voi kysyä klikkausten lukumäärän:

        if (  e.getClickCount() == 2) 

Klikatun solun koordinaatin (alkuperäisinä indekseinä) saa selville

            int r = grid.getRowNr();
            int c = grid.getColumnNr();

Vastaavasti riviä vastaavan olion saa tarvittaessa selville:

    Harrastus har = tableHarrastukset.getObject(r);

2.8 Näppäinten kuunteleminen

Vastaavasti näppäimiä voidaan kuunnella:

        grid.setOnKeyPressed( e -> {if ( e.getCode() == KeyCode.ENTER ) laitaMerkki();});

2.9 Solujen muokkaus

Jotta soluja voidaan muokata "lennossa", pitää tämä ilmoittaa taulukolle:

grid.setEditable(true);

Sitten mikäli halutaan muokatulle solulle tehdä jotakin tarkistuksia niin pitää tehdä takaisinkutsu tyyliin:

        grid.setOnGridLiveEdit((g, jasen, defValue, r, c, edit) -> {
            String virhe = jasen.aseta(c+eka,defValue);
            if ( virhe == null ) {
                kerho.korvaaTaiLisaa(jasen); // jotta saadaan muutos
                edit.setStyle(null);
                Dialogs.setToolTipText(edit,"");
            } else {
                edit.setStyle("-fx-background-color: red");
                Dialogs.setToolTipText(edit,virhe);
            }
            return defValue;
        });

Huomioi tuo return lause - heittää hämärähkön virheilmoituksen ilman sitä

28 Apr 21

3. Käyttöesimerkkejä

Seuraavana muutamia esimerkkiohjelmia tai niiden osia, joissa on käytetty StringGridin ominaisuuksia.

Kaikki esimerkit saa ehkä helpoiten Eclipse-käyttöön
kloonata gitlabista:

https://gitlab.jyu.fi/tie/ohj2/esimerkit/fxexamples/-/tree/master/Examples/src

3.1 RistiNolla

Ristinolla on yksinkertainen esimerkki RistiNolla.pelistä, missä solua klikkaamalla siiten tulee vuoroin X ja vuoroin O. Tässä myös eri solun väri vaihtuu. Vuorossa olevan pelaajan merkki näkyy taulukon vasemassa yläkulmassa.

Pelaaminen valmiista .jar-tiedostosta:

Ajettava esimerkki

# V1

3.2 Nimi ja vuosi

Esimerkki uudemman StringGridin käytöstä. Tässä on vain tarkoitus esitellä eri ominaisuuksia. Ominaisuuksia pitää katsoa yksittäisinä eikä niitä ole kaikkia pakko käyttää.

Kun muokataan numerosolua, niin se tulee punaiseksi jos jono ei vastaa kokonaislukua. Esimerkissä on aluksi asetettu yksi solu muuten vaan punaiseksi. Kun soluja klikataan, niistä lähtee väri pois. Muokatun solun jälkeen näytetään oikeassa reunassa muokatun solun arvo ja koordinaatti.

Ajaminen valmiista .jar-tiedostosta:

java -cp Examples.jar stringgrid.NimiJaVuosiMain

Ajettava esimerkki

3.3 Kerhon harrastukset

Alla tärkeimmät muutokset harrastusten näyttämiseksi. Esimerkissä kunkin harrastuksen tiedot laitetaan ensin merkkijonotaulukkoon, joka lisätään StringGridin riviksi (metodi naytaHarrastus).

public class KerhoGUIController implements Initializable {
    ...
    @FXML private StringGrid<Harrastus> tableHarrastukset;
    ...
    protected void alusta() {
        ...
        tableHarrastukset.setPlaceholder(new Label("Ei vielä harrastuksia"));
        
        // alustetaan hrrustustaulukon otsikot
        int eka = apuharrastus.ekaKentta();
        int lkm = apuharrastus.getKenttia();
        String[] headings = new String[lkm-eka];
        for (int i=0, k=eka; k<lkm; i++, k++) headings[i] = apuharrastus.getKysymys(k);
        tableHarrastukset.initTable(headings);
        tableHarrastukset.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
        tableHarrastukset.setEditable(false);
        
        // Tämä on vielä huono, ei automaattisesti muutu jos kenttiä muutetaan.
        tableHarrastukset.setColumnSortOrderNumber(1);
        tableHarrastukset.setColumnSortOrderNumber(2);
        tableHarrastukset.setColumnWidth(1, 60);
        tableHarrastukset.setColumnWidth(2, 60);
    }

    ...

    private void naytaHarrastukset(Jasen jasen) {
        tableHarrastukset.clear();
        if ( jasen == null ) return;
        
        try {
            List<Harrastus> harrastukset = kerho.annaHarrastukset(jasen);
            if ( harrastukset.size() == 0 ) return;
            for (Harrastus har: harrastukset)
                naytaHarrastus(har);
        } catch (SailoException e) {
            // naytaVirhe(e.getMessage());
        } 
    }


    private void naytaHarrastus(Harrastus har) {
        int kenttia = har.getKenttia();
        String[] rivi = new String[kenttia-har.ekaKentta()];
        for (int i=0, k=har.ekaKentta(); k < kenttia; i++, k++)
            rivi[i] = har.anna(k);
        tableHarrastukset.add(har,rivi);
    }
    ...
    
    private void naytaJasen() {
        jasenKohdalla = listJasenet.getSelectionModel().getSelectedItem();
        if (jasenKohdalla == null) return;
        JasenDialogController.naytaJasen(edits, jasenKohdalla);
        naytaHarrastukset(jasenKohdalla);
    }
    ...
}    
# V2
# jasen

3.4 Esimerkki: Jäsenen muokkaus StringGridissä

Tässä esimerkissä on tehty kokonainen ohjelma jossa on esimerkki jäsenten muokkaamiseksi StringGridissä. Esimerkissä ei lisätä merkkijonoja taulukon avulla kuten harrastusesimerkissä, vaan kysytään aina jäseneltä mikä teksti näytetään mihinkäkin soluun. Samoin lajittelua varten kerrotaan millaisen jonon perusteella lajitellaan. Esimerkin koodi on hieman lyhentynyt kun SailoException on peritty ajonaikaisesta poikkeuksesta ja näin sitä ei ole pakko ottaa kiinni joka paikassa:

public class SailoException extends RuntimeException {
Esimerkki jäsenten muokkauksesta taulukossa
Esimerkki jäsenten muokkauksesta taulukossa

Esimerkissä voi muokata jäseniä, mutta ei poistaa eikä lisätä. Mikäli muokkausmahdollisuus poistettaisiin, lyhenisi alusta-metodi melkein puolella (alusta voisi olla myös staattinen).

# JasenStringGrid

JasenStringGrid.java

019 public class JasenStringGrid extends Application {
020     @Override
021     public void start(Stage primaryStage) {
022         try {
023             final BorderPane root = new BorderPane();
024             StringGrid<Jasen> grid = new StringGrid<>();
025             root.setCenter(grid);
026             
027             primaryStage.setScene(new Scene(root));
028             primaryStage.setTitle("Kerho");
029             
030             Kerho kerho = new Kerho();
031             kerho.lueTiedostosta("kelmit");
032             alusta(kerho, kerho.etsi("*", 0),grid);
033             
034             primaryStage.setOnCloseRequest((event) -> {
035                 try {
036                     kerho.tallenna();
037                 } catch (SailoException e) {
038                     // TODO viesti
039                 }
040             } );
041 
042             primaryStage.show();
043         } catch(Exception e) {
044             e.printStackTrace();
045         }   
046     }
047 
048     
049     private void alusta(Kerho kerho, Collection<Jasen> jasenet, StringGrid<Jasen> grid) {
050         Jasen apujasen = new Jasen();
051         int eka = apujasen.ekaKentta();
052         int lkm = apujasen.getKenttia();
053         String[] headings = new String[lkm-eka];
054         for (int k=eka; k<lkm; k++) headings[k-eka] = apujasen.getKysymys(k);
055         grid.initTable(headings);
056 
057         for (int k=eka; k<lkm; k++) grid.setAlignment(k-eka, apujasen.getSijainti(k));
058         
059         grid.setOnCellString( (g, jasen, defValue, r, c) -> jasen.anna(c+eka) );
060         grid.setOnCellValue( (g, jasen, defValue, r, c) -> jasen.getAvain(c+eka) );
061 
062         grid.setTableMenuButtonVisible(true); // menu, josta voi valita sarakkeet
063         
064         grid.setEditable(true);
065         grid.setOnGridLiveEdit((g, jasen, defValue, r, c, edit) -> {
066             String virhe = jasen.aseta(c+eka,defValue);
067             if ( virhe == null ) {
068                 try {
069                     kerho.korvaaTaiLisaa(jasen); // jotta saadaan muutos
070                 } catch (kanta.SailoException e) {
071                     //
072                 } 
073                 edit.setStyle(null);
074                 Dialogs.setToolTipText(edit,"");
075             } else {
076                 edit.setStyle("-fx-background-color: red");
077                 Dialogs.setToolTipText(edit,virhe);
078             }
079             return defValue;
080         });
081         
082         grid.add(jasenet);
083     }
# V3

These are the current permissions for this document; please modify if needed. You can always modify these permissions from the manage page.