Interfejs graficzny…

od zawsze mając na myśli interfejs graficzny dla swojej aplikacji desktopowej czułem, że będzie wyglądała bardzo profesjonalnie. Co prawda to była tylko moja nadzieja, a może nawet marzenia.

Jednak nadal uważam, że bardzo ważnym czynnikiem aplikacji jest właśnie sam interfejs graficzny. Widocznie to ja źle tworzyłem interfejs graficzny, no cóż nie jestem UX Designerem. Tak czy siak, czy jest to aplikacja desktopowa czy webowa, interfejs jest bardzo ważny. W końcu to z niego korzysta nieznany nam użytkownik, który prawdopodobnie spędzi w naszej aplikacji sporo swojego czasu – oczywiście o ile interfejs będzie udany!

W tygodniu 8 pokażę Ci jak przy użyciu JavaFx stworzyć interfejs graficzny do swojej aplikacji – ostatecznie oczywiście będzie to aplikacja Management. 😉

Na samym wstępie chcę uprzedzić Cię, że kod, który zobaczysz w tej lekcji nie jest wzorcem dobrego, czystego kodu. Ta lekcja ma służyć tylko jako krótki wstęp do JavyFx – miejmy nadzieję, że w kolejnych lekcjach, kod który będizesz miał możliwość oglądać będzie dużo lepszej jakości. 😀

Tworzenie projektu

Nie mogłoby być inaczej jak stworzenie nowego projektu, nie różni się ono za bardzo od zwykłego tworzenia projektu – oczywiście oprócz konieczności zaznaczenia, że projekt będzie korzystał z JavaFx.

Uwaga: Twoje okno tworzenia projektu może się nieco różnić od wersji IntelliJ Idea – zależnie od tego czy masz Community czy Enterprise Edition.

Mając już utworzony projekt możemy przejść dalej, zobaczmy co tam mamy.

Struktura projektu

Nie wspomniałem jeszcze czym jest JavaFx – to nic innego jak narzędzia do łatwego budowania interfejsów graficznych aplikacji np. desktopowych. W internecie możesz oczywiście znaleźć wiele tutorialii na temat tworzenia interfejsów przy użyciu Swinga – jednak nie trać czasu na niego, nawet sam producent (Oracle) zaleca używanie JavaFx.

W projekcie JavyFx zostały wygenerowane tylko trzy pliki:

  • Main – to w tym pliku oczywiście rusza nasza aplikacja, interfejs graficzny (okienko) jest inicjalizowane przez kilka prostych linii kodu:
@Override
public void start(Stage primaryStage) throws Exception{
    Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
    primaryStage.setTitle("Hello World");
    primaryStage.setScene(new Scene(root, 300, 275));
    primaryStage.show();
}


public static void main(String[] args) {
    launch(args);
}

Ze względu, że klasa Main dziedziczy po Application możemy nadpisać metodę start, w której definiujemy jak ma zostać zainicjalizowane okienko – w metodzie main wywołanie launch(args) powoduje utworzenie okienka – metoda ta jest również zaczerpnięta z klasy Application.

  • sample.fxml – są to szablony, z których możemy korzystać w interfejsie. Jest to plik ze składnią XML, gdzie możemy dodawać przeróżne wbudowane w JavaFx komponenty np. inputy, buttony itd. oraz dodawać kontrolery (o nich niżej). Taki plik to nic innego jak to co będzie wyświetlane w okienku.
  • Controller – został również wygenerowana klasa Controller, która pozwala na definiowanie zachowań dla naszego interfejsu – a dokładnie komponentów, które zostały zdefiniowane w pliku fxml. Możemy mieć wiele różnych kontrolerów, ten akurat jest podpięty do sample.fxml, ponieważ zostało to zdefiniowane właśnie w tym pliku:
<GridPane fx:controller="sample.Controller"
          xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10">
</GridPane>

Ogólne koncepcje

Poniżej przedstawię Ci podstawowe zagadnienia po to, abyś wiedział o czym będę pisał w kolejnej części lekcji.

  • Stage – możemy przetłumaczyć sobie to jako teatr (poniżej powiem czemu po prostu nie scena), stage jest niczym jak całym wygenerowanym oknem interfejsu graficznego.
  • Scene – lepiej nazwy sceny użyć tutaj, sceny to nic innego jak interfejsy, które mają zostać wyświetlone na stage (w teatrze) – sceny mogą być definiowane w plikach fxml lub po prostu przy użyciu Javy, co my będziemy robić w tej lekcji
  • Parent – jest to typ interfejsu graficznego, który jest tworzony na podstawie pliku fxml np.
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
  • GridPane – jest to siatka (układ), z którego możemy skorzystać w naszych scenach do ułożenia elementów
  • Label – jest to etykieta, będzie wykorzystywać tego np. do wyświetlania nazw koło inputów – czyli przyczepimy mu etykietę
  • TextField – jest to input, czyli okienko do wprowadzania danych – trzeba pamiętać, że zawsze zwraca Stringa. Chcesz liczbę? Musisz dokonać konwersji na własną rekę lub spróbować zaimplementować TextFormatter.
  • Button – nic innego jak przycisk, możemy złapać akcję wciśnięcia go co pozwoli nam odpowiednio zareagować.

I taki mały słowniczek Ci wystarczy, przejdźmy do prostego użycia JavaFx tworząc interfejs przy użyciu kodu Javy.

Pierwszy interfejs!

Zanim przejdziemy do implementacji interfejsu, stwórzmy sobie jeszcze szybko prostą klasę Student:

package sample;

public class Student {
    private String name;
    private String lastName;

    public Student(String name, String lastName) {
        this.name = name;
        this.lastName = lastName;
    }

    public String getName() {
        return name;
    }

    public String getLastName() {
        return lastName;
    }

    @Override
    public String toString() {
        return name + " " + lastName;
    }
}

W klasie Main zadeklarujmy sobie na początek kilka pól:

List<Student> students = new LinkedList<>();
Stage primaryStage;
int width = 500;
int height = 300;

primaryStage będzie obiektem głównego teatru, gdzie będziemy mogli zmieniać sceny, width oraz height będą wymiarami okienka, a lista jeszcze nam się przyda. 😉

Formularz dodawania studentów

Stwórzmy sobie na początek metodę, która będzie dodawała formularz tworzenia studentów do podanego jej grida (GridPane).

private GridPane setStudentAddFormToGrid(GridPane grid) {
    
}

Dodajmy do grida nagłówek:

Label studentTitleLabel = new Label("Add student: ");
studentTitleLabel.setFont(Font.font(20));

Etykietę odnoszącą się do imienia studenta:

Label studentNameLabel = new Label("Student name: ");
studentNameLabel.setFont(Font.font(14));

Font.font(size) – ustawia wielkość czcionki naszego Labela.

Dodajmy input, w który będziemy mogli wpisać imię użytkownika:

TextField studentNameInput = new TextField();
studentNameInput.setPromptText("Type student name");

setPromptText odpowiada za ustawienie tzw. placeholdera czyli tekstu, który jest wyświetlany w inpucie, gdy nie kliknęliśmy na niego – taka mała podpowiadajka. 😉

Powtórzmy Label i TextField dla nazwiska studenta:

Label studentLastNameLabel = new Label("Student last name: ");
  studentLastNameLabel.setFont(Font.font(14));

  TextField studentLastNameInput = new TextField();
  studentLastNameInput.setPromptText("Type student last name");

Czas jeszcze stworzyć przycisk, który zatriggujemy na onClick – i będziemy wtedy tworzyć nowego studenta i dodawać go do listy.

Button createStudentButton = new Button("Add Student");

Cały układ GridPane jest siatką, która ma swoje kolumny i wiersze. Z tego względu, każdemu z tych obiektów musimy dodać odpowiednią kolumnę i wiersz – są to po prostu indexy.

int row = 1;
int col = 1;
GridPane.setConstraints(studentTitleLabel, col, row++);
GridPane.setConstraints(studentNameLabel, col, row++);
GridPane.setConstraints(studentNameInput, col, row++);
GridPane.setConstraints(studentLastNameLabel, col, row++);
GridPane.setConstraints(studentLastNameInput, col, row++);
GridPane.setConstraints(createStudentButton, col, row++);

Kolumna dla wszystkich będzie wspólna, jednak poprzez row++ otrzymamy zmianę wiersza każdego z elementów.

Skoro mamy już też stworzony nasz przycisk to musimy zatriggerować na nim akcję – stwórzmy do tego osobną metodę:

private void setActionOnCreateStudentButtonClick(Button button, TextField studentNameInput, TextField studentLastNameInput) {
}

Przekażemy do niej przycisk, który ma być zatriggerowany oraz pola, z których musimy sczytać wartości.

Będzie to przez chwilę dla Ciebie mała magia – czyli wyrażenia lambda:

private void setActionOnCreateStudentButtonClick(Button createStudentButton, TextField studentNameInput, TextField studentLastNameInput) {
    createStudentButton.setOnAction(e -> {
    })
}

Wyrażenie lambda to nic innego jak anonimowa funkcja, która ma zostać wywołana po kliknięciu buttona. Wyrażenia lambda weszły w życie wraz z Javą 8, jest to w skrócie duże ułatwienie pozwalające na definiowanie akcji bez tworzenia do tego osobnych metod. Sprawdza się np. teraz – więcej o lambdach możesz przeczytać np. tutaj.

W ciele lambdy (funkcji) musimy już tylko sczytać wartości z inputów przy użyciu getText()

Student student = new Student(studentNameInput.getText(), studentLastNameInput.getText());

Wyczyścić zawartość inputów:

studentNameInput.clear();
studentLastNameInput.clear();

I dodać studenta do listy:

students.add(student);

No i cała metoda wygląda ostatecznie tak:

private void setActionOnCreateStudentButtonClick(Button createStudentButton, TextField studentNameInput, TextField studentLastNameInput) {
    createStudentButton.setOnAction(e -> {
        Student student = new Student(studentNameInput.getText(), studentLastNameInput.getText());
        studentNameInput.clear();
        studentLastNameInput.clear();
        students.add(student);
    });
}

Wróćmy jeszcze do metody setStudentAddFormToGrid – jest jeszcze jeden ważny czynnik, czyli: grid.getChildren().addAll – który pozwala na dodanie wszystkich wcześniej elementów do naszego Grida.

grid.getChildren().addAll(studentTitleLabel, studentLastNameLabel, studentNameInput, studentLastNameInput, studentNameLabel, createStudentButton);

I jeszcze zwrócenie grida:

return grid;

I mamy całą metodę:

private GridPane setStudentAddFormToGrid(GridPane grid) {
    Label studentTitleLabel = new Label("Add student: ");
    studentTitleLabel.setFont(Font.font(20));

    Label studentNameLabel = new Label("Student name: ");
    studentNameLabel.setFont(Font.font(14));

    TextField studentNameInput = new TextField();
    studentNameInput.setPromptText("Type student name");


    Label studentLastNameLabel = new Label("Student last name: ");
    studentLastNameLabel.setFont(Font.font(14));

    TextField studentLastNameInput = new TextField();
    studentLastNameInput.setPromptText("Type student last name");

    Button createStudentButton = new Button("Add Student");

    int row = 1;
    int col = 1;
    GridPane.setConstraints(studentTitleLabel, col, row++);
    GridPane.setConstraints(studentNameLabel, col, row++);
    GridPane.setConstraints(studentNameInput, col, row++);
    GridPane.setConstraints(studentLastNameLabel, col, row++);
    GridPane.setConstraints(studentLastNameInput, col, row++);
    GridPane.setConstraints(createStudentButton, col, row++);

    setActionOnCreateStudentButtonClick(createStudentButton, studentNameInput, studentLastNameInput);


    grid.getChildren().addAll(studentTitleLabel, studentLastNameLabel, studentNameInput, studentLastNameInput, studentNameLabel, createStudentButton);
    return grid;
}

setStudentsListToGrid

Kolejno stworzymy sobie metodę odpowiedzialną za dodanie do siatki listy userów – w końcu chcemy też ich sobie wyświetlać. 😉

private GridPane setStudentListsToGrid(GridPane grid) {
}

W tej metodzie będziemy mieli listę Labelów (gdzie będziemy wyświetlać studentów) do której będziemy dodawać nowych studentów, ustawiać ich wiersz i kolumnę, a na koniec dodamy do grida. I bez obawy, zrobimy to wszystko w pętli.

private GridPane setStudentListsToGrid(GridPane grid) {
    List<Label> studentsLabels = new LinkedList<>();
    int rowIndex = 1;
    for(Student student: students) {
        Label studentLabel = new Label(rowIndex  + ". " + student.toString());
        grid.setConstraints(studentLabel, 2, rowIndex);
        rowIndex++;
        studentsLabels.add(studentLabel);
    }
    grid.getChildren().addAll(studentsLabels);
    return grid;
}

Kod jest analogiczny do tego, który powstał w metodzie setStudentAddFormToGrid, tym razem mając symetryczne pola (całą listę userów) możemy po niej przejść i wygenerować automatyczne JavaFx Label.

Inicjalizacja home scene

Zainicjalizujemy sobie główną scenę, czyli tą którą wrzucimy na sam początek na deski “teatru”. 😉

public Scene initializeHomeScene() {
}

Stworzymy w niej nowy grid:

GridPane grid = new GridPane();

Dodamy do niej formularz dodawania studentów:

grid = setStudentAddFormToGrid(grid);

Wyświetlanie listy studentów:

grid = setStudentListsToGrid(grid);

I na koniec zwrócimy to jako scene – gdzie wymagane jest podać Grid (lub Parent), wysokość i szerokość sceny.

public Scene initializeHomeScene() {
    GridPane grid = new GridPane();
    grid = setStudentAddFormToGrid(grid);
    grid = setStudentListsToGrid(grid);
    return new Scene(grid, width, height);
}

Półmetek

Mając to wszystko możemy edytować klasę Main, gdzie podamy swoją własną scenę – czyli homeScene.

@Override
public void start(Stage primaryStage) throws Exception{
    this.primaryStage=primaryStage;
    Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
    primaryStage.setTitle("1024kb.pl APP");
    primaryStage.setScene(initializeHomeScene());
    primaryStage.show();
}

primaryStage.setTitle(“1024kb.pl APP”); pozwala na zmianę nazwy okna – czyli nazwa wyświetlana na pasku okienka.

Pierwsze uruchomienie

Spróbujmy uruchomić naszą aplikację i stwórzmy kilku studentów:

I jak widać studenci nie są wyświetlani na liście – co się dzieje?

Problem tkwi w tym, że scena nie odświeża się po każdym nowo dodanym studencie – przez to musimy odświeżyć naszą scenę na nowo.

Metodę możemy zdefiniować tak:

public void refreshScene() {
    primaryStage.setScene(initializeHomeScene());
}

primaryStage to nasz główny teatr, do którego możemy w każdym momencie wrzucić nową lub już istniejącą scenę.

Oraz konieczne będzie wywołanie tej metody po utworzeniu studenta, czyli tutaj:

private void setActionOnCreateStudentButtonClick(Button createStudentButton, TextField studentNameInput, TextField studentLastNameInput) {
    createStudentButton.setOnAction(e -> {
        Student student = new Student(studentNameInput.getText(), studentLastNameInput.getText());
        studentNameInput.clear();
        studentLastNameInput.clear();
        students.add(student);
        refreshScene();
    });
}

Mając to wszystko możemy ponownie uruchomić aplikację i cieszyć się jej działanie z pięknym (no prawie :P) interfejsem graficznym. 😉

Podsumowanie

W tej lekcji pokazałem Ci podstaw tworzenia interfejsu graficznego przy użyciu technologii JavaFx. Zrobiliśmy to od stronu kodu Javy, w następnej lekcji pokażę Ci jak zrobić to profesjonalnie w SceneBuilderze, który generuje dla nas pliki .fxml i jak w łatwy sposób można podpiąć do sceny konkretny kontroler odpowiadający za interakcję użytkownika z interfejsem.

Tak jak wspominałem na początku, kod nie był zbyt piekny – jednak taki urok tworzenia interfejsu z poziomu kodu – tak czy siak cały kod z lekcji możesz zobaczyć tutaj.

Nie mogłoby zabraknąć również zadania domowego, a brzmi ono tak:

  1. Dodaj do studenta ID, które będzie generowane na podstawie ostatniego utworzonego studenta (poczynając od zera)
  2. Podczas wyświetlania listy studentów koło każdego studenta dodaj przycisk “Usuń”, która po ID studenta będzie usuwało studenta z listy studentów.
  3. Zbuduj scenę edycji studenta – po kliknięciu przycisku edytuj koło studenta na liście ma zmienić się scena na scenę edycji, gdzie będzie można edytować imię i nazwisko studenta. Po akceptacji edycji ma być wykonany powrót do homeScene.

 

Kamil Klimek

Od 2016 jestem programistą Java. Przez pierwsze 4 lata pracowałem jako Full Stack Java Developer. Później postanowiłem postawić nacisk na Javę, żeby jeszcze lepiej ją poznać.

Subscribe
Powiadom o
guest
0 komentarzy
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x