Versions Compared

Key

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

JavaFX er ment å være Java Swing sin etterfølger som Java sitt det foretrukne rammeverk for grafikk og brukergrensesnitt fra og med Java 7. JavaFX er gjør det bl.a. mer fleksibelt i hvordan man kan enklere å kombinere grafikk og interaktive komponenter, har bedre støtte for rik grafikk og animasjon, gjør det lettere å skille funksjon og det rent visuelle.

Som programmerer må man forholde seg til et fullstendig nytt API (klassene og metodene som utgjør rammeverket), men overgangen er ikke så stor fordi en godt kan bruke kjente teknikker og gradvis ta i bruk de nye mulighetene.

Basic oppbygning 

En JavaFX applikasjon er bygget opp med en stage i bunn som utgjør rammen til applikasjonen, og har typisk et navn som vises øverst applikasjonsvinduet. En stage fylles med en scene. Scenen utgjør området hvor vi framstiller knapper, figurer, tekst og andre komponenter. Dette skjer dog ikke helt vilkårlig, siden alle komponenter må legges til i en scene graph, og kalles da noder i denne grafen. Grafens struktur bestemmer hvordan nodene rendres på scenen.

Altså: Stage -> Scene -> Scene Graph

 

Et GUI-rammeverk er en samling klasser som gjør det mulig å bygge rike, funksjonelle grafiske brukergrensenitt uten alt for mye arbeid. Brukergrensesnitt bygget med slike rammeverk har noenlunde samme struktur, og her skal vi gi en liten introduksjon med utgangspunkt i JavaFX-rammeverket. Vi har valgt JavaFX, fordi det er mer fleksibelt og moderne enn Java Swing, som det er ment å ta over etter og det ligner mer på andre "rammeverk" som HTML5 og Flash.

JavaFX er en standard del av både Java 7 og Java 8, men merk at om du bruker Java 7, så må du selv legge til en JavaFX-jar i Build Path for prosjektet ditt, for å kunne bruke JavaFX-klassene. Denne heter jfxrt.jar og finnes i jre/lib-mappen i Java-installasjonen din. Et alternativ er å installere e(fx)clipse-tillegget til Eclipse, som bidrar med en veiviser for å opprette JavaFX-prosjekter.

Vi deler gjerne rammeverket opp i håndtering av

  • kjøring/oppstart av programmer
  • strukturer av grafiske og interaktive elementer, altså det rent visuelle
  • interaktivitet, altså brukerinput og dynamikk
  • GUI-definisjonsfiler

Et "vanlig" program starter naturlig nok med det første. Så bygges det opp en visuell struktur, med en blanding av rent grafiske elementer og interaktive komponenter (ofte kalt widgets). De interaktive elementene knyttes deretter til programlogikken som skal reagere på og håndtere input fra brukeren. De fleste rammeverk gjør det mulig å beskrive det andre punktet i separate XML-filer (litt som HTML brukes for web-sider), slik at programmet blir mindre og ryddigere.

Vi skal ta disse i tur og orden.

Kjøring/oppstart av programmer

JavaFX har en egen programklasse kalt Application (i pakken javafx.application) som en må arve fra. Denne definerer en del metoder som kalles når programmet initialiseres (init), starter (start) opp og stopper (stop). Av disse er det bare start-metoden som må defineres i din egen hovedprogramklasse. Men merk at det ikke er din egen kode som oppretter instansen av hovedprogramklassen og kaller disse oppstartsmetodene, det er det rammeverket selv som gjør. Og for at det skal skje må du i main-metoden kalle den statiske metoden launch i Application-klassen med navnet til din egen klasse som første argument og programargumentene til din main-metode som launch-metoden sitt andre argument. En minimalt hovedprogramklasse er vist under til venstre, med applikasjonsvinduet som da dukker opp til høyre.

 

import javafx.application.Application; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.paint.Color; import javafx.stage.Stage; public class SimpleJavaFX extends Application { public static void main(String[] args) { launch(args); } @Override GroupGroup , Color.AQUA Simple JavaFX

 

Code Block
languagejava
public class MinimalApplication extends Application {
    
    @Override
    
Code Block
languagejava
titleStage and scene with empty scene graph
collapsetrue
public void start(Stage stage) throws Exception {
        
        Pane root = new 
Pane(); // Root of the scene graph
        Scene scene = new Scene(root, 500, 500
);
    
        stage.setScene(scene);
        stage.setTitle("
MinimalApplication");
        stage.show();
    }
    public static void main(String[] args) {
        launch(MinimalApplication.class, args);
    }
}

Scene Graph

Det er scenegrafen som styrer selve utformingen av grensesnittet i en JaveFX-applikasjon. JavaFX Scene Graph API gjør det mye enklere å lage et GUI ved at en scenegraf på et hvilket som helst tidspunkt vet hvilke objekter som skal vises frem, hvilke områder på skjermen som må tegnes på nytt og hvordan alt skal rendres på en mest mulig effektiv måte. Ved å bruke scenegraf API'et slipper man å forholde seg til primitive tegnemetoder og kan heller la systemet ta seg av detaljer omkring rendring.

Image Added

start-metoden tar inn et argument av typen Stage (i javafx.stage-pakken), som tilsvarer applikasjonsvinduet. Denne må knyttes til innhold ved at en oppretter en instans av Scene (javafx.scene) med selve innholdet i. I denne minimale applikasjonen så nøyer vi oss med en instans av Pane (javafx.scene.layout), som gir oss t tomt panel. I tillegg til Pane-instansen så oppgir vi her også bredden og høyden Til slutt settes tittelen til applikasjonsvinduet og den vises frem. Merk at launch-metoden som ble kalt i main-metoden ikke returnerer (mens vinduet er oppe), så det er poengløst å legge annen koden etter launch-kallet.

Strukturer av grafiske og interaktive elementer

Innholdet i applikasjonen utgjøres av en såkalt scene graph, som er en hierarkisk struktur av såkalte noder. Node kan være beholdere (eng: container) for andre noder, rene grafiske elementer som streker, rektangler, ellipser polygoner osv. eller interaktive komponenter som knapper, tekstfelt, menyer, lister osv. Nodene har informasjon om plassering og beholderne kan i tillegg ha egen logikk for hvordan innholdet fordeles utover i bredden og høyden. Denne strukturen er illustrert i figurene under (hentet fra Oracle sine nettsider)En scene graph er en trestruktur som inneholder dataelementer, og hvert element kalles en node. En node er enten en rotnode, intern-node eller løvnode, noe som vil si at alle noder i treet, for utenom rotnoden, må ha en parent (forelder). Under er et eksempel på en enkel scene graph, med elementer både som nodetype, og med faktiske klasser som kan tilsvare nodetypen.

Nodetype
- Bildet er hentet fra oracles nettsider 

Klasser som kan opptre som tilsvarende nodetype
- Bildet er hentet fra oracles nettsider 

I pakken javafx.scene finnes det massevis av klasser, men de tre viktigste for å forstå oppbygningen til Scene Graph API er

  • Node: Abstrakt baseklasse for alle noder i en scene graph.
  • Parent: Abstrakt baseklasse for alle interne noder. (Arver direkte fra Node)
  • Scene: Base containeren for alt innholdet i en scene graph.

Disse klassene inneholder viktig funksjonalitet som arves videre og brukes i forbindelse med tegnerekkefølge, synlighet, transformasjoner, osv.
Parent arves av diverse internnode-klasser som Control, Group, Region og WebView. Det finnes også utallige løvnode-klasser, slik som javafx.scene.shape og javafx.scene.text.

Det er få om ingen begrensninger på hva som kan være inni hva, og sammen så forteller denne strukturen akkurat hva som skal vises på skjermen for brukeren. Dersom en underveis i kjøringen endrer på noen av objektene, f.eks. endrer posisjoner, farger, fonter eller legger til og/eller fjerner elementer, så oppdateres skjermen tilsvarende. Man slipper altså å forholde seg til primitive tegnemetoder, men manipulerer istedet på objektstrukturer og lar systemet ta seg av detaljer omkring hvordan disse tegnes (såkalt rendering).

Container-klasser og layout

Når en bygger opp dette objekt-hierarkiet så tenker en gjerne først på den overordnede layout-strukturen, dvs. utlegget av hovedelementer og velger container-typer som gir ønskede muligheter. Merk at det er mulig å unnta en node fra den vanlige layout-logikken til en container vet at den markeres som ikke managed, ved å kalle setManaged(false).

Her er en liste av de vanligste container-typene (nærmere beskrevet i http://docs.oracle.com/javafx/2/layout/builtin_layouts.htm):

  • HBox og VBox - legger ut elementene horisontalt (x-dimensjonen, dvs. bortover) eller vertikalt (y-dimensjonen, dvs. nedover) og gjør dem like store i den andre dimensjonen.
  • GridPane og TilePane - plasserer elementene i et rutenett, TilePane gjør alle ruten like store, mens GridPane er mer fleksibel.
  • BorderPane - fordeler elementene i regioner, midten (for hovedinnholdet), top, bunn, venstre og høyre, og passer for hovedinnholdet i applikasjonen.
  • AnchorPane - henger elementene fast i punkter knyttet til en eller flere av sidene
  • Pane - generell container uten spesifikk layout, så posisjonen på elementer må settes manuelt.

Alle container-objektene har en liste med elementer i sin children-liste (hentes ut med getChildren()), som igjen kan være container-objekter, om en ønsker, f.eks. en GridPane inni en AnchorPane inni en BorderPane. Hele denne strukturen legges så inn i en Scene i en Stage.

Det er generelt to metoder for å legge til barn:

  • container.getChildren().add(node); // legger til én node
  • container.getChildren.addAll(node1, node2, node3,....); // legger til flere noder

Noen container-klasser har egne metoder for å legge inn noder, f.eks. har BorderPane én metode for hver av de fem regionene som den deler panelet inn i. Dette er vist i eksemplet under, som utvider MinimalApplication med en BorderPane og ett tekst-objekt for hver region.

Code Block
languagejava
public class BorderPaneApplication extends Application {
    
    @Override
    public void start(Stage stage) throws Exception {
        
        BorderPane root = new BorderPane(); // Root of the scene graph
        
        // Add one Text node in each region
        root.setTop(new Text("top"));
        root.setBottom(new Text("bottom"));
        root.setLeft(new Text("left"));
        root.setRight(new Text("right"));
        root.setCenter(new Text("center"));
        
        Scene scene = new Scene(root, 500, 500);
    
        stage.setScene(scene);
        stage.setTitle("BorderPaneApplication");
        stage.show();
    }
    public static void main(String[] args) {
        launch(BorderPaneApplication.class, args);
    }
}
Image Added

 

Noder

Det finnes noen fellestrekk som gjelder for alle nodene man bruker i JavaFX. Alle noder, utenom rotnoden, legges til i grafen ved å addes i listen av barn legge dem inn i children-lista til en parent Parent-nodeinstans. En node kan bare eksistere på et sted. Dersom være ett sted i hierarkiet og dersom man forsøker å legge til et objekt som child på flere forskjellige steder i grafen, allerede er i hierarkiet til en annen/ny container, så vil objektet fjernes fra dens forrige parrents liste over children,  før det addes som child hos sin nye parent. Det kan heller ikke finnes sykler i grafen. før den legges til den nye.

Til hver node kan det knyttes en id, altså et unikt navn (må sikres av utvikleren) tilsvarende id-tag'en i HTML. En nyttig funksjon er Id
Hver node kan også gis en id. Dette er en id som ligner veldig på id-tagen i HTML, og det er opp til utvikleren å sikre at en id er unik i en scene graph. En nyttig funksjon i denne forbindelse er lookup(String id) som kan brukes til å finne en node med en unik id i en scene graphdel av hierarkiet. Ellers kan også id'en brukes til å identifisere noder når man vil legge til stiler, se bruker css-stiler (mer om css lenger ned)

Koordinater
Nodene følger et tradisjonell grafisk Alle noder har en x,y-posisjon og størrelse (desimaltall) i et koordinatsystem med økende verdier mot høyre på x-aksen, og økende verdier nedover på y-aksen. Med dette er altså (0,0) lokalisert øverst til venstre. Man kan også definere koordinatene med flyttall og (0.5 , 0.5) tilsvarer da sentrum av pixelet øverst til venstre.

Transformasjoner

...

Denne x,y-posisjonen angis imidlertid ikke direkte, men er kombinasjonen av ulike typer transformasjoner:

  • translateX og translateY - forskyver noden i x- og

...

  • y-retningen
  • rotate - roterer noden et antall grader rundt et gitt referansepunkt.

...

  • scaleX og scaleY - skalerer størrelen

...

  • med en x- og y-faktor

...

  • og settes med scale-metoden
  • layoutX og layoutY - gir en ekstra forskyving i x- og y-retning og brukes gjerne i forbindelse med midlertidige justeringer, f.eks. drag'n drop

Det går også an å lage egne transformasjonsobjekter, hvor disse effektene kan kombineres, og som kan knyttes til noder.

Figurer

Det finnes en rekke standard-figurer som kan brukes for rent grafisk innhold. Disse er alle subklasser av Shape-klassen (javafx.scene.shape), som får dermed en del felles egenskaper:

  • fyll (fill) - fargen eller effekten (av typen Paint) som fyller figuren
  • strek (stroke) - fargen eller effekten som brukes for å tegne figuren
  • strek-tykkelse (strokeWidth), plassering (strokeType), stipling (strokeDashOffset) og hjørne/endehåndtering (strokeLineJoin og strokeLineCap)

Nyttige figurer er strek (Line), rektangel (Rectangle), sirkel og ellipse (Circle og Ellipse), buesegment (Arc), polygon (Polygon), segmentert figur (Path) og tekst (Text). I tillegg er det en egen klasse for bilder (ImageView som viser et Image, som ikke er en Shape). Noen av disse er vist i eksemplet under, hvor center-regionen er fylt med et Pane-objekt med noen figurer i, med ulike grafiske effekter.

Code Block
languagejava
    @Override
    public void start(Stage stage) throws Exception {
        
        BorderPane root = new BorderPane(); // Root of the scene graph
        
        // Add one Text node in each surrounding region
        root.setTop(new Text("top"));
        root.setBottom(new Text("bottom"));
        root.setLeft(new Text("left"));
        root.setRight(new Text("right"));
        
        Pane shapesPane = new Pane();
        shapesPane.setPrefSize(300, 300);
        Line line = new Line(10, 10, 100, 100); // x1, y1, x2, y2
        line.getStrokeDashArray().setAll(10.0d, 10.0d); // dashes
        Rectangle rect = new Rectangle(150, 10, 30, 40); // x, y, w, h
        rect.setFill(Color.BLUE);
        Ellipse ell = new Ellipse(40, 180, 40, 30); // cx, cy, rx, ry
        ell.setStroke(Color.RED);
        ell.setStrokeWidth(5);
        ell.setFill(Color.GREEN);
        Text text = new Text(180, 180, "center");
        List<String> fonts = Font.getFamilies();
        text.setFont(new Font(fonts.get((int) (Math.random() * fonts.size())), 32));
        shapesPane.getChildren().addAll(line, rect, ell, text);
        root.setCenter(shapesPane);
        
        Scene scene = new Scene(root, 500, 500);
    
        stage.setScene(scene);
        stage.setTitle("BorderPaneApplication");
        stage.show();
    }
Image Added

Les mer om enkel 2d-grafikk her: Enkel 2D-grafikk med JavaFX og FXML

Grafisk stil med CSS (Cascading Style Sheets)

...

En node har feltene id, styleClass og style. Disse brukes til å endre nodens grafiske stil med CSS. Feltene id og styleClass brukes i CSS for å avgjøre hvilke noder som skal ha de forskjellige stilene. Feltet style brukes til å legge på en CSS stil til en node direkte i koden. For en basic forståelse av dette konseptet, ta en titt på denne videoen. Oracle har også en god tutorial for å få et lite innblikk i mulighetene CSS gir oss.

Interaktivitet

De mest interessante nodene er de som er interaktive, altså reagerer på input fra brukeren og brukes til å styre applikasjonen og redigere applikasjonsdata. Her er en oversikt over standard-komponentene: http://docs.oracle.com/javafx/2/ui_controls/jfxpub-ui_controls.htm Standard-utvalget er foreløpig ikke så godt som i Java Swing, men det begynner å komme en del gode tredjeparts klassebiblioteker, f.
 eks. http://fxexperience.com/controlsfx/

De interaktive komponentene har en felles måte å rapportere hva brukeren gjør, gjennom såkalte hendelser (eng: event). Hendelsene kan fortelle om både elementære ting (såkalte leksikalske hendelser) som at muspekeren flyttes eller at en tast trykkes ned, eller mer høynivå ting (såkalte syntaktiske hendelser) som at en knapp trykkes, meny- eller liste-element velges, tekst redigeres osv. For å motta en hendelse, så må applikasjonen registrere såkalte lyttere, dvs. objekter som implementerer spesifikke lytter-grensesnitt og som får beskjed om hendelsene ved at spesifikke metoder kalles.

Det er to hovedkategorier hendelser, interaksjonshendelser og dataendringshendelser, og disse bruker hver sine lyttergrensesnitt og metoder for å registrere lyttere. La oss ta et tekstfelt som eksempel, siden rapporterer hendelser av begge typer:

Interaksjonshendelsen "action"

Når brukeren trykker return (for å angi at teksten er ferdigredigert), så genereres en interaksjonshendelser kalt "action" av typen ActionEvent. Denne rapporteres til grensesnittet EventHandler<ActionEvent> og metoden handle(ActionEvent). I praksis betyr dette at grensesnittet med tilhørende metode må implementeres av et objekt, og dette objektet må registreres som lytter. Det vanlige er at lytteren implementeres vha. en såkalt anonym indreklasse, som enkelt sagt er et enkeltobjekt som implementerer grensesnittet uten at en eksplisitt må opprette og navngi en ny klasse. I dette tilfellet gjøres det med følgende kode:

Code Block
languagejava
new EventHandler<ActionEvent>() {
	@Override
	public void handle(ActionEvent actionEvent) {
		... her er selve koden ...
    }
}

Denne lytter(grensesnitt-instans)en oppgis direkte som argument til metoden som registrerer lytteren. Det finnes to (typer) metoder som brukes til dette:

  • en generell addEventHandler-metode, som tar inn en hendelsestype og lyttereren, i dette tilfellet addEventHandler(ActionEvent.ACTION, new EventHandler<ActionEvent>() {... });
  • spesielle setOnXXX-metoder, som brukes for spesifikke hendelsestyper og tar inn en lytter, i dette tilfellet setOnAction(, new EventHandler<ActionEvent>() {... })

I koden nedenfor har vi brukt setOn-varianten, siden den er enklest å lese og skrive.

Dataendringshendelse for "text"-egenskapen

Mens brukeren redigerer teksten i et tekstfelt, så genereres det kontinuerlig (for hver enkelt-endring) en dataendringshendelser for "text"-egenskapen (eng: property) til tekstfeltet. En slik egenskap kan ses på som en verdi (i dette tilfellet en String) som er innkapslet av et objekt som lar en lese, endre og lytte til endringer. Dette er en generell teknikk som brukes av alle dataorienterte komponenter, for å bruke og redigere data. Denne typen hendelser rapporteres til grensesnittet ChangeListener spesialisert til typen data, i dette tilfellet ChangeListener<String> og metoden changed. Også her er det praktisk å bruke enkeltobjekter som implementerer grensesnittet, i dette tilfellet følgende kode:

Code Block
languagejava
new ChangeListener<String>() {
	@Override
	public void changed(ObservableValue<? extends String> property, String oldValue, String newValue) {
		... her er selve koden ...
	}
}

Denne lytter(grensesnitt-instans)en oppgis (som over) direkte som argument til metoden som registrerer lytteren. Metoden er en del av objektet som innkapsler String-verdien, så følgende kode brukes: textProperty().addListener(new ChangeListener<String>() {... }); textProperty()-kallet returnerer text-egenskapen og det er denne som registrerer lytteren med addListener.

I eksemplet under har vi kombinert både interaksjonshendelser og dataendringshendelser for et tekstfelt i topp-regionen. Hvor hver redigering så kopieres teksten i tekstfeltet til Text-objektet i midt-regionen, og når brukeren trykker return, så gjøres denne teksten om til store bokstaver.

Code Block
languagejava
    @Override
    public void start(Stage stage) throws Exception {
        
        BorderPane root = new BorderPane(); // Root of the scene graph
        
        textField = new TextField("center");
        textField.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent arg0) {
                centerText.setText(textField.getText().toUpperCase());
            }
        });
		textField.textProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> value, String oldText, String newText) {
                centerText.setText(textField.getText());
            }
        });
		centerText = new Text(180, 180, textField.getText()); // x, y, text
        
        root.setTop(textField);
        root.setBottom(new Text("bottom"));
        root.setLeft(new Text("left"));
        root.setRight(new Text("right"));
        Pane shapesPane = new Pane();
        shapesPane.setPrefSize(300, 300);
        Line line = new Line(10, 10, 100, 100); // x1, y1, x2, y2
        line.getStrokeDashArray().setAll(10.0d, 10.0d); // dashes
        Rectangle rect = new Rectangle(150, 10, 30, 40); // x, y, w, h
        rect.setFill(Color.BLUE);
        Ellipse ell = new Ellipse(40, 180, 40, 30); // cx, cy, rx, ry
        ell.setStroke(Color.RED);
        ell.setStrokeWidth(5);
        ell.setFill(Color.GREEN);
        centerText = new Text(180, 180, "center");
        List<String> fonts = Font.getFamilies();
        centerText.setFont(new Font(fonts.get((int) (Math.random() * fonts.size())), 32));
        shapesPane.getChildren().addAll(line, rect, ell, centerText);
        root.setCenter(shapesPane);
        
        Scene scene = new Scene(root, 500, 500);
    
        stage.setScene(scene);
        stage.setTitle("BorderPaneApplication");
        stage.show();
    }
Image Added

GUI-definisjonsfiler

For større GUI, så blir koden for å bygge opp hierarkiet av grafiske og interaktive elementer fort nokså stor, selv om den ikke er så kompleks. Spesielt når en begynner å endre mange egenskaper, f.eks. farge, strek, font osv. så blir det mye kode og mye "støy". Helt analogt med hvordan HTML brukes til å beskrive strukturen og innholdet til en nettside, så tilbyr JavaFX et XML-format kalt FXML for å beskrive strukturen og innholdet til et GUI. Hierarkiet av XML-elementer (tags) tilsvarer hierarkiet av grafiske og interaktive elementer, og formatet støtter "alle" klasser og typer som er presentert over. En kan også utvide med egne elementer, hvis en lager egne klasser som passer inn i hierarkiet. Den viktigste ulikheten med HTML er at FXML-koden ikke inkluderer programlogikk, det er Java-koden som laster inn FXML-fila. Vi skal ikke gå i dybden på FXML-språket her (se heller http://docs.oracle.com/javafx/2/api/javafx/fxml/doc-files/introduction_to_fxml.html), men komme med en generell forklaring.

Det viktigste å vite om FXML er at det bygger direkte på klassene, metodene og attributtene som finnes i JavaFX-rammeverket og at disse brukes som navn på XML-elementer og attributter. Hvis et element heter javafx.scene.layout.Pane, så er det en direkte referanse til JavaFX-klassen Pane-klassen, og som i Java-kode så er det støtte for å importere navn, så en kan bruke kortformen Pane. Tilsvarende så kan en sette teksten i et Text-objekt med XML-attributtet text i (Text-elementet) fordi Text-klassen har en setText-metode. Sammenhengen mellom hva som er gyldig XML og hvilke klasser, metoder og attributter som finnes i JavaFX-rammeverket er sammenfattet i en del relativt enkle regler, så hvis en kjenner JavaFX-API'et, så er det ikke så vanskelig å bli vant med FXML-formatet.

Under ser vi det siste eksemplet kodet som FXML.

Code Block
languagehtml/xml
<?xml version="1.0" encoding="UTF-8"?>
 
<?import java.lang.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.textfield.*?>
<?import javafx.scene.shape.*?>
<?import java.lang.Double?>
 
<BorderPane xmlns:fx="http://javafx.com/fxml"
    prefHeight="500" prefWidth="500">
     <top>
         <TextField id="textField" text="center"/>
     </top>
     <center>
         <Pane>
             <Line startX="10" startY="10" endX="100" endY="100">
                 <strokeDashArray>
                     <Double fx:value="10"/>
                     <Double fx:value="10"/>
                 </strokeDashArray>
             </Line>
             <Rectangle x="150" y="10" width="30" height="40" fill="blue"/>
             <Ellipse centerX="40" centerY="180" radiusX="40" radiusY="30" strokeWidth="5" fill="green" stroke="red"/>
            <Text id="centerText" x="180" y="180" text="center">
                <font>
                    <Font name="Times" size="32"/>
                </font>
            </Text>
         </Pane>
     </center>
     
     <left>
         <Text text="left"/>
     </left>
     
     <right>
         <Text text="right"/>
     </right>
     
     <bottom>
         <Text text="bottom"/>
     </bottom>
     
</BorderPane>