Controller…

choć wdrożyliśmy się już w technologię JavaFX to nie użyliśmy jeszcze najważniejszej jego części – Kontrolerów. Póki co wszystko staraliśmy się zrobić poprzez Maina, jednak jak łatwo można było zauważyć poziom skomplikowania i nieczystości kodu rósł proporcjonalnie do każdego nowo dodanego przycisku lub etykiety.

Czas z tym skończyć – nadchodzi era Controllera! 😀

Punkt startowy

W poprzedniej lekcji skończyliśmy na utworzeniu szablonu kalkulatora przy użyciu narzędzia SceneBuilder, udało się nam nawet uruchomić owy interfejs graficzny. Oczywiście nie jest on w ogóle użyteczny, dlatego czas to zmienić. W tej lekcji stworzymy Controller, dzięki któremu “oprogramujemy” nasz interfejs kalkulatora – do dzieła!

W tej lekcji posłużę się interfejsem bardzo podobnym do tego z poprzedniej lekcji – różnica jest jedna, w poprzedniej lekcji zapomniałem o dodaniu przycisku “równa się” – dlatego w nowej wersji zamieniłem kropkę na właśnie ten przycisk.

W celu pracy na tym samym interfejsem co ja użyj mojego projektu:

git clone https://github.com/1024kb-pl/KJOP_JavaFX_Templates

Co do pliku .fxml – nazwę kontrolera możesz zmienić w tej linii:

<SplitPane dividerPositions="0.22110552763819097" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" orientation="VERTICAL" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">

fx:controller określa nazwę kontrolera – dodatkowo podaje się pakiet, w którym się znajduje.

SplitPane jest akurat w moim interfejsie, u Ciebie może to być inny komponent – chodzi tylko o klucz fx:controller.

<Button fx:id="button_plus" mnemonicParsing="false" onAction="#clickButtonAdd" prefHeight="47.0" prefWidth="139.0" text="+" GridPane.halignment="CENTER" GridPane.valignment="CENTER" />

Metody z kontrollera do akcji przypisujemy w taki sposób, że podajemy z hashem nazwę metody z kontrolera przy kluczu onAction.

Przypominam, że te wszystkie rzeczy wcześniej zostały skonfigurowane w SceneBuilderze.

Kalkulator

Zanim przejdziemy do implementacji kontrolera naszego interfejsu zbudujmy prostą klasę odpowiedzialną za działanie kalkulatora.

Będzie do niej mogli wrzucić dwie liczby – zapisywane będą one w polach, po wywołaniu odpowiedniej metody na liczbach będzie wykonywana odpowiednia operacja np. dodawania.

package sample;

public class Calculator {
    private final static Calculator instance = new Calculator();
    private int firstNumber = 0;
    private int secondNumber = 0;

    private Calculator() {

    }

    public static Calculator getInstance() {
        return instance;
    }

    public void setFirstNumber(int firstNumber) {
        this.firstNumber=firstNumber;
    }

    public void setSecondNumber(int secondNumber) {
        this.secondNumber = secondNumber;
    }

    public int add() {
        return firstNumber + secondNumber;
    }

    public int subtract() {
        return firstNumber - secondNumber;
    }

    public int multiply() {
        return firstNumber * secondNumber;
    }

    public int divide() {
        return firstNumber / secondNumber;
    }
}

Ze względu, że zamieniłem kropkę na równa się to będziemy pracować na liczba całkowitych – nawet wynik z dzielenia. Nie chodzi mi tu o stworzenie idealnego kalkulatora tylko o pokazanie Ci pewnej zależności podczas używania kontrolerów.

Operations

Aby łatwiej było nam operować kalkulatorem stworzyłem sobie również typ wyliczeniowy trzymający możliwe operacje matematyczne wbudowane w nasz kalkulator.

package sample;

public enum Operations {
    ADD, SUBTRACT, DIVIDE, MULTIPLY;
}

Controller

Czas przejść do implementancji kontrolera – czyli głównej gwiazdy tej imprezy. 😉

Na poczatku będziemy potrzebować kalkulatora oraz typu operacji, która została wywołana:

public class Controller {
    Calculator calculator = Calculator.getInstance();
    Operations operation;
}

Następne trzy pola będą miały za zadanie: przechowywać wpisane liczby oraz trzymać kolejność wpisywanych liczb – czyli, do której liczby będziemy dopisywać wartości.

public class Controller {
    Calculator calculator = Calculator.getInstance();
    Operations operation;
    boolean isTypingFirstNumber = true;
    String promptFirstNumber = "";
    String promptSecondNumber = "";
}

Kluczowym momentem kontrolera jest pole Label – czyli pole, do którego będziemy zapisywać wartość liczb i wynik.

@FXML
Label label_result;

O co chodzi z tym @FXML? – możliwe, że zapytasz. Już tłumaczę.

Ze względu, że jestesmy “podłączeni” naszym kontrolerem do szablonu (czyli pliku) .fxml to możemy się dostawać do jego komponentów – w tym przypadku Label, lecz możemy również się dostawać np. do Buttonów.

Małpa oznacza adnotację, jest to specjalny interfejs, który jest wykorzystywany w refleksjach. Ze względu, że jest to bardziej skomplikowany temat to wystarczy Ci wiedzieć, że dobrze utworzona adnotacja potrafi zrobić wiele za nas.

Co adnotacja FXML robi za nas? “Wstrzykuje” ona za nas label o nazwie label_result czyli ten:

<Label fx:id="label_result" layoutX="8.0" layoutY="7.0" prefHeight="72.0" prefWidth="581.0" style="-fx-background-color: #efefef;" textAlignment="RIGHT">

Spójrz, że nazwa pola zgadza się z fx:id!

Podsumowując – dzięki adnotacji @FXML mamy dostęp do komponentu z naszego interfejsu bez zbędnego jego wyszukiwania lub  tworzenia, wystarczy, że obie nazwy się zgadzają i możemy już na nim działać.

Skoro mamy już wszystkie pola – czyli w takiej formie:

public class Controller {
    Calculator calculator = Calculator.getInstance();
    Operations operation;
    boolean isTypingFirstNumber = true;
    String promptFirstNumber = "";
    String promptSecondNumber = "";
    @FXML
    Label label_result;
}

Zajmijmy się teraz wspólną częścią dla wielu metod.

Wyświetlanie odpowiedniej liczby

Ze względu stanu isTypingFirstNumber musimy wyświetlać odpowiednią liczbę w odpowiednim czasie w result_label.

private void displayRightNumberInResultLabel() {
    label_result.setText(isTypingFirstNumber ? promptFirstNumber : promptSecondNumber);
}

Dzięki tej metodzie w result_label znajdzie się zawsze ta liczba, której jest “kolej”.

? i : są częścią operatora warunkowego trójargumentowego, który ma postać:

warunek ? wykonaj_gdy_prawda : wykonaj_gdy_falsz;

Jest to po prostu skrócona wersja zwykłego if – else. Nasza metoda równie dobrze może wyglądać tak:

private void displayRightNumberInResultLabel() {
    String number;
    if (isTypingFirstNumber) {
        number = promptFirstNumber;
    } else {
        number = promptSecondNumber;
    }
    label_result.setText(number);
}

Nie uważasz, że ten zapis jest brzydszy od tego? 😉

private void displayRightNumberInResultLabel() {
    label_result.setText(isTypingFirstNumber ? promptFirstNumber : promptSecondNumber);
}

Wpisywanie do odpowiedniej liczby

Klikając po przyciskach będziemy musieli dopisywać cyfry do odpowiedniej liczby – w tym celu stworzymy metodę, która dopisze podaną cyfrę do odpowiedniego Stringa.

private void promptNumberToRightNumber(int number) {
    if (isTypingFirstNumber) {
        promptFirstNumber = promptFirstNumber.concat(String.valueOf(number));
    } else {
        promptSecondNumber = promptSecondNumber.concat(String.valueOf(number));
    }

    displayRightNumberInResultLabel();
}

Na koniec oczywiście trzeba pamiętać o ponownym wyświetleniu liczby! 😉

Dodatkowe metody

Przydadzą się również nam dodatkowe metody takie jak:

Wyświetlanie pierwszej liczby w result_label:

private void displayFirstNumber() {
    label_result.setText(promptFirstNumber);
}

Sprawdzenie czy druga liczba została wprowadzona:

private boolean isSecondNumberExist() {
    return promptSecondNumber.length() > 0;
}

Czy operacja została wybrana:

private boolean isOperationDefined() {
    return operation != null;
}

Zapisywanie wybranych cyfr

Na początku obsłużmy wszystkie przyciski z cyframi – wykorzystamy do tego wcześniej zdefiniowaną metodę promptNumberToRightNumber, która wpiszę do odpowiedniego pola naszą cyfrę.

public void clickButtonZero() {
    promptNumberToRightNumber(0);
}

public void clickButtonOne() {
    promptNumberToRightNumber(1);

}

public void clickButtonTwo() {
    promptNumberToRightNumber(2);
}


public void clickButtonThree() {
    promptNumberToRightNumber(3);
}

public void clickButtonFour() {
    promptNumberToRightNumber(4);

}

public void clickButtonFive() {
    promptNumberToRightNumber(5);
}

public void clickButtonSix() {
    promptNumberToRightNumber(6);
}

public void clickButtonSeven() {
    promptNumberToRightNumber(7);

}

public void clickButtonEight() {
    promptNumberToRightNumber(8);

}
public void clickButtonNine() {
    promptNumberToRightNumber(9);
}

Wywołanie obliczeń

Potrzebujemy również metody, która wywoła odpowiednią metodę calculatora na podstawie wybranej operacji – posłuży nam do tego taka implementacja:

private int callCalculatorAction() {
    switch(operation) {
        case ADD:
            return calculator.add();

        case DIVIDE:
            return calculator.divide();

        case SUBTRACT:
            return calculator.subtract();

        case MULTIPLY:
            return calculator.multiply();

        default:
            return 0;
    }
}

Przełączanie liczb

Po wciśnięciu przycisku operacji trzeba zmienić wajchę w naszym programie – czyli zapisywać do drugiej liczby, czyli zmienić stan isTypingFirstNumber na false. 😉

private void switchToSecondNumber() {
    isTypingFirstNumber = false;
    displayFirstNumber();
}

Operacje

Trzeba również obsłużyć przyciski operacji – będą po prostu przypisywać odpowiednią wartość do pola operation:

public void clickButtonAdd() {
       operation = Operations.ADD;
       switchToSecondNumber();
   }

   public void clickButtonMinus() {
       operation = Operations.SUBTRACT;
       switchToSecondNumber();

   }

   public void clickButtonMultiply() {
       operation = Operations.MULTIPLY;
       switchToSecondNumber();
   }

   public void clickButtonDive() {
       operation = Operations.DIVIDE;
       switchToSecondNumber();
   }

Czyszczenie kalkulatora

Potrzebujemy jeszcze wyczyszczenia całej “pamięci” kalkulatora – czyli obu liczb, przywrócić stan isTypingNumber na true oraz ustawić operację na null.

public void clickButtonClear() {
    label_result.setText("");
    promptFirstNumber="";
    promptSecondNumber="";
    isTypingFirstNumber=true;
    operation = null;
}

Obliczanie

Na sam koniec musimy oczywiście w jakiś sposób obliczyć wynik, czym byłby kalkulator bez możliwości obliczania. 😉

public void clickButtonResult() {
    if (isSecondNumberExist() && isOperationDefined()) {
    }
}

Na początek musimy sprawdzić czy druga liczba została wprowadzona czy operacja została wybrana.

Następnie ustawiamy pola w Calculatorze na podstawie wczytanych Stringów i obliczamy wynik:

public void clickButtonResult() {
     if (isSecondNumberExist() && isOperationDefined()) {
         calculator.setFirstNumber(Integer.valueOf(promptFirstNumber));
         calculator.setSecondNumber(Integer.valueOf(promptSecondNumber));
         int result = callCalculatorAction();
     }
 }

Integer.valueOf – zamienia inny typ danych na integer, w naszym przypadku zamienia ze Stringa.

Na koniec wpisujemy wynik do pierwszej liczby (aby można było nadal wykonywać operację na swoim wyniku), czyścimy drugą liczbę i wypisujemy wynik do result_label.

public void clickButtonResult() {
    if (isSecondNumberExist() && isOperationDefined()) {
        calculator.setFirstNumber(Integer.valueOf(promptFirstNumber));
        calculator.setSecondNumber(Integer.valueOf(promptSecondNumber));
        int result = callCalculatorAction();

        isTypingFirstNumber = false;
        promptFirstNumber = String.valueOf(result);
        promptSecondNumber="";
        displayFirstNumber();
    }
}

I w końcu po tylu trudach tworzenia kalkulatora możemy go wypróbować poprzez interfejs graficzny. Mam nadzieję, że u Ciebie też działa. 😛

Podsumowanie

W tej lekcji pokazałem Ci jak wykorzystać kontroler w interfejsie graficznym JavaFx – jak widać zaoszczędził nam on wiele pracy oraz zbędnego kodu, ja kto było w pierwszej lekcji JavaFx.

Dzięki adnotacji @FXML pozbyliśmy się wyszukiwania komponentu interfejsu, został on automatycznie “wstrzyknięty” jako obiekt naszej klasy – później wystarczyło ustawiać tylko na nim tekst. 😉

Co prawda tworzenie oddzielnej klasy dla kalkulatora może jest trochę sztuką dla sztuki – ale za to jakiej… Zauważ, że dzięki temu oddzieliliśmy logikę biznesową od logiki aplikacji – dzięki czemu mamy różne warstwy aplikacji.

Czemu jest to takie ważne? Wyobraź sobie, że klient chce również mieć kalkulator w konsoli – co robisz? Podpinasz klasę Calculator do interfejsu konsoli i tam obsługujesz np. klawiaturę zamiast pisać od nowa implementację kalkulatora.

Logika biznesowa to nic innego jak ostatnia warstwa, z która ma kontakt bezpośredni użytkownik, klient – zazwyczaj jest to interfejs aplikacji, ale też mogą nią być Restowe End Pointy.

Za logikę aplikacji uważamy wszystko to co się dzieje pod spodem – czyli cała walidacja danych, operacje na danych, zapisywanie, odczyt itd.

Kod calej aplikacji możesz znaleźć tutaj.

Jeśli chcesz poćwiczyć kontrolery w JavaFx to zaimplementuj kontroler dla interfejsu z poprzedniego zadania domowego – czyli interfejsu kalkulatora jednostek miar i wag.

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