Twitter: @grzegg
Kategoria: java, Tagi: - - - .

Java [38] – JavaFX cz. 4: Otwieranie plików, menu, tabela, wykres kołowy

Przyszedł czas na utworzenie bardziej złożonej aplikacji w JavieFX, przy czym będziemy to robić z wykorzystaniem FXML i SceneBuildera. Jeśli tego nie robiłeś warto najpierw zapoznać się z lekcją w której wyjaśniłem jak się tworzy taki projekt, jak można zainstalować SceneBuildera i jak z niego korzystać we współpracy z NetBeans.
W aplikacji, którą zbudujemy użyjemy nowych kontrolek i kontenera, pokażę także jak otwierać pliki oraz utworzymy wykres.

Tworzenie projektu i podstawowej formy interfejsu

Zacznijmy, oczywiście od stworzenia nowego projektu („JavaFX FXML Application”) o nazwie „DaneFX”. Pokaże się domyślny projekt z przyciskiem (Button) i etykietą (Label). Etykieta nie jest widoczna, ale można ją zobaczyć klikając po lewej stronie w rozwijanym okienku Hierarchy na element Label.

Zaczniemy od usunięcia zawartości aplikacji. Kliknij w okienku Hierarchy na AnchorPane i usuń go wybierając Menu->Edit->Delete lub używając odpowiedniego skrótu klawiszowego (można go podejrzeć w Menu). SceneBuilder najpierw upewni się, że wiemy co robimy:

Zatwierdzamy klikając na Delete. Centralna część SceneBuildera staje się pusta i szara z zaznaczonym prostokątem zachęcającym nas „Drag Library items here…”

Teraz wędrujemy do palety kontrolek po lewej stronie i tam w sekcji Containers, znajdujemy BorderPane.

Przeciągamy kontener do szarego obszaru, teraz widzimy znów zarys okna:

Niestety teraz nie widać struktury kontenera BorderPane, choć w okienku Hierarchy można dowiedzieć się, że składa się on z pięciu części: TOP, BOTTOM, LEFT, RIGHT oraz CENTER. Ich układ uwidoczni się, kiedy przeciągniemy na niego pierwszy element. Będzie to pasek menu. Rozwiń w palecie kontrolek sekcję Controls (nie Menu!) i tam zlokalizuj MenuBar. Przeciągnij go na obszar projektowanej aplikacji, wtedy pojawi się struktura kontenera BorderPane.

Jak widać nazwy obszarów odpowiadają nazwom, co więcej jest to układ, który sprawdza się dla wielu aplikacji. W części górnej (TOP) zwykle umieszcza się menu, na dole (BOTTOM) można umieścić np. pasek wyświetlający różne informacje a w obszarach pomiędzy nimi umieszcza się inne elementy interfejsu. Oczywiście nie musimy wykorzystać wszystkich obszarów.

Pasek menu umieszczamy oczywiście na górze:

Rozwijając „drzewko” w okienku Hierarchy widać strukturę utworzonego paska menu. Domyślnie posiada on trzy sekcje: File, Edit oraz Help, a każdy z nich po jednym elemencie. Ponieważ nasza aplikacja nie będzie szczególnie skomplikowana, usuń od razu sekcje Edit i Help. Najłatwiej je zaznaczać w okienku Hierarchy i usuwać.

Pozostała nam sekcja File z elementem Close. Zaznacz w Hierarchy sekcję Menu i po prawej w Inspektorze zmień zawartość pola Text z „File” na „Plik”.

Podobnie zrób z elementem menu, zmień „Close” na „Zamknij”.

Jeśli teraz uruchomisz aplikację z poziomu NetBeans (nie zapomnij wcześniej zapisać pracy w SceneBuilderze) to zobaczysz, ze aplikacja posiada rozwijane menu, ale do elementu Zamknij nie jest przypisana żadna akcja. Trzeba więc to zmienić.

Modyfikujemy menu

Najpierw zaznacz w okienku z hierarchią element odpowiadający elementowi menu. Następnie w inspektorze wybierz sekcję Code i tam wypełnij pole fx:id wartością „menu_zamknij” a następnie w polu OnAction umieść nazwę metody: „zamknijAplikacjeAction”.

Niestety, SceneBuilder (na razie) w takiej sytuacji nie utworzy automatycznie odpowiedniej metody, co więcej kiedy usunęliśmy kontener AnchorPane utraciliśmy powiązanie elementów opisanych w pliku FXMLDocument.fxml z plikiem FXMLDocumentController.java. Trzeba to poprawić.

Zacznijmy od modyfikacji pliku FXMLDocument.fxml

Znajdź tam znacznik otwierający dla konteneru BorderPane, powinien wyglądać tak:


<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1">

Nie ma tu informacji o kontrolerze, w którym znajduje się wykonywalny kod, dodajmy go: fx:controller="danefx.FXMLDocumentController. Część danefx odpowiada nazwie pakietu, jeśli inaczej nazwałeś projekt/pakiet to zmodyfikuj odpowiednio ten element.

Teraz znacznik wygląda tak:


<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="danefx.FXMLDocumentController">

Czas zmodyfikować plik FXMLDocumentController.java. Przede wszystkim usuwamy niepotrzebną już deklarację etykietki oraz kod przeznaczony dla usuniętego przycisku:

Zamiast niego umieszczamy kod metody, którą przypisaliśmy dla elementu menu:

Zadbaj jeszcze o zaimportowanie odpowiedniej biblioteki.

Teraz jeśli uruchomimy aplikację i wybierzemy z menu Zamknij to program grzecznie się zamknie.

Dodajemy tabelę

Następną kontrolką jaką dodamy do aplikacji będzie TableView. Jak sama nazwa wskazuje, ma ona formę tabeli, w której można umieszczać na przykład dane, co też zrobimy. Znajdź w palecie kontrolek w sekcji Controls kontrolkę TableView.

Przeciągnij ją do części LEFT kontenera.

Po lewej stronie pojawiła się pusta tabela zawierająca domyślnie dwie kolumny. W razie potrzeby można dodać kolejne kolumny przeciągając je z palety na wstawioną tabelę ale w naszej aplikacji wystarczą dwie.

Kliknij na pierwszą kolumnę i w inspektorze w sekcji Properties ustaw wartość pola Text na „Seria”, będzie to nazwa kolumny widoczna w jej nagłówku. Analogicznie nadaj nazwę „Średnia” drugiej kolumnie. Wszystko zapisz.

Następnie znów kliknij na pierwszą kolumnę i w inspektorze przejdź do sekcji Code. Tam ustaw wartość pola fx:id na „nazwaColumn”, dla drugiej kolumny ustaw tą wartość na „sredniaColumn”. Po zapisaniu zmian plik FXMLDocument.fxml powinien wyglądać tak:

Dodajemy dane do tabeli

Nasza tabela jest teraz przygotowana do przyjęcia danych. Dane nie będą skomplikowane, każdy rząd będzie odpowiadał jednej serii danych a właściwie nazwie tej serii i średniej z danych. Zaczniemy od stworzenia nowej klasy w naszym pakiecie o nazwie Seria.java. Obiekt tej klasy, będzie przechowywał dane dla jednej serii. Klasa zawiera dwa pola „nazwa” i „średnia”, pierwsze z nich typu String, drugie Double. Ponadto posiada konstruktor przyjmujący jako argumenty wartości obu pól a także odpowiednie akcesory.

Kod klasy Seria.java.

Docelowo dane będziemy wczytywać z pliku, ale tymczasem zrobimy to bezpośrednio z kodu. W metodzie initialize() pliku FXMLDocumentController.java umieść kod, a także nie zapomnij o deklaracjach tablicy i kolumn:

W pierwszej części tworzymy kolekcję obiektów. JavaFX udostępnia własne wersje kolekcji i map implementujących i poszerzających interfejsy Set, List i Map. Tutaj będziemy wykorzystywać observableArrayList, pozostałe można znaleźć w dokumentacji pakietu javafx.collections a o sposobach i przykładach ich użycia można poczytać np. w tutorialu firmy Oracle. Są one „obserwowalne” zatem można do nich np. dodawać listenery.

Jak widać, stworzyliśmy listę o nazwie dane i wypełniliśmy ją obiektami typu Seria, z których każdy posiada nazwę i wartość średniej. W kolejnych liniach kodu wiążemy listę z tablicą a poszczególne kolumny z polami obiektów.

Jeśli teraz uruchomimy aplikację, uzyskamy taki rezultat:

Jak widać, tabela jest trochę za szeroka. Można temu zaradzić, ustawiając dla tabeli w zakładce Layout inspektora wartość pola Pref Width na 150.

Projekt w NetBeans (spakowany w pliki .zip) na tym etapie można pobrać tutaj.

Dodajemy wykres kołowy i wstawiamy do niego dane

Teraz przyszedł czas na wstawienie wykresu.

Wybierz z palety kontrolek, z sekcji Charts wykres kołowy (Pie Chart) i umieść go w centralnej części kontenera. Następnie ustaw dla niego w sekcji Code kontrolera wartość pola fx:id na wykres1PieChart. Zapisz.

Następnie musimy umieścić w nim dane. Zatem trzeba wrócić do edycji pliku FXMLDocumentController.java. Najpierw dodaj deklarację świeżo dodanego wykresu:


@FXML private PieChart wykres1PieChart;

Obiekt typu PieChart wyświetla dane, które przekazujemy mu jako listę (observableArrayList) obiektów typu PieChart.Data, który przechowuje informację o nazwie danych i ich wartości. Tak się składa, że odpowiada to zawartości obiektów Seria. Musimy je jednak przekonwertować, w tym celu tworzymy odpowiednią metodę:

Teraz w metodzie initialize() umieszczamy kolejną instrukcję, która wstawia dane do wykresu:


wykres1PieChart.dataProperty().set(konwertujDoPieChartData(dane));

Dane zwraca w odpowiedniej formie metoda konwertujDoPieChartData().

Zapisujemy plik i uruchamiamy program. Teraz naszym oczom pokazuje się taki obrazek:

Wczytywanie danych pliku i modyfikacje aplikacji

Przyszedł czas na dodanie możliwości wczytania danych z pliku. Najpierw przygotujmy odpowiedni plik w formacie TSV, czyli dane będą odseparowane tabulatorami. Niech będą do wyniki pierwszej tury wyborów prezydenckich 2015. Dane pochodzą ze strony PKW.
Nazwij plik na przykład wybory.tsv i zapisz w dogodnym miejscu.

Uwaga: Jeśli poniższe dane skopiujesz i wkleisz do edytora, upewnij się czy dane są oddzielone tabulatorem (pomiędzy imieniem i liczbą głosów). Jeśli znajdują się tam spacje to zmień je na znak tabulatora.

Pobierz plik

Teraz trzeba dodać możliwość odczytu pliku. Zacznijmy od dodania odpowiedniej pozycji w menu. Przejdź do SceneBuildera, rozwiń sekcję Menu w palecie kontrolek a następnie hierarchię kontrolek a w niej rozwiń zawartość menu tak, by był widoczny element Zamknij. Teraz przeciągnij z palety MenuItem do drzewka hierarchii tak by znalazł się na tym samym poziomie, ale powyżej elementu Zamknij. Powinno to wyglądać tak:

Następnie w inspektorze w sekcji Properties zmień pole Text z „Unspecified Action” na „Otwórz…” a w sekcji Code wpisz w pole onAction: „otworzPlikAction”. Ta ostatnia wartość to oczywiście nazwa metody, która się będzie uruchomiała po wybraniu świeżo dodanej pozycji menu. Trzeba zatem dodać tą metodę do pliku `FXMLDocumentController.java”, na razie jest ona pusta:

Dalej, dodajemy deklarację Stage, będziemy się do niej odwoływać:


@FXML
private Stage stage;

Wypełniamy treścią metodę otworzPlikAction():

Na razie metoda pozwala wybrać plik, do czego służy okienko dialogowe FileChooser a także drukuje w terminalu prowadzącą do niego ścieżkę (zwróć uwagę jaka metoda jest wywoływana). Do okna dialogowego dodajemy filtr, który pozwala na selekcję plików możliwych do otworzenia w zależności od przedłużenia nazwy pliku. Tutaj filtr pozwala na wybranie plików, które mają przedłużenie .tsv ale możesz oczywiście dodać dowolną liczbę takich filtrów dla różnych rodzajów plików. Oczywiście trzeba później zadbać, aby aplikacja mogła je obsłużyć. Okienko dialogowe FileChooser może także zapisywać pliki. W tym celu należy użyć metody showSaveDialog() zamiast showOpenDialog() ale nie będziemy z tej możliwości tutaj korzystać.

Uruchom program, wybierz w menu opcję Otwórz..., wybierz plik z danymi, który wcześniej utworzyliśmy, w terminalu powinna zostać wypisana ścieżka do pliku. Jeśli wszystko działa czas dopisać kod, który odczyta dane z pliku a następnie umieści je w tabeli i na wykresie.

Konwersja danych odczytanych z pliku i przekazanie ich do kontrolek

O podstawach odczytu plików tekstowych pisałem tutaj, nie będę wiec tego powtarzał.
W pliku FXMLDocumentController.java umieść nową metodę:

Jak widać, metoda ladujDane() pobiera dane z pliku tekstowego, umieszcza je w obiektach typu PieChart.Data a te z kolei zbiera w obserwowalnej liście o nazwie dane. Następnie listę te wykorzystuje zarówno jako źródło danych dla wykresu jak i dla tablicy. Mogliśmy w tym wypadku zrezygnować z obiektów typu Seria, ponieważ obiekt typu PieChart.Data przechowuje String i double w polach o nazwach odpowiednio name i pieValue. Możemy zatem usunąć z projektu plik Seria.java. Część metody odpowiadająca za przypisywanie danych do tabeli i wykresu odpowiada zawartości metody initialize() po uwzględnieniu odpowiednich modyfikacji związanych z ujednoliceniem sposobu przechowywania danych. Trzeba także zmodyfikować deklaracje obiektów, usunąć zawartość metody initialize() oraz metodę konwertujDoPieChartData(), która też już jest zbędna. Kompletny kod pliku FXMLDocumentController.java wygląda teraz tak:

FXMLDocumentController.java

Modyfikacje interfejsu graficznego

Po uruchomieniu aplikacji i załadowaniu danych ujrzymy takie okienko:

Dane się załadowały, ale teraz widać pewne niedociągnięcia w interfejsie graficznym. Nagłówki tabeli nie odpowiadają obecnym danym, ponadto szerokość kolumn jest stanowczo za wąska. Sam wykres jest co prawda mały, ale powiększając okno aplikacji można go łatwo powiększyć. Zajmijmy się zatem tabelą. Przejdź do SceneBuildera i ustaw wartość szerokości tabeli (sekcja Layout inspektora, pole pref Width na 120, a szerokość kolumn na 150 i 80. Nagłówki kolumn zmień na „Kandydat” i „L. głosów”. Warto też ustawić preferowaną szerokość BorderPane na 900 a wysokość na 600 żeby okno aplikacji było większe. Teraz ustawmy pewne właściwości wykresu (PieChart) w sekcji Properties. Title: „Wyniki I tury wyborów prezydenckich 2014”, Legend Side: TOP.

Teraz aplikacja po uruchomieniu i załadowaniu danych wygląda tak:

Dużo lepiej, prawda? Zauważ, że klikając na nagłówki kolumn, możesz sortować dane wg. nazwisk kandydatów albo liczby uzyskanych głosów (rosnąco i malejąco).

Warto jeszcze zmienić nazwy obiektów reprezentujących kolumny aby uwzględnić zmiany jakie zaszły podczas rozwoju naszej aplikacji na przykład z sredniaColumn na liczbaGlosowColumn, nie ma to oczywiście znaczenia dla działania kodu ale wpływa na jego czytelność. Zostawiam to jednak czytelnikowi.

Na koniec podaję kompletny kod pozostałych plików programu:

DaneFX.java

Ten plik w zasadzie nie został zmieniony.

FXMLDocument

6 komentarzy Java [38] – JavaFX cz. 4: Otwieranie plików, menu, tabela, wykres kołowy

  • P

    copy + paste and your code doesn’t work

    • Grzegorz

      Thanks for your comment. I have just checked the code and it works. However, I realized that the problem could be with the datafile. As I described in the text, data should be separated by tabs, but probably WordPress (or plugin) in some way changed all tabs to spaces (sometimes it do strange things with code), so when data were copied and pasted to text file, the file had bad format. You can change all spaces between names and numbers to tabs (one tab between name and number) but now I corrected page and copy and paste should work, at least on my computer worked ;-).
      However if there is other problem with the code, please describe it.

  • rysic

    Mnie ciekawi jak z poziomu kontrolera zmienić nazwę kolumn?
    Szukałem już wszędzie i nie widzę.

  • Kasia

    Proszę o pomoc- robiła krok po kroku i nie działa przy otwieraniu pliku: Wyskakuje błąd:

    Caused by: java.lang.ClassCastException: sun.nio.fs.WindowsPath cannot be cast to javafx.scene.shape.Path
    at sample.Controller.ladujDane(Controller.java:54)
    at sample.Controller.otworzPlikAction(Controller.java:48)

Leave a Reply