You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 9 Next »

Introduksjon

I denne oppgaven skal du lære hvordan du kan koble FXML-kode opp mot Java-kode og gjøre applikasjoner interaktive! Målet med denne oppgaven er å lage en enkel kalkulator-applikasjon.

Steg 1: Opprette app-pakke, app-klasse, FXML-fil og controller-klasse

Filene i denne øvingen skal ligge i ovinger/src/encapsulation. Lag først en ny klasse med navnet KalkulatorApp. Åpne KalkulatorApp-klassen og rediger slik at du får følgende kode:

package encapsulation;

import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;


public class KalkulatorApp extends Application {
	public void start(Stage primaryStage) throws IOException {
		FXMLLoader fxmlLoader = new FXMLLoader(KalkulatorApp.class.getResource("KalkulatorApp.fxml"));
		Pane root = fxmlLoader.load();
		primaryStage.setScene(new Scene(root));
		primaryStage.show();
	}

	public static void main(String[] args) {
		launch(KalkulatorApp.class, args);
	}
}

 

Lag en ny FXML-fil ved navn KalkulatorApp.fxml. 

Rediger KalkulatorApp.fxml-fila med FXML-editoren og/eller SceneBuilder, slik at du får en kalkulator med knapper (type Button) for hvert tall 0-9, desimalpunktum, de fire regneartene +, -, *, / og likhetstegn =. Over disse har du et tekstfelt (TextField).

Det kan se ut omtrent som vist til høyre.

Hvis du kjører KalkulatorApp med Run As > Java Application, så vil du få opp et vindu som ser ut som en kalkulator, men som ikke gjør noe.

Din oppgave er å lage en Controller som forbinder logikken for å kjøre en kalkulator med grafikken. Lag derfor en klasse som heter KalkulatorController.

Det neste trinnet er å fortelle JavaFX at denne klassen skal være kontroller-klassen til app-en. Dette gjøres ved å legge inn fx:controller="encapsulation.KalkulatorController" i FXML-koden for kalkulator-panelet, altså KalkulatorApp.fxml. Hvis du f.eks. bruker en AnchorPane som panel så vil koden se omtrent slik ut:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.text.*?>
... flere <?import ... ?> her ...
<?import javafx.scene.control.TextField?>

<AnchorPane prefHeight="126.0" prefWidth="222.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
	fx:controller="encapsulation.KalkulatorController">

 
	... alle knappene kommer her ...
 
</AnchorPane>

Det vesentlig her er fx:controller="encapsulation.KalkulatorController", som forteller at KalkulatorController er klassen som skal reagere på og styre knappene og tekstfeltet i KalkulatorApp.fxml.

Steg 2: Koble FXML-fil og Controller sammen

Neste skrittet er å sammenkoble FXML-fila og Controller-klassen. 

Sammenkobling går to veier:

  1. Fra FXML til Controller: knappene i KalkulatorApp.fxml sier fra til KalkulatorController når de trykkes på.
  2. Fra Controller til FXML: KalkulatorController oppdaterer tekstfeltet i KalkulatorApp.fxml når tilstanden inni kalkulatoren endres, f.eks. når den beregner en ny verdi som skal vises frem.

I begge tilfeller må en legge til kode i både Kalkulator.fxml og KalkulatorController, men på ulike måter.

Fra FXML til Controller

For at knappene i FXML-en skal kunne si ifra må det legges til en aksjon på noe som skal skje. Det gjøres ved å redigere FXML-knappene med å legge til

onAction="#handleDigitButton" på alle disse som vist i figuren under til høyre. En må selvsagt huske på å lage handleDigitButton-metoden også i kontrolleren og. Her er eksempelkode for tall-knappene:

I KalkulatorController.java:

public class KalkulatorController {
 
	... annen kode ...
 
	@FXML
	void handleDigitButton(ActionEvent event) {
		// denne setningen skriver ut hvilken knapp som kalte metoden
		System.out.println(event.getSource());
		... her er kode for tall-knappene ...
	}
}

@FXML brukes for å gi FXML-en lov til å kalle metoden.

event-parameteret (av typen ActionEvent) inneholder informasjon om hendelsen. F.eks. kan en hente ut hvilken knapp som ble trykket med event.getSource(). Her skriver vi ut denne verdien, for å sjekke at det virker som det skal. Hvis det ikke er viktig å vite hvilken knapp som ble trykket (f.eks. hvis den bare kalles av én knapp), så trenger du ikke ha med event-parameteret i metoden.

Hvis du senere ønsker å håndtere andre typer hendelser, f.eks. museklikk, så må du bruke en annen argument-type enn ActionEvent. For museklikk (og andre muse-hendelser) må du f.eks. bruke MouseEvent.

I KalkulatorApp.fxml

<?xml version="1.0" encoding="UTF-8"?>

<AnchorPane ...	fx:controller="encapsulation.KalkulatorController">
	<children>
		<Button text="1" onAction="#handleDigitButton" ... />
		<Button text="2" onAction="#handleDigitButton" ... />
 
	... de andre knappene kommer her ...
	</children>
</AnchorPane>

Både 1- og 2-knappen har onAction="#handleDigitButton", så for begge disse vil handleDigitButton blir kalt når de trykkes på.

Prøv å gjøre endringene vist over, kjør koden og se om du får skrevet ut hvilken knapp som trykkes. Husk at det bare er knappene som har onAction="#handleDigitButton" som vil kalle handleDigitButton-metoden.

Fra Controller til FXML

For at KalkulatorController-koden skal ha en synlig effekt, så må den kunne endre på elementene som vises i app-vinduet. F.eks. så ønsker jo vi å endre på teksten som vises i tekstfeltet. For å få det til, så må en ha én eller flere variabler som peker på elementer i KalkulatorApp.fxml-fila og få satt disse automagisk når FXML-fila lastes inn. Trikset er igjen å legge inn kode både i KalkulatorController og KalkulatorApp.fxml. I dette tilfellet deklarerer man en variabel i KalkulatorController og legger in et fx:id-attributt i KalkulatorApp.fxml. Typen og navn til variabelen må stemme med typen FXML-element og fx:id-en. 

I KalkulatorController.java:

public class KalkulatorController {
 
	... annen kode ...
 
	@FXML
	TextField valueTextField; // denne vil bli satt til å peke på tekstfeltet med fx:id="valueTextField"
	@FXML
	void handleDigitButton(ActionEvent event) {
		Button button = (Button) event.getSource();
		valueTextField.setText(button.getText());
	}
}

@FXML brukes her for å gi FXML-en lov til å sette variabelen. Typen TextField stemmer med FXML-elementet og navnet "valueTextField" stemmer med fx:id-verdien.

Koden i handleDigitButton er endret, slik at den først henter ut knappen med event.getSource()og så setter teksten til tekstfeltet til knappens tekst med valueTextField.setText(button.getText()).

I KalkulatorApp.fxml

<?xml version="1.0" encoding="UTF-8"?>

<AnchorPane ...	fx:controller="encapsulation.KalkulatorController">
	<children>
		<TextField fx:id="valueTextField" ... />
 
		<Button text="1" onAction="#handleDigitButton" ... />
		<Button text="2" onAction="#handleDigitButton" ... />
 
	... de andre knappene kommer her ...
	</children>
</AnchorPane>

Tekstfeltet har fått en fx:id som stemmer med variabel-navnet. Også typen TextField stemmer med FXML-koden for tekstfeltet.


Prøv å gjøre endringene vist over, kjør koden og se om teksten i tekstfeltet endres når du trykker på tall-knappene.

Steg 3: Kalkulator-logikk

Så langt har vi fokusert på å få teknikken for å koble FXML-fil og Controller-klasse sammen til å virke. Nå kan vi begynne å se på kalkulator-logikken. Det vanskeligste er i grunnen å finne ut hvilke variabler som trengs for å holde rede på kalkulator-tilstanden, og så endre variablene på riktig måte når knappene trykkes på. Det kan også være vanskelig å organisere koden så det ikke bare blir rot når en legger til logikk.

Det første du skal gjøre er å endre logikken slik at det er mulig å bygge opp hele tall ved å legge til nye sifre bakerst, altså det som gjør at en får 2469 hvis en trykker knappene 2, 4, 6 og 9 i rekkefølge. For å realisere dette skal du implementere de følgende endringene i metodene og tilstanden til KalkulatorController-klassen:

  • String valueText - teksten som skal vises i kalkulator-vinduet.
  • void appendDigit(String digit) - legger til digit på slutten av tallet i kalkulator-vinduet.
  • void updateValueTextField() - viser verdien til valueText i kalkulator-vinduet.
  • void handleDigitButton(ActionEvent event) - legger til det gitte sifferet bakerst i valueText og oppdaterer verdien som vises i kalkulator-vinduet.

Når du har implementert disse endringene skal nye siffer bli lagt til på slutten av tallet når du klikker på de, men de andre knappene har enda ikke noe funksjonalitet. For å få oversikt over all nødvendig tilstand og logikk, så er det lurt å prøve å tenke på hva som skal skje for ulike rekkefølger av tastetrykk. Vi tenker dette er best forklart med et eksempel. Dette kan du løse litt som du vil, men her er noen tilstander og funksjoner kan være nyttige å implementere

 

  • double memory - minnet til kalkulatoren over hva som har blitt regnet ut tidligere
  • String operator - Den siste operatoren (+, -, *, /) som har blitt skrevet inn av brukeren
  • void clearValueText() - Fjerner det som står i tekstfeltet i kalkulatoren
  • void storeOperator() - Oppdaterer operatoren etter at en bruker har trykket på et slikt tegn i operator
  • void computeAndStoreValue() - Regner ut tall ved hjelp av siste operator trykket inn, og tall trykket inn og tar varet på verdien i memory. 
  • void storeValueText() - Kan brukes første gang brukeren trykker på en operator, da ingenting faktisk skal regnes ut. (Gjelder når memory er 0) 

I tabellen under så har vi laget fem kolonner. Den første, Tastetrykk, viser hva som er tastet så langt. Den andre, Logikk, forklarer med tekst hva som gjøres. De tre neste er variablene som trengs: valueText er tallet en er i ferd med å legge inn, memory er tallet en allerede har skrevet inn eller regnet ut, og operator er den siste operasjonen en trykket på. Verdien i hver variabel-kolonne er resultatet av å utføre det som er beskrevet i Logikk-kolonnen. Den siste er teksten som vises i tekstfeltet, som om updateValueTextField() kalles automatisk etter at Logikk-en er utført. 


Det er valgfritt om du vil bruke de metodene vi har foreslått eller om du vil lage dine egne. 

TastetrykkLogikkString valueTextdouble memoryString operatorvalueTextField.text
(før noen knapper er trykket)memory vises i tekstfeltetnull0.0null"0.0"
1Bygger opp ny valueText, som vises i tekstfeltet"1"0.0null"1"
1, 2Legger nytt siffer bakerst i valueText"12"0.0null"12"
1, 2, +

Gjør om valueText til et tall og legger det i memory.
Blanker valueText og lagrer +-operasjonen i operator

null12.0"+""12.0"
1, 2, +, 3Bygger opp ny valueText"3"12.0"+""3"
1, 2, +, 3, 4Legger nytt siffer bakerst i valueText"34"12.0"+""34"
1, 2, +, 3, 4, =Gjør om valueText til et tall og utfører operator-operasjonen på memory og det nye tallet.
Resultatet legges i memory, og så blankes operator og valueText
null46.0null"46.0"

 

Nyttige kodesnutter

For å gjøre det litt lettere så viser vi her noen kode-snutter for småproblemer du sikkert vil støte på underveis.

ProblemKodeForklaring
Gjøre om fra tekst (String) to (desimal)tall (double)Double.valueOf(text)Du kaller Double-klassen sin valueOf-metode, for å konvertere en tekst til en double.
Gjøre om fra (desimal)tall til tekstString.valueOf(num) eller "" + numDu kaller enten String.valueOf, eller legger num til "", som konverterer num i samme slengen
Sjekke om en String-variabel er ingentingif (text != null) { ... }En bruker != null for å sjekke om er variabel er ingenting
Sjekke om en String-variabel er tomif (text.length() == 0) { ... }text.length() gir deg antall bokstaver i en String, så her sjekker vi om antall bokstaver er 0

 

Steg 4: Innkapsling og utskilling

I det siste steget skal du innkapsle programmet du har laget så langt. Dette gjøres blant annet med en kalkulator-klasse med lese- og endringsmetoder, i tillegg til synlighetsmodifikatorer.

Kalkulator-klasse
I applikasjoner er det vanlig å dele programmet opp i model, view og controller (MVC-arkitektur). I denne oppgaven vil vi dele det opp slik:

  • Model: Kalkulator-klasse som håndterer lesing og endring av tilstanden til kalkulatoren
  • Controller: KalkulatorController-klasse som tar i mot inputs fra viewet og får kalkulator-objektet til å endre tilstand tilsvarende, samt gir tilstanden til kalkulator-objektet til viewet, slik at det kan vises til brukeren av programmet.
  • View: kalkulatorApp.fxml som viser tilstanden til kalkulator-objektet slik vi vil, og som lar brukeren gi inputs.

Til nå har du laget view (kalkulatorApp.fxml) og controller (KalkulatorController). Modellen vil da være en egen klasse, Kalkulator, som skal ta for seg alt som har med tilstanden til selve kalkulatoren å gjøre, dette krever innkapslede metoder for lesing og endring av attributter innen kalkulator-klassen.


Du skal altså

  • Flytte logikk fra KalkulatorController som endrer tilstanden til en kalkulator, som for eksempel memory-feltet og metoder som endrer tilstanden til kalkulatoren.
  • Innkapsle kalkulatoren for å sørge for at den har gyldig tilstand, med både lese- og endringsmetoder, samt synlighetsmodifikatorer.

PS: Husk at hva som skrives inn i selve tekstfeltet er en grafisk del og trenger ikke være en del av kalkulatoren før en har trykket på en operator.

 


  • No labels