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

Compare with Current View Page History

« Previous Version 9 Next »

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:

    <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.

Ved å se på figur-typen og koordinatene skal det være nokså lett å se hvilke kode-linjer som tilsvarer hodet, øyet, nesa, kropp, armer og bein. Posisjonen (koordinatene) styres på tre måter:

  • layoutX og layoutY i Group-nodene: Disse verdiene legges til koordinatene lenger ned i hierarkiet.
  • layoutX og layoutY i Circle og Line: Disse plasserer figurene i forhold til gruppen de er i.
  • startX, startY, endX og endY i Line: Disse verdiene angir start- og slutt-posisjonen til streken og er relativt til layout-verdiene.

Legg altså merke til hvordan koordinat-verdiene legges sammen, slik at den faktiske plasseringen bestemmes av verdier mange steder i koden. Gruppering med Group kan nettopp være lurt for å gjøre det enklere å plasserer figure-elementer riktig.

Visuelle effekter angis med ullike attributter, f.eks. brukes fill for å angi fargen som en (del)figur fylles med og stroke angir strek-fargen. Muligheten er bestemt av typen figur-element og egenskapene til den tilsvarende Java-klassen. F.eks. kan både Line og Circle ha et stroke-attributt, fordi begge arver en setStroke-metode fra deres felles superklasse Shape.

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.

<?xml version="1.0" encoding="UTF-8"?>
 
<?import javafx.scene.text.Text?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.BorderPane?>
<?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">
        <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>

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.

Bevegelige figurer

Figurene i FXML-filer er i utgangspunktet statiske; filene lastes inn og innholdet vises frem. For å lage dynamisk grafikk, altså bevegelige figurer, så trenger en å koble sammen FXML-kode og Java-kode. For å få det til, så må koden kunne to ting:

  1. Referere til figur-elementer: Uten denne muligheten går det ikke an å få endre på figur-instansene, f.eks. Circle- og Line-objekter, som ble laget da FXML-fila ble lastet inn og vist.
  2. Reagere på input fra brukeren: Uten denne muligheten kan ikke brukeren påvirke eller styre hva som skjer.

Dette kan gjøres uten FXML, men det er litt enklere hvis en bruker mekanismer innebygget i FXML, som vi skal se.

Referanser til figur-objekter

Når en laster inn og viser frem (innholdet i) en FXML-fil, så blir det enkelt sagt, laget ett Java-objekt for hver FXML-tag tilvarende tag-navnet. F.eks. vil <Group> gi et Group-objekt og <Circle> gir et Circle-objekt. Objektene legges inni hverandre tilsvarende tag-strukturen. F.eks. vil sirkelen havne inni gruppa, siden <Circle> inni <Group>. Det første problemet en støter på når program-koden skal endre på sirkelen, er hvordan få tak i sirkel-objektet i første omgang, når det ligger dypt nedi en struktur som ble laget under FXML-innlasting. Heldigvis har FXML en todelt mekanisme for akkurat det: 1) en kan knytte id-er til tag-ene og 2) få FXML til å sette tilsvarende variabler ved innlasting. 

Til hver FXML-tag eller node kan det knyttes en id, altså et unikt navn (må sikres av utvikleren), helt analogt med id-tag'en i HTML. Dette angis i FXML-en med fx:id="...", f.eks. <circle fx:id="headCircle"/>, hvor alle id-er må være forskjellig i én fil. (fx-prefikset må være deklarert tidligere med xmlns:fx="http://javafx.com", typisk i ytterste tag, som vist i koden over.) Men merk at det kun er elementene en må ha tak i senere, som trenger id, så en trenger ikke finne på så mange id-er pr. fil.

Når en FMXL-fil først er lastet inn, så får en i utgangspunktet kun en referanse til det ytterste objektet, gjerne kalt rot-noden. For å få tak i et objekt med en bestemt id lenger ned i strukturen, så kan en kalle metoden lookup(String id) på rot-noden. F.eks. vil rootNode.lookup("#headCircle") returnere noden under rootNode med id-en headCircle. Det er også mulig å søke etter noder eller strukturer av bestemte typer, men det er mindre praktisk siden en ofte har mange objekter av samme type.

Den typiske kode-strukturen vil være:

  • Ett felt pr. JavaFX-objekt en vil ha tak i, gjerne med samme navn id-en, f.eks. Circle headCircle;
  • En initialiseringsmetode 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").

Siden dette er et så vanlig mønster, så kan FXML-mekanismen automatisere oppslag og setting av feltene. Denne automatikken krever to ting:

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

    FXMLLoader fxmlLoader = new FXMLLoader();
    fxmlLoader.setController(this);
    Parent root = (Parent) fxmlLoader.load(this.getClass().getResourceAsStream("StickMan2.fxml"));
  • Feltene som skal settes automatisk  annoteres med @FXML, så det er tydelig at feltet skal håndteres av automatikken, f.eks.

    @FXML Circle headCircle;

Reagere på input fra brukeren

 

 

  • No labels