Programovanie (2) v Jave
1-INF-166, LS 2016/17

Úvod · Pravidlá · Prednášky · Projekt · Netbeans · Odovzdávanie · Test a skúška
· Vyučujúcich môžete kontaktovať pomocou e-mailovej adresy E-prg.png (bude odpovedať ten z nás, kto má príslušnú otázku na starosti alebo kto má práve čas).
· Predvádzanie projektov bude v pondelok 5.6. od 9:00 do 12:00 a v utorok 6.6 od 12:00 do 13:30 (po skúške), oboje v miestnosti M217. Na termín sa prihláste v AIS. Ak robíte vo dvojici, prihlási sa iba jeden člen dvojice.
· Body zo záverečného testu sú na testovači. Poradie príkladov: P1: do šírky, P2: topologické, P3: výnimky, P4: iterátor, P5: testy, P6: strom. Bolo potrebné získať aspoň 20 bodov zo 40.
· Opravný test bude 19.6.2017 od 9:00 v miestnosti M-I. Na termín sa prihláste v AISe.
· Zapisovanie známok a osobné stretnutia ku skúške budú v utorok 13.6. 13:30-14:30 v M163 a v stredu 14.6. 14:00-14:30 v M163.


Prednáška 32

Z Programovanie
Prejsť na: navigácia, hľadanie

Oznamy

  • DÚ15 do dnes 22:00
  • DÚ16 do utorka 2.5., aplikácia v JavaFX
  • Java FX sa týka aj nepovinný projekt za bonusové body do výšky 10% známky. Na projekt si vyberte jednu zo štyroch ponúkaných tém. Svoj výber oznámte do 15.5. na testovači. Samotný projekt sa bude odovzdávať v prvom týždni skúškového, termín oznámime.
  • Tento týždeň normálne prednáška aj cvičenia
  • Budúci týždeň iba cvičenia (bude aj rozcvička)

Zložitejšie prvky v JavaFX

  • Pozrieme sa na niektoré vybrané prvky, ktoré sa zaujímavejšie alebo komplikovanejšie chovajú.
  • Skúsime jednotlivé prvky použiť na jedno spoločné zadanie - výber farby z vymedzeného zoznamu (pôvodne čierna, možnosti červená, modrá, zelená, príp. žltá)
Pane root = new Pane();
Rectangle rect=new Rectangle();
rect.setFill(Color.BLACK);

TextField

  • Na prvej prednáške sme videli TextField. Vieme, že jeho akcia je zmena textu a Enter.
  • Takže ako môžeme vybrať farbu? Najjednoduchšie je niekam napísať zoznam farieb a očakávať číslo, podľa ktorého hodnotu nastavíme.
    • Pre poriadok by bolo dobré nejakým spôsobom reagovať aj na prípady, kedy sme nedali korektnú hodnotu a používateľovi to oznámiť. Ako by sme to robili?
Label lab0=new Label("0 Black"); lab0.setLayoutY(0);
Label lab1=new Label("1 Red");   lab1.setLayoutY(30);
Label lab2=new Label("2 Green"); lab2.setLayoutY(60);
Label lab3=new Label("3 Blue");  lab3.setLayoutY(90);
TextField tf=new TextField("0"); tf.setLayoutY(120); 
       
tf.setOnAction(new EventHandler<ActionEvent>() {           
    @Override public void handle(ActionEvent event) {
        try {
            switch (Integer.parseInt(tf.getText())) {
                case 0: rect.setFill(Color.BLACK); break;
                case 1: rect.setFill(Color.RED); break;
                case 2: rect.setFill(Color.GREEN); break;
                case 3: rect.setFill(Color.BLUE); break;
            }
        } catch (Exception e){}
    }
});

root.getChildren().addAll(rect,tf,lab0,lab1,lab2,lab3);

Menu

  • Bežnou súčasťou GUI býva Menu. Či už vo forme hlavného menu, v hornej časti alebo vo forme vyskakovacieho menu.
  • Každé Menu sa skladá z prvkov, ktoré sú buď priamo prvky menu (MenuItem) alebo ďalšie Menu.
  • Na každý z prvkov pochopiteľne môžeme umiestniť EventHandler, ktorý čaká na stlačenie konkrétnej položky (pri stlačení Menu sa toto rozbalí)
  • Okrem toho celé Menu môže byť umiestnené v MenuBar, čo je vlastne lišta obsahujúca niekoľko prvkov menu (klasicky v GUI býva File, Edit...)
  • Skúsime si premyslieť, ako by naše menu mohlo vyzerať
Color (Menu)--- Red (MenuItem)
             | 
             |- Green (MenuItem)
             |
             |- Blue (MenuItem)
             |
             -- Other (Menu) --- Black (MenuItem)
  • Celé to ešte dáme do MenuBaru, ktorý umiestnime do ľavého horného rohu obrazovky
  • Pri kliknutí na MenuItem sa zmení farba nášho štvorčeka podľa vybranej položky
MenuBar menuBar = new MenuBar();

Menu menu1 = new Menu("Color");
menuBar.getMenus().add(menu1);

MenuItem menu11 = new MenuItem("Red");
menu11.setOnAction(new EventHandler<ActionEvent>(){
    @Override public void handle(ActionEvent event) { rect.setFill(Color.RED);}
});
MenuItem menu12 = new MenuItem("Green");
menu12.setOnAction(new EventHandler<ActionEvent>(){
    @Override public void handle(ActionEvent event) { rect.setFill(Color.GREEN);}
});
MenuItem menu13 = new MenuItem("Blue");
menu13.setOnAction(new EventHandler<ActionEvent>(){
    @Override public void handle(ActionEvent event) { rect.setFill(Color.BLUE);}
});
Menu menu14=new Menu("Others");
MenuItem menu141 = new MenuItem("Black");
menu141.setOnAction(new EventHandler<ActionEvent>(){
    @Override public void handle(ActionEvent event) { rect.setFill(Color.BLACK);}
});
menu14.getItems().add(menu141);
menu1.getItems().addAll(menu11,menu12,menu13,menu14);
root.getChildren().addAll(rect,menuBar);
  • Miernou obmenou môžeme rovnaké menu vytvoriť aj pri kliknutí na plochu
    • Pochopiteľne nepotrebujeme MenuBar a menu1 (Color) nebude typu Menu ale ContextMenu
    • Jeho vytvorenie zabezpečíme v metóde handle pri kliknutí myšou na plochu pomocou metódy show
      • menu1.show(root, event.getScreenX(), event.getScreenY());

Radio Buttony

  • Bežný spôsob ako vybrať jednu z niekoľkých možností je stlačenie jedného z niekoľkých gombíkov
  • My si tu ukážeme špeciálne RadioButton, ktorý má dva základné stavy - označený a neoznačený
  • Urobíme teda niekoľko RadioButtonov, ktoré reprezentujú farby, ktoré môže štvorček mať
  • Pri stlačení iného sa zmení farba štvorčeka
    • Pozor, musíme zároveň odznačiť ten, čo bol označený
    • Treba si premyslieť, čo sa stane, ak nebude označený žiaden - napríklad bude štvorček čierny (alebo odznačenie nedovolíme)
RadioButton rb1= new RadioButton("Red");
RadioButton rb2= new RadioButton("Blue");
RadioButton rb3= new RadioButton("Green");
rb1.setOnAction(new EventHandler<ActionEvent>(){
    @Override public void handle(ActionEvent event) { 
        if (rb1.isSelected()) {
            rect.setFill(Color.RED);
            rb2.setSelected(false); rb3.setSelected(false);
        } 
        else rect.setFill(Color.BLACK);
    }
});
rb2.setLayoutX(100);
rb2.setOnAction(new EventHandler<ActionEvent>(){
    @Override public void handle(ActionEvent event) { 
        if (rb2.isSelected()) {
            rect.setFill(Color.BLUE);
            rb1.setSelected(false); rb3.setSelected(false);
        } 
        else rect.setFill(Color.BLACK);
    }
});
rb3.setLayoutX(200);
rb3.setOnAction(new EventHandler<ActionEvent>(){
    @Override public void handle(ActionEvent event) { 
        if (rb3.isSelected()) {
            rect.setFill(Color.GREEN);
            rb2.setSelected(false); rb1.setSelected(false);
        } 
        else rect.setFill(Color.BLACK);
    }
});

root.getChildren().addAll(rect, rb1, rb2, rb3);
  • Vidíme, že takto spravovať viacero gombíkov môže byť vcelku otravné.
  • Keď teda chceme, aby používateľ musel vždy mať vybratú práve jednu možnosť z gombíkov použijeme ToggleGroup
    • final ToggleGroup group = new ToggleGroup();
    • Ak potom všetky gombíky pridáme do tejto skupiny pomocou rb1.setToggleGroup(group); odklikávanie gombíkov manažuje systém

Zoznam a jeho selection model

  • Asi najkomplikovanejším prvkom, ktorým môžeme vybrať jeden z prvkov je zoznam
  • Samotný zoznam je objekt typu ListView, ktorý v konštruktore potrebuje vedieť čo bude zobrazovať (v podstate nejaký ArrayList)
    • Mierny rozdiel je v tom, že ListView potrebuje taký ArrayList, ktorý umožní sledovať zmeny, ktoré nastanú
    • Takýto ArrayList je pripravený v FXCollections (obdoba Collections pre JavaFX) a implementuje generické rozhranie ObservableList<>
    • Konkrétna implementácia tohto rozhrania, ktorú budeme využívať je FXCollections.observableArrayList
    • Má niekoľko konštruktorov, kde ako parameter môže byť nič, konkrétny zoznam alebo ArrayList toho istého typu
    • Akákoľvek zmena, ktorá sa stane v ObservableList sa okamžite premietne aj do zobrazovaného zoznamu (čo môžeme využiť a počas behu napr. pridávať farby)
ObservableList<Color> data = FXCollections.observableArrayList(Color.BLACK, Color.RED, Color.GREEN, Color.BLUE);
ListView listView = new ListView(data);
listView.setMaxWidth(100);
listView.setMaxHeight(150);
  • Ďalšia špeciálna vlastnosť ListView je prístup k výberu - samotný ListView nevie, kto je vybratý
  • Na tieto účely má každý ListView svoj SelectionModel, ktorý si od neho vieme vypýtať pomocou metódy getSelectionModel()
  • SlectionModel (defaultne je nastavený tak, že vybraný môže byť jeden prvok, ale dá sa to zmeniť) vie pomocou svojich metód pracovať s výberom - upravovať resp. odovzdať
  • Vieme teda pomocou ListView nastaviť farbu a po stlačení gombíka na túto farbu zmeniť náš štvorček
Button btn=new Button("Change Color");
btn.setLayoutY(200);
btn.setOnAction(new EventHandler<ActionEvent>(){
    @Override public void handle(ActionEvent event) {
        Color col=(Color)listView.getSelectionModel().getSelectedItem();
        rect.setFill(col);
    }
});

root.getChildren().addAll(rect, listView, btn);

ChangeListenery

  • Pri zozname typu ListView a vlastne aj pri prvkoch typu RadioButton sme narazili (alebo mohli naraziť) na situáciu, kedy sme chceli zistiť, či nastala nejaká udalosť v celom prvku (resp. zoskupení prvkov)
    • napríklad sme chceli niečo urobiť, keď sa zmenil vybraný prvok zoznamu (resp. vybraný RadioButton)
    • pri skupine RadioButtonov sme to vedeli obísť, keď sme na každý samotný gombík dali EventHandler, pri zozname nemáme veľmi na čo pri prvkoch čakať - posunitie na ďalší prvok nemusí vzniknúť kliknutím používateľa
  • Na takéto situácie sa používajú tzv. Properties a ChangeListenery
    • Property je vlastnosť, ktorá na nejakom prvku nastáva
    • ChangeListener je niečo podobné ako EventHandler, len očakáva zmenenie vlastnosti, na ktorú je nasadený a zavolá svoju metódu changed()
group.selectedToggleProperty().addListener(new ChangeListener<Toggle>(){
    @Override public void changed(ObservableValue<? extends Toggle> observable, Toggle oldValue, Toggle newValue) {
            if (group.getSelectedToggle()==null) rect.setFill(Color.BLACK);
            else {
                if (newValue.equals(rb1)) rect.setFill(Color.RED);
                if (newValue.equals(rb2)) rect.setFill(Color.GREEN);
                if (newValue.equals(rb3)) rect.setFill(Color.BLUE);
            }
    }
});
  • Ešte dôležitejšie je to pri ListView, kde sa k tomu, že nastala zmena dostávame komplikovanejšie (nemusíme to zistiť z Eventov)
    • Vlastnosť, ktorej zmenu očakávame je vlastne vlastnosť SelectionModelu (pripomínam, že ten pracuje s výberom prvkov a nie sám ListView)
    • Ide o vlastnosť selectedItemProperty()
listView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Color>(){
    @Override public void changed(ObservableValue<? extends Color> observable, Color oldValue, Color newValue) {
        if (newValue==null) rect.setFill(Color.BLACK);
        else rect.setFill(newValue);
    }
});
  • Vlastnosti majú aj iné prvky. Napríklad scene, ktoré tvorí vlastne základ nášho hlavného okna aplikácie má zaujímavé vlastnosti widthProperty a heightProperty.

Vďaka nastaveniu listenera na niektorú z týchto vlastností vieme reagovať keď sa nám zmení veľkosť okna.

scene.widthProperty().addListener(new ChangeListener<Number>() {
    @Override public void changed(ObservableValue<? extends Number> observableValue, Number oldSceneWidth, Number newSceneWidth) {
        System.out.println("Width: " + newSceneWidth);
    }
});
scene.heightProperty().addListener(new ChangeListener<Number>() {
    @Override public void changed(ObservableValue<? extends Number> observableValue, Number oldSceneHeight, Number newSceneHeight) {
        System.out.println("Height: " + newSceneHeight);
    }
});
  • Vlastnosť si môžete vhodne pripraviť nielen pre tieto prvky - môžete ju definovať aj pre svoju triedu
  • Viac o Properties a ich spracovaní môžete pozrieť tu
  • POZOR! v iných jazykoch (napríklad .NET) sú Properties niečo úplne iné

Viacoknové aplikácie

  • Často chceme v aplikácii zadávať niektoré údaje nie na hlavnom okne, ale bokom (v novom okne, ktoré nastaví konkrétnu hodnotu, ktorá nás trápi
  • Druhá možnosť prečo chceme druhé okno je nejaký oznam používateľovi (niečo sa nepodarilo, podarilo, iné..)
  • Máme niekoľko možností, ako takéto okno vytvoriť
    • Pre špeciálne hodnoty, ktoré je bežné nastavovať existujú predpripravené dialógy (farba, súbor)
    • Nájdeme si jednoduchý dialóg (informačný, warning, zadanie hodnoty), ktorý upravíme k obrayu svojmu
    • Vytvoríme si niečo vlastné

Predpripravené dialógy

  • ColorPicker - je v podstate mierne upravený ComboBox, ktorý vie okrem už pripravených veľa farieb pridať aj výber vlastnej farby
final Rectangle rect=new Rectangle();
ColorPicker btn1=new ColorPicker(Color.BLACK);
btn1.setOnAction(new EventHandler<ActionEvent>(){
    @Override public void handle(ActionEvent event) {
        rect.setFill(btn1.getValue());
    }
});
  • FileBrowser - je dialóg, ktorý ako výsledok odovzdá súbor, ktorý bol vybratý
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Open Resource File");
String currentDir = System.getProperty("user.dir") + File.separator;
fileChooser.setInitialDirectory(new File(currentDir));
File file = fileChooser.showOpenDialog(primaryStage);
try {
    Scanner sc = new Scanner(file);
    ...                               //nacitanie farby
    rect.setFill(c);
    sc.close();
}
catch (Exception e){
    System.err.println("Neexistujuci alebo chybny subor!");
}

Jednoduché dialógy

  • Pozrieme sa aké jednoduché dialógy existujú a upravíme si ich (viac ukážok tu)
  • Ide o špeciálne triedy dialógov, ktoré sa očakáva, že sú jednoduchšie (a bežne používané)
  • Čo si treba vždy premyslieť je, čo ak používateľ nič nezadá (vedieť spracovať) a ako veľmi chceme nedovoliť pracovať zvyšku aplikácie kým je otvorený dialóg (tzv. Modalita) - z časti ovplyvníme tým, ako dialóg ukážeme
  • Keď ho máme vytvorený, ukážeme ho pomocou metódy showAndWait() alebo show() podľa toho, či vyžadujeme blokovanie zvyšku aplikácie
  • Výsledok dialógu môžeme získať pomocou Optional<> result = dialog.showAndWait();
    • Typ v tejto generickej metóde závisí od typu dialógu a od toho, čo z neho môžeme chcieť vrátiť
    • Pre alert obvykle gombík (resp. typ gombíka), ktorý bol stlačený, pre textový vstup to bude pochopiteľne zadaný vstup a pod.
    • K výsledkom dialógu sa dostávame pomocou metód result.isPresent() a result.get()

Alert (informačný, upozorňovací a pod.)

  • Pri konštrukcii musíme zadať Alert.AlertType, ktorý hovorí o dizajne a prvkoch, ktoré sú defaultne na ňom
  • Môžeme mu pridať aj vlastné gombíky a tým rozšíriť možnosti, čo používateľ môže odpovedať
Alert alert = new Alert(AlertType.CONFIRMATION);
alert.setTitle("Alert Dialog");
alert.setHeaderText("What color do you want?");
alert.setContentText("Red?");
ButtonType buttonOK = new ButtonType("OK");
ButtonType buttonCancel = new ButtonType("Cancel");

alert.getButtonTypes().setAll(buttonOK, buttonCancel);
Optional<ButtonType> result = alert.showAndWait();
if (result.isPresent()&&result.get() == buttonOK){
    rect.setFill(Color.RED);
}
else {}

TextInputDialog

  • zadávanie textovej hodnoty (v podstate TextField s potvrdzovacím tlačidlom na vlastnom dialógu)
  • Pri konštrukcii môžeme zadať defaultnú hodnotu textového poľa na dialógu
TextInputDialog textin = new TextInputDialog();
textin.setTitle("TextInput");
textin.setHeaderText("Set the red value of the Rectangle Color:");
textin.setContentText("double value 0.0-1.0");

Optional<String> result = textin.showAndWait();
if (result.isPresent()){
    try {
        String str=result.get();
        ...                     //zisti farbu col zo str
        rect.setFill(col);
    } catch (Exception e){}
}

ChoiceDialog<>

  • výber z položiek zadaných ArrayListom
  • Pri konštrukcii zadáme ArrayList a defaultnú hodnotu
List<Color> choices = new ArrayList<>();   ...       //napln choices

ChoiceDialog<Color> dialog = new ChoiceDialog<>(Color.BLACK, choices);

dialog.setTitle("Choice Dialog");
dialog.setHeaderText("Look, a Choice Dialog");
dialog.setContentText("Choose your color:");

Optional<Color> result = dialog.showAndWait();
if (result.isPresent()){
    rect.setFill(result.get());
}

Všeobecný dialóg

  • dá sa aj všeobecný Dialog s niekoľkými výstupmi, ale už to asi nie je jednoduchý dialóg

Vlastné dialógy

  • Pre zložité dialógy si môžeme vytvoriť vlastný Stage, na ktorý umiestnime scénu, panel a pracujeme s ním ako s úvodným oknom aplikácie
  • Samozrejme si môžeme Stage aj rozšíriť a vytvoriť si vlastnú triedu, ktorá vie niečo naviac (napríklad má nejaké premenné navyše)
  • Doležité je nezabudnúť nastaviť Modalitu a vedieť sa vyrovnať s tým, že používateľ mohol okno zavrieť bez zadania (korektného) výsledku
Stage dialog = new Stage();
dialog.initStyle(StageStyle.UTILITY);
dialog.initModality(Modality.APPLICATION_MODAL);
TilePane pan=new TilePane();
Scene sc = new Scene(pan);

TextField rText= new TextField();
TextField gText= new TextField();
TextField bText= new TextField();
Label rLabel=new Label("Red 0.0-1.0: ");
Label gLabel=new Label("Green 0.0-1.0: ");
Label bLabel=new Label("Blue 0.0-1.0: ");
Button ok=new Button("OK");

ok.setOnAction(new EventHandler<ActionEvent>(){
    @Override
    public void handle(ActionEvent event) {
        try {
            double r =Double.parseDouble(rText.getText());
            double g =Double.parseDouble(gText.getText());
            double b =Double.parseDouble(bText.getText());
            if (r>=0 && g>=0 && b>=0 && r<=1 && g<=1 && b<=1){
                rect.setFill(new Color(r,g,b,1));
                dialog.close();    
            }
        }
        catch (Exception e){
            System.err.println("ERR:NekorektnaFarba");
        }                        
    }
});

pan.setPrefColumns(2);
pan.getChildren().addAll(rLabel,rText,gLabel, gText,bLabel,bText,ok);
dialog.setScene(sc);
dialog.showAndWait();