JavaFX, czyli pierwszy widok dla lifetimera

Postanowiłem stworzyć prosty minutnik w celu zaprezentowania proces tworzenia GUI w technologii JavaFX.

Od czego zacząć?

Zaczynając pracę z JavaFX 8 wystarczy mieć JDK 1.8. Biblioteki są zawarte w narzędziach deweloperskich Javy.

Klasa uruchomieniowa

Klasa główna powinna rozszerzać klasę javafx.application.Application, która pozwala w łatwy sposób uruchomić samodzielną aplikację. W funkcji main wywołujemy metodę launch(String... args) z argumentami aplikacji. Klasa Application jest klasą abstrakcyjną z jedną metodą do zaimplementowania. W funkcji abstrakcyjnej start(Stage stage) piszemy początkowy kod naszej aplikacji.

U mnie klasa wygląda następująco:

Ustawiam ikonę do okna aplikacji. Następnie wywołuję klasę, która reprezentuje mi scene konfiguracji minutnika.

Teraz przedstawię wygląd pliku fxml:

Przedstawię co dokładnie zaimplementowałem.

<?import java.lang.*?> <?import java.net.*?> <?import java.util.*?> <?import javafx.scene.*?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <?import pl.devpragmatic.lifetimerjavafxgui.control.*?>

Kod przedstawia importy klas. W pliku fxml potrzebujemy zaimportować także klasy z paczki java.lang.*, w zwykłych klasach Javy ten import nie występuje.

<StackPane id="root" prefHeight="200.0" styleClass="mainFxmlClass" xmlns:fx="http://javafx.com/fxml/1" fx:controller="pl.devpragmatic.lifetimerjavafxgui.controller.WatchstopConfigController"> ... </StackPane>

Tutaj tworzę główny kontener, który nazywam root. Przypisuje mu klasę stylu (czyli klasę z CSS) mainFxmlClass. Sugeruje jaką powinnień mieć szerokość początkową. Ustawiam przestrzeń nazw dla fx z adresu "http://javafx.com/fxml/1" oraz klasę kontrolera "pl.devpragmatic.lifetimerjavafxgui.controller.WatchstopConfigController". StackPane jest kontenerem, który umieszcza elementy na sobie. Dokładniejsze zastosowanie można zauważyć w następnym pliku fxml.

<stylesheets> <URL value="@/styles/Styles.css"/> </stylesheets>

Tutaj podajemy link do pliku stylizującego css.

<children> ... </children>

W znaczniku children podajemy węzły podrzędne rodzica. W naszym przypadku będą w rodzicu HBox umieszczone 4 kontrolki Label, 4 NumberTextField oraz jeden przycisk. HBox to kontener, który porządkuje węzły podrzędne poziomo (czyt. Horizontal Box).

Mam nadzieję, że rozjaśniłem troszkę strukturę pliku fxml. Po drodzę wystąpiły dwie obce klasy. Jedna to kontroler, który przedstawię i opiszę poniżej:

Implementuje ona interfejs inicjalizujący kontroler. U mnie nie występuje tam żaden kod. Elementy GUI otrzymuje wykorzystując DI (Dependency Injection). Uzyskuje ten efekt za pomocą adnotacji @FXML przy prywatnych polach klasy. Jednocześnie implementuje tutaj metode kontrolera, która jest wywoływana po naciśnięciu przycisku. Oznaczam ją także adnotacją @FXML, aby kompilator pliku fxml wiedział na co ma zwracać uwagę przypisując funkcję guzikowi. Kontroler służy tutaj do zebrania danych ze sceny aplikacji i przesłania jej do drugiej w celu uruchomienia jej z tymi parametrami.

Druga niestandardowa klasa to NumberTextField:

Jest to rozszerzenie kontrolki TextField o dodatkową walidację, która pozwala na wpisywanie tylko cyfr o zadeklarowanej długości oraz z ograniczeniem górnym. Przydaje mi się to w celu uzyskania od użytkownika wpisania odpowiednich danych czasowych.

Ukończyłem pierwszą scenę. Wygląda ona jak na obrazku poniżej.

W widoku można zobaczyć jak w praktyce działa HBox. Elementy są umieszczone obok siebie w jednej lini.

Już w trakcie opisywania poprzedniej sceny wyszło, że posiadam kolejną która służy za minutnik.

Tutaj sytuacja wygląda bardzo prosto. Jest umieszczony ProgressBar a na nim tekst. Bardziej skomplikowanie wygląda jednak uruchomienie tego mechanizmu.

Tutaj wczytywanie elementów GUI podzielone jest troszkę na etapy. Nie robię tego automatycznie. Powód tego jest taki, że potrzebuje dostać się do kontrolera widoku, który będzie służył, za mechanizm zmieniania danych w widoku. Kontroler ma możliwość operowania na komponentach GUI. W trakcie jego wczytywania elementy są wstrzykiwane do niego, dlatego to on posłuży mi do wcześniej wspomnianego mechanizmu.

Pobieranie kontrolera robię za pomocą FXMLLoadera w linijce:

WatchstopController controller = fXMLLoader.getController();

Następnie uruchamiam licznik wywołując metode:

controller.runWatchstop(time, stage);

Przedstawie teraz jak wygląda mechanizm licznika. Napisałem wcześniej, że jest on umieszczony w kontrolerze.

Wyjaśnię co się tutaj dzieje.

final Timeline timeline = new Timeline(); timeline.setCycleCount(Timeline.INDEFINITE);

Tworzę tutaj obiekt Timeline (linia czasu). Pozwala ona na stworzenie animacji w GUI, której proces będzie realizowany co jakiś określony czas. U mnie tą animacją będzie zmieniający się czas i stan progresBara co sekunde. Następnie ustawiam, że ilość cykli tego obiektu jest niezdefiniowana. Pozwala to na uzyskanie efektu wykonywania animacji bez przerwy, aż wywołana została metoda stop(). Animację także można zatrzymać także metodą pause().

progressBar.prefWidthProperty().bind(stage.widthProperty().subtract(5)); progressBar.prefHeightProperty().bind(stage.heightProperty().subtract(5));

W tym fragmencie powiązuje szerokość progressBar'a do szerokości okna oraz analogicznie jego wysokość.

timeline.getKeyFrames().add(...)

Dodaje tutaj do listy obserwatorów zmian na lini czasu. Zastosowany jest tutaj popularny wzorzec w GUI obserwator. Do listy dodaje swój KeyFrame, który ma być wywoływany co okres jednej sekundy.

tmp.subTime(timeSecond); progressBar.setProgress((time.getAsSeconds()-tmp.getAsSeconds())/(double) time.getAsSeconds()); if(progressBar.getProgress() > 0.99){ progressBar.setStyle("-fx-accent: red;"); }else if(progressBar.getProgress() > 0.75){ progressBar.setStyle("-fx-accent: yellow;"); }else{ progressBar.setStyle("-fx-accent: green;"); } stage.setTitle(tmp.getAsString()); timeLabel.setText(tmp.getAsString()); if(tmp.getAsSeconds() == 0L){ timeline.stop(); progressBar.setStyle("-fx-accent: red;"); }

Tutaj obliczany jest aktualny czas do wyświetlenia oraz stosunek czasu, który już minął, w celu ustawienia odpowiedniego stanu procesu dla processBar'a. Jednocześnie w zależności od tego stanu zmienia on swój kolor. Na końcu jest sprawdzany warunek stopu obiektu timeLine.

Po ustaleniu co ma się wykonywać co sekunde, czas na odpalenie naszego licznika.

timeline.playFromStart();

Minutnik wygląda następujaco:

Tak oto kończę swój wpis. Mam nadzieję, że przybliżyłem troszkę mechanikę tworzenia aplikacji samodzielnych z GUI. Zapraszam do zapoznania się z moim poprzednim wpisem w którym wyjaśniam czym jest JavaFx. A jakby kogoś interesował programik (to jeżeli ma JRE 8) to zapraszam do pobrania tutaj.

Comments

Popular posts from this blog

Why TDD is bad practice?

How correctly imitate dependencies?

Software development using Angular 5 with Spring Boot