Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3
Excerpt

Med JavaFX er det lett å lage enkel 2D-grafikk, og med FXML så blir det enda enklere!

 

Figurer i FXML

Med FXML er det nokså enkelt å lage større figurer, som en sammensetning av enkle figur-elementer som sirkler, rektangler og streker. En enkel strekmann kan se ut som følger med FXML:

Code Block
languagejavafx
    <Group layoutY="50">
        <Circle layoutX="100" layoutY="20" radius="20" stroke="black" fill="white"/>
        <Circle layoutX="112" layoutY="20" radius="2" stroke="black" fill="blue"/>
        <Line layoutX="120" layoutY="20" startX="0" startY="-4" endX="3" endY="0" stroke="black"/>
        <Line layoutX="120" layoutY="20" startX="3" startY="0" endX="0" endY="1" stroke="black"/>
        <Line layoutX="100" layoutY="40" endX="0" endY="30" stroke="black"/>
        <Group layoutX="100" layoutY="50">
            <Line layoutY="0"  endX="-10" endY="10" stroke="black"/>
            <Line layoutY="0"  endX= "10" endY="10" stroke="black"/>
            <Line layoutY="20" endX="-10" endY="15" stroke="black"/>
            <Line layoutY="20" endX= "10" endY="15" stroke="black"/>
        </Group>
    </Group>

Denne figuren er laget med FXML-koden til venstre.

...

Merk at for å vise frem strekmann-figuren, så trengs det litt mer FXML-kode over/rundt og litt Java-kode for å laste inn FXML-koden og vise frem figuren. Den komplette FXML- og Java-koden er vist under.

Code Block
languagejavafx
collapsetrue
<?xml version="1.0" encoding="UTF-8"?>
 
<?import javafx.scene.textlayout.TextPane?>
<?import javafx.scene.control.TextFieldGroup?>
<?import javafx.scene.controlshape.ButtonCircle?>
<?import javafx.scene.layoutshape.BorderPaneLine?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.layout.HBox?>

<?import javafx.scene.layout.Region?>
<?import javafx.scene.Group?>
<?import javafx.scene.shape.Circle?>
<?import javafx.scene.shape.Line?>

<Pane xmlns:fx="http://javafx.com/fxml"
     minWidth="400" minHeight="600">
    <Group layoutY="50">
        
<Pane xmlns:fx="http://javafx.com/fxml"
     minWidth="400" minHeight="600">
    <Group layoutY="50">
        <Circle layoutX="100" layoutY="20" radius="20" stroke="black" fill="white"/>
        <Circle layoutX="112" layoutY="20" radius="2" stroke="black" fill="blue"/>
        <Line layoutX="120" layoutY="20" startX="0" startY="-4" endX="3" endY="0" stroke="black"/>
        <Line layoutX="120" layoutY="20" startX="3" startY="0" endX="0" endY="1" stroke="black"/>
        <Line layoutX="100" layoutY="40" endX="0" endY="30" stroke="black"/>
        <Group layoutX="100" layoutY="50" visible="true">
            <Line layoutY="0"  endX="-10" endY="10" stroke="black"/>
            <Line layoutY="0"  endX= "10" endY="10" stroke="black"/>
            <Line layoutY="20" endX="-10" endY="15" stroke="black"/>
            <Line layoutY="20" endX= "10" endY="15" stroke="black"/>
        </Group>
    </Group>
</Pane>

Code Block
languagejava
collapsetrue
package trinn2;

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

public class StickMan1 extends Application {
    @Override
    public void start(Stage primaryStage) throws IOException {
        Parent root = FXMLLoader.load(this.getClass().getResource("StickMan1.fxml"));
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }
    public static void main(String[] args) {
        launch(args);
    }
}

Den komplette FXML-koden er vist til venstre. Import-linjene øverst trengs for å kunne bruke navn tilsvarende Java-klasser lenger ned i fila. F.eks. vil Circle egentlig referere til java-klassen javafx.scene.shape.Circle. Java-koden antar her at FXML-fila ligger i samme mappe som java-fila.

...

  • Ett felt pr. JavaFX-objekt en vil ha tak i, gjerne med samme navn id-en, f.eks. Circle headCircle;
  • En initialiseringsmetode med (enten start-metoden selv eller en egen initialize-metode) med én linje pr. felt/objekt, hvor en slår opp objektet med lookup-metoden og setter feltet, f.eks. headCircle = (Circle) rootNode.lookup("#headCircle").

...

  • FXML-fila må lastes inn av et eget FXMLLoader-objekt, som må få vite hvilket objekt som håndteres av automatikken. Dette objektet fungerer som såkalt kontroller og settes med setController-metoden, f.eks. slik (hentet fra eksempel-koden lenger ned):

    Code Block
    FXMLLoader fxmlLoader = new FXMLLoader();
    fxmlLoader.setController(this);
    Parent root = (Parent) fxmlLoader.load(this.getClass().getResourceAsStream("StickMan2.fxml"));

    . For små applikasjoner, så brukes gjerne applikasjonsobjektet selv, som i koden under hvor this brukes som argument.

  • Feltene som skal settes automatisk  annoteres med @FXML, så det er tydelig at Feltene som skal settes automatisk  annoteres med @FXML, så det er tydelig at feltet skal håndteres av automatikken, f.eks.

    Code Block
    @FXML Circle headCircle;

Reagere på input fra brukeren

 

...

  • . Merk at automatikken utføres som en del av kallet til FXMLLoader sin load-metode, så en kan ikke referere til de @FXML-annoterte feltene før load-kallet er utført.

Her er eksempel-kode som viser dette i praksis (komplett kode er vist lengre ned):

Code Block
languagejava
public class StickMan2 extends Application {
    @Override
    public void start(Stage primaryStage) throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader();
        fxmlLoader.setController(this);
        Parent root = (Parent) fxmlLoader.load(this.getClass().getResourceAsStream("StickMan2.fxml"));
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }
	...
    @FXML Node stickMan, armsAndLegs1, armsAndLegs2;
    @FXML Shape eye;
	...
}

Reagere på input fra brukeren

Standard-teknikken for å håndtering av interaktive elementer er bruk av såkalte lyttere, som er (ofte egne) objekter med metoder som kalles når noe bestemt skjer, f.eks. at en bruker trykker en knapp. Denne teknikken er litt omstendelig (vi viser ikke eksempler her), så også her tilbyr FXML litt automatisering. Tanken er at det samme kontroller-objektet som settes med setController-metoden, også brukes som lytter, dvs. inneholder metoder som automatisk skal kalles ved gitte hendelser, som musklikk og tastetrykk. Denne automatikken ligner litt på teknikken beskrevet over, ved at den også er en kombinasjon av FXML-attributter og @FXML-annotering av Java-elementer:

  • I Java-koden lager en metoder for å håndtere bruker-input, og disse annoteres ed @FXML, f.eks. @FXML void handleButtonClick() { ... }. Disse metodene kan (men må ikke) deklarere et hendelse-argument for å ta imot mer detaljert informasjon om hva brukeren gjorde. Typen til argumentet er avhengig av typen hendelse, f.eks. vil et trykk på en Button ha typen ActionEvent.
  • I FXML-koden angis at disse metoden skal kalles ved å bruke on-attributter med verdien #metodenavn, f.eks. <Button onAction="#handleButtonClick"/>. Akkurat hvilke on-attributter en kan bruke, er avhengig av typen element, f.eks. støtter Button flere bl.a. onAction, onKeyPressed og onMouseClicked.

Her er eksempel-kode som viser dette i praksis, med Java-kode til venstre og FXML-kode til høyre (komplett kode er vist lengre ned):

Code Block
languagejava
    @FXML
    void walk() {
        step = ! step;
        stickMan.setLayoutX(stickMan.getLayoutX() + 5);
        update();
    }

@FXML angir at walk-metoden deltar i håndtering av hendelser vha. FXML-automatikken

 

Code Block
languagejavafx
        <HBox>
            <Button text="Walk!" onAction="#walk"/>
        </HBox> 

onAction="#walk" angir at knappens action-hendelse skal trigge walk-metoden

StickMan2

Teknikken beskrevet over er brukt i en utvidelse av eksemplet over, hvor en strekmann "spaserer" mot høyre og blunker når brukeren klikker på en knapp. Bevegelsen mot høyre håndteres ved å øke x-koordinaten for hvert steg (klikk på knappen), med følgende linje i walk-metoden: stickMan.setLayoutX(stickMan.getLayoutX() + 5); Skritt-effekten håndteres ved å ha to sett med armer og ben og veksle mellom dem ved å slå dem av og på med kall til setVisible-metoden. Blunke-effekten håndteres ved å veksle mellom to farger med kall til setFill-metoden. Steg-logikken er fordelt mellom walk-metoden, som bare lar step-feltet veksle mellom true og false, og update-metoden, som kaller setVisible- og setFill-metodene med verdier avhengig av step-feltet.

Code Block
    void update() {
        if (step) {
            eye.setFill(Color.BLUE);
        } else {
            eye.setFill(Color.WHITE);
        }
        armsAndLegs1.setVisible(step);
        armsAndLegs2.setVisible(! step);
    }

Her er komplett Java- og FXML-kode:

Code Block
languagejava
collapsetrue
package trinn2;

import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Shape;
import javafx.stage.Stage;

public class StickMan2 extends Application {
    @Override
    public void start(Stage primaryStage) throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader();
        fxmlLoader.setController(this);
        Parent root = (Parent) fxmlLoader.load(this.getClass().getResourceAsStream("StickMan2.fxml"));
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }
    @FXML Node stickMan, armsAndLegs1, armsAndLegs2;
    @FXML Shape eye;
    
    boolean step = true;
    @FXML
    void initialize() {
        update();
    }
    void update() {
        if (step) {
            eye.setFill(Color.BLUE);
        } else {
            eye.setFill(Color.WHITE);
        }
        armsAndLegs1.setVisible(step);
        armsAndLegs2.setVisible(! step);
    }
    @FXML
    void walk() {
        step = ! step;
        stickMan.setLayoutX(stickMan.getLayoutX() + 5);
        update();
    }
    public static void main(String[] args) {
        launch(args);
    }
}
Code Block
languagejavafx
collapsetrue
<?xml version="1.0" encoding="UTF-8"?>
 
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.Pane?>

<?import javafx.scene.Group?>
<?import javafx.scene.shape.Circle?>
<?import javafx.scene.shape.Line?>

<BorderPane xmlns:fx="http://javafx.com/fxml"
     minWidth="400">
    <top>
        <Button text="Walk!" onAction="#walk"/>
    </top>
    <center>
        <Pane minWidth="400" minHeight="600">
            <Group fx:id="stickMan">
                <Circle layoutX="100" layoutY="20" radius="20" stroke="black" fill="white"/>
                <Circle fx:id="eye" layoutX="112" layoutY="20" radius="2" stroke="black" fill="blue"/>
                <Line layoutX="120" layoutY="20" startX="0" startY="-4" endX="3" endY="0" stroke="black"/>
                <Line layoutX="120" layoutY="20" startX="3" startY="0" endX="0" endY="1" stroke="black"/>
                <Line layoutX="100" layoutY="40" endX="0" endY="30" stroke="black"/>
                <Group fx:id="armsAndLegs1" layoutX="100" layoutY="50" visible="true">
                    <Line layoutY="0"  endX= "-5" endY="10" stroke="black"/>
                    <Line layoutY="0"  endX= "10" endY="10" stroke="black"/>
                    <Line layoutY="20" endX="-10" endY="15" stroke="black"/>
                    <Line layoutY="20" endX=  "5" endY="15" stroke="black"/>
                </Group>
                <Group fx:id="armsAndLegs2" layoutX="100" layoutY="50" visible="false">
                    <Line layoutY="0"  endX="-10" endY="10" stroke="black"/>
                    <Line layoutY="0"  endX=  "5" endY="10" stroke="black"/>
                    <Line layoutY="20" endX= "-5" endY="15" stroke="black"/>
                    <Line layoutY="20" endX= "10" endY="15" stroke="black"/>
                </Group>
            </Group>
        </Pane>
    </center>
</BorderPane>

StickMan3

Dette er en annen variant som bruker de samme teknikkene, og lar brukeren bruke piltastene for å flytte strekmannen. Her er komplett Java- og FXML-kode:

Code Block
languagejava
collapsetrue
package trinn2;

import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Shape;
import javafx.stage.Stage;

public class StickMan3 extends Application {
    @Override
    public void start(Stage primaryStage) throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader();
        fxmlLoader.setController(this);
        Parent root = (Parent) fxmlLoader.load(this.getClass().getResourceAsStream("StickMan3.fxml"));
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }
    @FXML Pane stickManPane;
    @FXML Node stickMan, armsAndLegs1, armsAndLegs2;
    @FXML Shape leftEye, rightEye;
    
    boolean step = true;
    KeyCode direction = KeyCode.DOWN;
    
    @FXML
    void initialize() {
        update();
    }
    void update() {
        if (direction == KeyCode.LEFT) {
            leftEye.setVisible(true);
            rightEye.setVisible(false);
        } else if (direction == KeyCode.RIGHT) {
            leftEye.setVisible(false);
            rightEye.setVisible(true);
        } else {
            leftEye.setVisible(true);
            rightEye.setVisible(true);
        }
        if (step) {
            leftEye.setFill(Color.BLUE);
            rightEye.setFill(Color.BLUE);
        } else {
            leftEye.setFill(Color.WHITE);
            rightEye.setFill(Color.WHITE);
        }
        armsAndLegs1.setVisible(step);
        armsAndLegs2.setVisible(! step);        
    }
    
    @FXML
    void keyPressed(KeyEvent keyEvent) {
        step = ! step;
        if (keyEvent.getCode() == KeyCode.LEFT) {
            stickMan.setLayoutX(stickMan.getLayoutX() - 5);
        } else if (keyEvent.getCode() == KeyCode.RIGHT) {
            stickMan.setLayoutX(stickMan.getLayoutX() + 5);
        } else if (keyEvent.getCode() == KeyCode.DOWN) {
            stickMan.setLayoutY(stickMan.getLayoutY() + 5);
        } else if (keyEvent.getCode() == KeyCode.UP) {
            stickMan.setLayoutY(stickMan.getLayoutY() - 5);
        } else {
            return;
        }
        direction = keyEvent.getCode();
        update();
    }
    public static void main(String[] args) {
        launch(args);
    }
}
Code Block
languagejavafx
collapsetrue
<?xml version="1.0" encoding="UTF-8"?>
 
<?import javafx.scene.layout.Pane?>

<?import javafx.scene.Group?>
<?import javafx.scene.shape.Circle?>
<?import javafx.scene.shape.Line?>

<Pane xmlns:fx="http://javafx.com/fxml" minWidth="400">
    <Pane fx:id="stickManPane" minWidth="400" minHeight="600" focusTraversable="true" onKeyPressed="#keyPressed">
        <Group fx:id="stickMan">
            <Circle layoutX="100" layoutY="20" radius="20" stroke="black" fill="white"/>
            <Circle fx:id="leftEye" layoutX="92" layoutY="20" radius="2" stroke="black" fill="blue"/>
            <Circle fx:id="rightEye" layoutX="110" layoutY="20" radius="2" stroke="black" fill="blue"/>
            <Line layoutX="100" layoutY="40" endX="0" endY="30" stroke="black"/>
            <Group fx:id="armsAndLegs1" layoutX="100" layoutY="50" visible="true">
                <Line layoutY="0"  endX= "-5" endY="10" stroke="black"/>
                <Line layoutY="0"  endX= "10" endY="10" stroke="black"/>
                <Line layoutY="20" endX="-10" endY="15" stroke="black"/>
                <Line layoutY="20" endX=  "5" endY="15" stroke="black"/>
            </Group>
            <Group fx:id="armsAndLegs2" layoutX="100" layoutY="50" visible="false">
                <Line layoutY="0"  endX="-10" endY="10" stroke="black"/>
                <Line layoutY="0"  endX=  "5" endY="10" stroke="black"/>
                <Line layoutY="20" endX= "-5" endY="15" stroke="black"/>
                <Line layoutY="20" endX= "10" endY="15" stroke="black"/>
            </Group>
        </Group>
    </Pane>
</Pane>