The page has been modified since the last reload. Refresh now?

There are {{ $ctrl.pendingUpdatesCount() }} pending paragraph updates.

{"classes": ["linkit"], "rd": "114039", "rl": "no", "rp": "jjgCelf0svvh"}

Dialogit ja ponnahdusikkunat

Tässä dokumentissa tarkastellaan ohjelmissa yleisesti käytettävien dialogien lisäämistä sekä kurssin FXGui-apupaketin käyttöä.

Tämä sivu ei ole vain “FYI”, vaan tarvitset tämän neuvoja harjoitustyön tekemiseen.

Muista taas kommentoida rohkeasti dokumentin marginaaliin jos jokin on epäselvää tai ei onnistu (tai saat ahaa-elämyksiä). Palautteesi voi ratkaista muidenkin ongelmia :)

1. JavaFX:n valmiit dialogit

JavaFX:ssä on valmiina kysymys- ja varoitusdialogeja. Valmiit dialogit ovat yleensä pieniä modaalisia ponnahdusikkunoita. Modaalisuus tarkoittaa, että käyttäjä ei voi tehdä mitään muuta ennenkuin on kuitannut dialogin pois. Modaalisuuteen palataan myöhemmin tässä dokumentissa.

Löydät valmiita koodiesimerkkejä alempaa tästä dokumentista sekä code.makeryn sivuilta.

.java / .fxml Kannattaa heti opetella tunnistamaan, mikä koodi kuuluu .fxml-tiedostoon (ne missä koodi on xml-muodossa) ja mikä .java-tiedostoon. Kyseessä on kaksi aivan eri kieltä. Yleensä esimerkeissä oleva Java-koodi sijoitetaan Eclipsessä controller-luokkaan jonkin tapahtumakäsittekijä-metodin koodin yhteyteen.

Interaktiivisen esimerkin rajoitukset: Alla olevissa TIM-esimerkeissä koodi ajetaan yliopiston palvelimilla olevassa virtuaalikoneessa siihen asti kun dialogi on tullut näytölle, siten TIM ottaa dialogista kuvankaappauksen ja esittää sen sinulle. Tämän takia et voi painella dialogien painikkeita. Myös otsikkopalkit puuttuvat esimerkeistä, vaikka ne nimetäänkin koodissa.

Tämän sivun interaktiiviset osiot ovat kuitenkin mainioita harjoitusalustoja dialogien ulkoasujen harjoitteluun. Huomaa kuitenkin dialogien layoutin käyttöjärjestelmäsidonnaisuus: valmiita dialogeja käytettäessä OK- ja Cancel-painikkeiden järjestys vaihtuu sen mukaan mikä käyttöjärjestelmä on käytössä. Windowsissa OK-painike on Cancel-painikkeen vasemmalla puolella ja muissa OK-painike on oikeassa laidassa.

{}

1.1 Alert (box)

Mikä? Alert boxilla tarkoitetaan pientä ponnahdusikkunaa, jonka avulla halutaan esimerkiksi varmistaa, että käyttäjä varmasti huomaa jonkin asian (ìnformation-tyyppi) tai esimerkiksi antaa selkeän hyväksynnän jatkamiselle (confirmation`-tyyppi).

Alert boxien käyttöä kannattaa harkita. Mitä enemmän niitä käyttää, sitä vähemmän käyttäjä kiinnittää niiden sisältöön huomiota.

Kokeile! Muokkaa ja aja alla olevaa koodia. Huomaa, että tulokset ovat .png-muotoisia kuvia, eli voit luoda kuvia myös tässä harjoitustyösi suunnitelmaa varten.

Jos jokin on epäselvää, heitä kysymyksiä TIMin kommenttityökalulla marginaaliin :)

Esimerkkikoodi: Simppeli ´information`-tyypin alert, joka kuitataan yhdellä painikkeella.

#

Please to interact with this component

//
        Alert alert = new Alert(AlertType.INFORMATION);
        alert.setTitle("Huomautus");
        alert.setHeaderText(null);
        alert.setContentText("Huomasithan että joulu meni jo!");
        alert.showAndWait();

Näyttää vain harmaita laatikoita. Haluaisin käytää tätä suunnitelman alertboxien kuviin, mutta nyt ei onnistu ja dedline on tänään. Ei ole hyvä tämä!

VL: kyllä mulla toimii. Onkohan sun koodi pielessä? Sitten näytä koko koodi niin näet valmiin ohjelman jonka voi copy/pasteta Eclipseen ja käyttää sieltä.

Lukekaa ja tehkää ehdottomasti tuo:

https://tim.jyu.fi/view/kurssit/tie/ohj2/tyokalut/JavaFX/kaytto

niin opitte käyttämään Eclipseä samalla. Mutta kokeilkaa tässäkin muutaman kerran, sillä toimintalogiikka on sellaien että ohjlma käynistetään ja odotetaan hetken ja sitten otetaan siitä kuva. Riippuen kuormasta tuo kuva voi olla valmis ennemmin tai myöhemmin.

01 Jun 17 (edited 24 Jan 18)
{}

Mikäli dialogin ulkoasuun haluaa vaikuttaa, voisi sen tehdä tyyleillä (kokeile laittaa edellä ennen alert.showAndWait() -kutsua):

        alert.getDialogPane().setStyle(" -fx-max-width:200px; -fx-max-height: 800px;"+
                                       " -fx-pref-width: 200px; -fx-pref-height: 800px;");

Tai sama voidaan tehdä myös käyttäen vastaavia metodeja:

        alert.getDialogPane().setMaxWidth(200);
        ...
        alert.getDialogPane().setPrefHeight(800);

Fontin voisi vaihtaa tasaväliseksi esim:

      alert.getDialogPane().setStyle("-fx-font-family: monospace;");

Esimerkkikoodi: Confirmation-tyypin alert box, joka pyytää käyttäjää tekemään Kyllä/Ei -valinnan:

#

Please to interact with this component

//
        Alert alert = new Alert(AlertType.CONFIRMATION);
        alert.setTitle("Valitse");
        alert.setHeaderText(null);
        alert.setContentText("Tallennetaanko?");

        ButtonType buttonTypeYes = new ButtonType("Kyllä", ButtonData.OK_DONE);
        ButtonType buttonTypeCancel = new ButtonType("Ei", ButtonData.CANCEL_CLOSE);

        alert.getButtonTypes().setAll(buttonTypeYes, buttonTypeCancel);

        Optional result = alert.showAndWait();
        if ( result.get() == buttonTypeYes ) System.out.println("Talletaan");

Vinkki! Käy nappaamassa koodia Code.makeryn esimerkeistä ja kokeile ajaa niitä TIMissä.

1.2 TextInputDialog

Käyttäjältä voidaan pyytää dialogi-ikkunassa myös muutakin, kuin painikkeiden klikkailua. Esimerkiksi tekstiä voit käyttäjältä kysyä seuraavalla tavalla:

            TextInputDialog dialog = new TextInputDialog("tähän tulee muodostajassa ollut teksti");
            dialog.setTitle("Tähän tulee Title");
            dialog.setHeaderText("Tähän tulee HeaderText");
            dialog.setContentText("Tähän tulee ContentText:");

#

Please to interact with this component

//
        TextInputDialog dialog = new TextInputDialog("kelmit");
        dialog.setHeaderText(null);
        dialog.setTitle("Vastaa");
        dialog.setContentText("Kerhon nimi:");
        Optional answer = dialog.showAndWait();
        System.out.println(answer.isPresent() ?
           answer.get() : "Ei ollut vastausta");

Esimerkissä käyttäjän vastaus tulostetaan konsoliin System.out.println(answer.get()); -lauseella, mutta käyttäjän vastausta voi käyttää muuhunkin.

#

2. Dialogit FXGui.jarissa

Jos olet jo rakennellut harjoituskäyttöliittymää SceneBuilderissa, niin kurssia varten luotu FXGui-aliohjelmakirjasto on jo hieman tuttu.

Ilouutinen on, että ´FXGui´ helpottaa sinua myös dialogien käytössä, kunhan vältät tyypillisen aloittelijan (ja kokeneemmankin) virheen ja muistat importata FXGui-kirjaston sekä lisätä sen projektin luokkapolkuun.

Eli kaikissa käyttötapauksissa lisää .java-tiedoston import-osaan aina:

import fi.jyu.mit.fxgui.*;

Alta löydät esimerkit edellä esiteltyjen alert boxien (information ja confirmation) sekä TextInputDialogin käytöstä FXGui-kirjaston avulla.

2.1 showMessageDialog

Esimerkki: Informoivan alert boxin luominen on näin helppoa:

#

Please to interact with this component

//
        // Kun koodissa tarvitsee tulostaa jokin viesti:
        Dialogs.showMessageDialog("Ei osata vielä lisätä");

2.2 showQuestionDialog

Esimerkki: Confirmation-tyyppinen Kyllä/Ei -kysely onnistuu näin:

#

Please to interact with this component

//
        // Kun tarvitsee kysyä Kyllä/Ei tyyppisiä kysymyksiä:

        boolean vastaus = Dialogs.showQuestionDialog("Poisto?",
                           "Poistetaanko jäsen: Hopo Hessu", "Kyllä", "Ei");
        // if ( vastaus ) poistaJasen(...

2.3 showInputDialog

Esimerkki: Käyttäjältä voi pyytää tekstiä (TextInputDialog) näin:

#

Please to interact with this component

//
       // Kun tarvitaan merkkijonoa:

       String kerhonNimi = Dialogs.showInputDialog("Anna kerhon nimi", "kelmit");
       if ( kerhonNimi == null ) return;
       // käytä kerhonNimi -muuttujaa johonkin
#

2.4 Dialogin ulkoasuun vaikuttaminen

Kaikkiin edellä mainittuihin kutsuihin voidaan lisätä viimeiseksi parametriksi takaisinkutsu, jolla saadaan dialogi käyttöön ennen sen näyttämistä. Näin voidaan esimerkiksi säätää näytettävän dialogin kaikkia asetuksia, esimerkiksi leveyttä (kokeile lisätä edellisiin):

	        Dialogs.showMessageDialog("Ei osata vielä lisätä",
	                dlg -> dlg.getDialogPane().setPrefWidth(400));

Mikäli halutaan tehdä useampia muutoksia, pitää nuolen jälkeinen lause tehdä aaltosuluilla kootuksi lauseeksi:

        Dialogs.showMessageDialog("Ei osata vielä lisätä",
           dlg -> {
             dlg.getDialogPane().setPrefWidth(200);
             dlg.getDialogPane().setPrefHeight(400);
           });

Luonnollisesti asetuksia voidaan säätää myös luvun alun esimerkin setStyle-metodilla.

        Dialogs.showMessageDialog("Ei osata vielä lisätä",
           dlg -> dlg.getDialogPane().setStyle(
             " -fx-max-width:200px; -fx-max-height: 400px;"+
             " -fx-pref-width: 200px; -fx-pref-height: 400px;"));

3. Ikkunan näyttäminen modaalisena

Usein ohjelmissa on useita näkymiä. Näitä voidaan hoitaa joko niin, että yhdessä “ikkunassa” vaihdetaan sisältöä. Tämä on varsin tyypillistä esimerkiksi mobiilisovelluksille.

Toinen vaihtoehto on avata uusia ikkunoita. Tämä taas on tyypillinen tapa työpöytäsovelluksissa. Tavalliselle peruskäyttäjälle useiden ikkunoiden käyttäminen on osoittautunut haastavaksi. Siksi eräs tapa uuden ikkunan avaamiseksi on avata se modaalisena, eli sellaisena että se estää muiden ikkunoiden käyttämisen kunnes avattu ikkunan on suljettu. Tämä periaatteessa tuo saman tilanteen kuin ikkunan sisällön vaihtaminen, paitsi että käyttäjälle jää selvä kuva siitä, mihin palataan kun ikkuna suljetaan.

Sen lisäksi että modaaliset ikkunat ovat selkeämpiä käyttäjälle, ne ovat myös hieman selkeämpiä ohjelmoijan näkökulmasta. Mikäli ikkuna suljetaan OK painikkeesta hyväksytään se mitä ikkunassa on tehty, muuten hylätään. Esimerkkejä modaalisista dialogeista oli edellä olevat valmiit Alert yms. dialogit.

Alempana näytetään miten tehdään modaaliseksi .fxml-tiedostossa esitetty sisältö.

3.1 Käyttö

Pyritään käytössä samaan kuin Alert-dialogien kanssa, että ikkunan käynnistäminen olisi pääohjelma puolesta mahdollisimman helppoa.

Alla on interaktiivinen TIM-esimerkki, jonka ajaminen vaatii että ensin tallennat ja käännät sen alapuolella olevat tiedostot.

Esimerkki Kutsutaan koodissa käyttäjän syötettä pyytävää käsittelijää.

#

Please to interact with this component

Jotta yllä oleva koodi voidaan ajaa, paina alta “tallenna” (ja käy kääntämässä vielä seuraava tiedosto)

Esimerkki: Suunnitellaan dialogi KerhonNimiView.fxml-tiedostoon (huom. ei siis pääikkunan tiedosto) vaikkapa SceneBuilderillä tai ihan vaikka käsin Eclipsessa. Klikkaa “Näytä koko koodi”, niin saat paremman käsityksen millaista koodia .fxml-tiedostoon syntyy:

#

Please to interact with this component

The controller 'KunnanNimiController' has no field 'hakuehtoo' Ilmotus ei tuu problems ikkunaan vaan näkee vaan fxml tiedostoa selaamalla.

VL: Onko siellä kontrollerissa hakuehtoo-nimistä @FXML alkiosta attribuuttia? Koska nähtävästi tuon niminen on siellä .fxml-tiedostossa.

12 Feb 18 (edited 14 Feb 18)

Huomaa, että .fxml-tiedostossa on määritelty alla oleva .java-tiedoston luokka controlleriksi.

<BorderPane ... fx:controller="modalDialog.KerhonNimiController"> 

Paina allaolevasta “käännä” ja palaa ylemmäs kokeilemaan ohjelmaa.

Tässä tehdään kontrolleri, jossa on staattisena metodina tuon ikkunan käynnistäminen ja näyttäminen ja vastauksen hakeminen dialogista. Klikkaa “Näytä koko koodi”, niin näet millainen aliohjelman pitäisi olla. Huomaa että tämä kontrolleri pitää olla mainittuna vastaavassa .fxml-tiedostossa

#

Please to interact with this component

#

Please to interact with this component

Onnistuuko? Jos jokin ei ole selvää, niin et varmasti ole ainoa - kysy tai ihmettele rohkeasti kommentoimalla sopivaa kohtaa marginaaliin :)

#

4. Modaalinen dialogi FXGuin avulla

Edellä on paljon koodia, joka täytyy toistaa lähes samanlaisena dialogista riippumatta.

4.1 Dialogille oletuskontrolleri

FXGui.jar-kirjastossa tämä koodi on valmiina ja minimissään sitä voidaan käyttää tyyliin:

Tämä toimii, kun haluan avata uuden modaalisen ikkunan käyttöliittymääni. Onko mahdollista liittää "KerhonNimiView.fxml"-tiedostoon uutta, erillistä controller-tiedostoa? SceneBuilderista olen yrittänyt, ja ohjelma kaatuu, kun yrittää toteuttaa vasemmalla olevaa komentoa fxml-tiedostolle, joka on linkitetty erilliseen controlleriin.

E: Kuten ohjeessa lukeekin, piti vain toteuttaa rajapinta, eli "KerhoGUIController implements ModalControllerInterface".

06 Feb 18 (edited 08 Feb 18)

Implementointi antaa virheen "ModalControllerInterface is a raw type. References to generic type ModalControllerInterface should be parameterized", mitä se tarkoittaa? Ohjeissa on mukana "oletus", mutta en löydä selitystä sille. Onko kyseessä "oletus"-niminen muuttuja vai missaanko jotain muuta?



VL: sen on arvo jota käytetään oletuksena josta lähdetään liikkeelle. Ks alla oleva käyttöesimerkki.
KerhonNimiController.java

13 Feb 18 (edited 13 Feb 18)

Miten Modaalisen dialogin Controllirin avataan.
vL: En ymmärrä kysymystä. Tiistain luennolla tulee näistä.

01 Feb 19 (edited 01 Feb 19)
  ModalController.showModal(KerhonNimiController.class.getResource("KerhonNimiView.fxml"),
                "Kerho", null, oletus);

Em. toimii jos .fxml-tiedostossa on kontrolleriksi laitettu:

<BorderPane xmlns="... fx:controller="fi.jyu.mit.fxgui.ModalController">

Tällöin ei tietenkään saada mitään dialogikohtaista toimintaa. Mutta tuo kelpaa ensimmäiseksi vaihtoehdoksi itse suunnitellun dialogin näyttämiseksi.

#

4.2 Dialogille oma kontrolleri

Mikäli halutaan dialogikohtaista toimintaa, pitää olla tehtynä kontrolleriluokka, joka on mainittu .fxml-tiedostossa ja kontrolleriluokan pitää toteuttaa rajapinta ModalControllerInterface, eli olla tyyliin:

public class KerhonNimiController implements ModalControllerInterface<String> {

Kontrolleriin on luvatun rajapinnan takia toteutettava metodit:

  • public void setDefault(TYPE oletus) {
    • asetetaan mahdollinen alustustieto dialogin sisälle. Tässä parametri oletus on sama mikä on showModal-kutsussa ja TYPE sama kuin edellä kirjoitettiin kulmasulkuihin.
  • public void handleShown() {
    • metodi jota kutsutaan kun dialogi on tullut näkyväksi. Tässä on tarkoitus esimerkiksi laittaa kursori (focus) haluttuun paikkaan valmiiksi. Myöhemmin esiteltävää lambda-lauseketta kutsutaan ennen tätä. Toteutus voi olla myös tyhjä, mitään erityistä ei tarvitse tehdä.
  • public TYPE getResult() {
    • showModal kutsuu tätä kun dialogi on piilotettu ja tulos pitää palauttaa.

Tehdään edellinen dialogi uudelleen FXGui.jar-kirjastoa käyttäen. Asennetun FXGui-paketin ansiosta se onnistuu pienemmällä koodimäärällä. Käytön tekee helpoksi se, että yksi metodi hoitaa selkeästi dialogin luomisen JA näyttämisen SEKÄ lopulta myös palauttaa dialogista saadun tuloksen.

Voidaan käyttää samaa dialogia kuin edellä KerhonNimiView.fxml:

#

Please to interact with this component

Huomaa että .fxml-tiedostossa on eri kontrollerin määritys (muista että paketin nimi ja luokan nimi riippuvat siitä mihin olet niitä laittanut)

fx:controller="fxKerho.KerhonNimiController"

Kontrolleriluokan toteuttamiseksi riittää toteutaa rajapinta
ModalControllerInterface<String>
jota valmis staattinen metodi ModalController.showModal voi käyttää hyväkseen. Näin kontrolleriluokkaan tarvitsee kirjoittaa vain se koodi, joka käyttää hyväkseen käyttöliittymää. Rajapinta on geneerinen, eli funktion paluuarvo voi olla mikä tahansa oliotyyppi.

#

Please to interact with this component

//
    public static String kysyNimi(Stage modalityStage, String oletus) {

Pääohjelman puolelle tämä ei aiheuta muutoksia.

#

Please to interact with this component

#

Please to interact with this component

Funktio showModal palauttaa valitun tyyppisen olion, edellisessä esimerkissä siis String-tyyppisen. Mikäli tehdään esimerkiksi modaalinen dialogi joka käsittelee jäsenen tietoja, voisi kutsu olla muotoa:

Jasen muokattu = ModalController.showModal(
                JasenDialogController.class.getResource("JasenDialogView.fxml"),
                "Kerho",
                modalityStage, jasen);

Tällöin esimerkiksi palautetaan joko viite alkuperäiseen jäseneen (ellei kontrolleri tee siitä kopioita) tai null mikäli painetaan Cancel. Kontrolleriluokka saa täysin päättää käyttäytymisen.

4.3 Enemmän parametreja modal-dialogille

Mikäli kontrolleriluokalle pitäisi viedä enemmän parametreja kuin pelkkä yksi muokattava parametri, voidaan käyttää showModal-funktion yleisempää muotoa:

    public static Jasen kysyJasen(Stage modalityStage, Jasen oletus, Kerho kerho) {
        return ModalController.<Jasen, JasenDialogController>showModal(
                JasenDialogController.class.getResource("JasenDialogView.fxml"),
                "Kerho",
                modalityStage, oletus, ctrl -> ctrl.setKerho(kerho));
    }

missä viimeinen parametri on esimerkiksi lambda-lauseke, jolla kerrotaan mitä tehdään kun dialogi on luotu valmiiksi. Tässä esimerkissä kutsutaan kontrolleriluokan setKerho-metodia. Tällä tavalla voidaan kontrolleriin laittaa haluttu määrä ominaisuuksia ennen kuin dialogi näytetään.

Tässä tapauksessa geneerinen metodi ei voi kääntäjän toimesta tietää geneerisiä tyyppejä automaattisesti, joten käytettävät tyypit pitää sanoa itse showModal-funktiolle:

ModalController.<Jasen, JasenDialogController>showModal(

Kutsu voitaisiin kirjoittaa myös muotoon:

        return ModalController.showModal(
                JasenDialogController.class.getResource("JasenDialogView.fxml"),
                "Kerho",
                modalityStage, oletus, ctrl -> ((JasenDialogController)ctrl).setKerho(kerho));

mutta tämä on pakotetun tyyppinmuunnoksen takia huonompi vaihtoehto.

#

4.4 Ruksin toiminnan hallinta

Jos modaalisen dialogin oikean ylänurkan ruksin käytös halutaan niin, että siitä sulkeminen palauttaa null:in, niin silloin kannattaa tehdä niin, että vain handleOK-metodi muuttaa palautettavan attribuutin arvon ei-nulliksi kuten edellä olevassa fxKerho/KerhonNimiController.java-esimerkissä.

Jos toisaalta halutaan ruksista jokin muu arvo, mut akuitenkin halutaan tarkistaa tiedon oikeellisuus, niin voidaan tehdä esimerkiksi niin, että setDefault-metodissa alustetaan käsittelijä ruksista sulkemiselle:

@Override
public void setDefault(String oletus) {
    textVastaus.setText(oletus);
    ModalController.getStage(textVastaus).setOnCloseRequest((event) -> {
        String teksti = textVastaus.getText();
        if ( teksti.equals("") { // ei saa olla tyhjä vastaus
            event.consume();
            return;
        }
        vastaus = teksti;
    });
}

Edellä siis ruksista sulkemista ei hyväksytä jos tekstikenttään ei ole kirjoitettu mitään. Silloin “kulutetaan” pois ruksin tapahtuma ja sulkemista ei tapahtu.

Mikäli ruksin toiminta halutaan kokonaan estää, niin voitaisiin laittaa vain:

@Override
public void setDefault(String oletus) {
    textVastaus.setText(oletus);
    ModalController.getStage(textVastaus).setOnCloseRequest(event -> event.consume() );
}

Staattisen getStage-metodin kutsussa saa olla mikä tahansa komponentti joka on lomakkeella olemassa. Sen ainoa tarkoitus on antaa yksi komponentti, josta voidaanlähteä kiipeämään ylöspäin etsimään lomakkeen stagea.

Jos haluat tarkemmin nähdä mitä tapahtuu, katso lähdekoodia:

Uudelleen määriteltyjen (@override) metodien kutsujärjestys:

  • initialize
  • setDefault
  • handleShown

5. Ei-modaalinen (modeless) dialogi FXGUI:in avulla

Ei-modaalinen (modeless) dialogi voidaan tehdä vastaavasti, silloin kutsu on muotoa:

    public static TulostusController tulosta(String tulostus) {
        TulostusController tulostusCtrl = 
          ModalController.showModeless(TulostusController.class.getResource("TulostusView.fxml"),
                "Tulostus", tulostus);
        return tulostusCtrl;
    }

Funktio showModeless palauttaa luomansa kontrolleriluokan ja sen kautta voidaan asettaa lisäominaisuuksia kontrolleriin.

Käyttäjälle modeless-dialogit ovat yleensä hankalampia, koska ne aukeavat olemassa olevan ikkunan rinnalle ja voivat helposti jäädä sen alle. Myös ohjelmoijalle ne ovat haastavampia, koska ei ole selkeää ajanhetkeä, milloin niihin syötetty tieto pitäisi käyttää. Parhaiten ne sopivat tarkoituksiin, joissa näytetään jotakin tiettyä tietoa ohjelmasta, mutta sitä ei voida niissä muokata.

Kokemuksia, vinkkejä, lisälukemista

Sana on vapaa, lisää omia vinkkejäsi alle tai kommentoiden.

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