Informatica JavaFX 11 R.M.M. Woudt
2009 2010 R.M.M. Woudt Delfstrahuizen remie.woudt@gmail.com Omslag: Haberdasher s problem 2003 R.M.M. Woudt In Haberdasher s problem gaat het erom een gelijkzijdige driehoek d.m.v. drie sneden zodanig te verdelen dat van de uitgeknipte stukken een vierkant gemaakt kan worden. 2
11 Waar komen de nodes? Tot nu toe hebben we ons nog niet zo bezig gehouden met de plaats van de nodes. We gingen ze een beetje op hun plek schuiven met bijvoorbeeld translatex en translatey en dat was het dan. Maar op die manier krijgen de nodes vaak vaste plaatsen op het scherm. Dat kan lastig zijn wanneer we, zoals tegenwoordig gebruikelijk, het scherm gaan vergroten of verkleinen. Het is dan handiger om gebruik te maken van de standaard layout mogelijkheden van JavaFX. In dit deel gebruiken we de HBox, de VBox en de Tile layout. Zoals gewoonlijk doen we dat weer aan de hand van opdrachten. De HBox en VBox layout Opdracht 11.1 We gaan de interface maken van een soort van mp3 speler. Hiernaast zie je wat de bedoeling is. Het gaat nog niet om een werkende versie maar nu nog alleen om de layout ervan. Maak een JavaFX project, maar daarbinnen een package en daar weer binnen een leeg JavaFX bestand. De namen ervoor mag je zelf bedenken. Kijk je naar de afbeelding hiernaast dan zie je in feite twee kolommen. In de eerste kolom staat een lijst met namen van liedjes, in de tweede zie je de knoppen waarmee je het betreffende liedje zou kunnen laten afspelen. Allereerst beginnen we, zoals gewoonlijk, met de Scene: import javafx.stage.stage; import javafx.scene.scene; import javafx.scene.paint.color; Stage { title: "Liedjes" scene: Scene{ fill: Color.NAVY width: 250 height: 230 Tot zover niets nieuws. Wat je heermee op het scherm krijgt is een bijna vierkant window met een donkerblauwe achtergrond. We gaan nu een lijst met liedjes maken. We doen dat nu eenvoudig met behulp van een sequentie maar je zou je voor kunnen stellen dat het 3
programma ook zelf op zoek gaat naar muziekbestanden en die zo op je scherm laat zien. import javafx.scene.paint.color; def liedjes = [ "You Raise Me Up", "No Surrender", "A Night Like This", "In Da Name Of Love", "Fireflies" ; Stage { Nu we de lijst met liedjes hebben, kunnen we ze ook in een variabele stoppen. Dit doen we door met for door de sequentie te lopen. Maar eerst voegen we de bijbehorende import opdrachten toe. import javafx.scene.paint.color; import javafx.scene.group; import javafx.scene.shape.rectangle; import javafx.scene.paint.lineargradient; import javafx.scene.paint.stop; import javafx.scene.text.text; import javafx.scene.text.font; import javafx.scene.text.fontweight; def liedjes = [ Dat zijn heel wat import opdrachten maar er komt dan ook heel wat code bij: ; "In Da Name Of Love", "Fireflies" var speellijst = for (liedje in liedjes) Group { content:[ Rectangle{ width: 200 height: 25 fill: LinearGradient { starty: 5 endx: 190 endy: 10 stops: [ Stop { offset: 0.0 color: if (indexof liedje mod 2 == 0) then Color.LIGHTGREY else Color.LIGHTBLUE Stop { offset: 1.0 color: Color.NAVY 4
Text { font: Font.font(null, FontWeight.BOLD, 14) y: 20 x: 5 fill: Color.BLACK content: liedje Dit is veel code in één keer en het gekke is, als je het programma nu laat runnen verschijnt er nog niets op het scherm. Maar we hebben nu wel een sequentie gemaakt waarbij de balken vastliggen waarin de titels van de liedjes komen te staan. Laten we het maar eens wat nader bekijken: In de eerste regel staat het belangrijkste: var speellijst = for (liedje in liedjes) Group { Je kunt dit lezen als: voor ieder liedje in de sequentie liedjes wordt er een Group aangemaakt en die wordt geplaatst in de variabele speellijst. In die groep wordt dan een rechthoek en een tekst gezet. De rechthoek heeft een lineaire gradatie van de kleuren, lopend van LIGHTGREY naar NAVY (de even balken) en van LIGHTBLUE naar NAVY (de oneven balken). Klopt dat wel? In de code zien we: if (indexof liedje mod 2 == 0) then Color.LIGHTGREY else Color.LIGHTBLUE Dus wanneer (indexof liedje mod 2 == 0) hebben we te maken met een even balk en wordt hij LIGHTGREY maar we zien toch duidelijk in de afbeelding de tweede en de vierde balk LIGHTBLUE. Denk erop dat er bij sequenties altijd vanaf 0 geteld wordt. Dus de eerste, de derde en de vijfde balk in het plaatje hebben de indexof waarden 0, 2 en 4 en gelden hier dus als even! De lineaire gradatie heeft hier de toevoeging: Dat betekent dat de gradatiegrenzen hier met de pixelwaarde worden aangegeven. Experimenteer maar eens met de getallen om de verschillen te zien. De rest van de code bevat geen nieuwe zaken. Bedenk dus dat voor ieder liedje in de liedjeslijst hier zo n groep bestaande uit een rechthoek en de tekst van het liedje worden aangemaakt. Maar dan is het wel zo leuk als we daar ook vast iets van kunnen zien. Eerst weer even wat code: Voeg de volgende import opdrachten toe: import javafx.scene.text.fontweight; import javafx.scene.layout.vbox; import javafx.scene.layout.hbox; 5
En in de scene komt het volgende: height: 230 content: HBox { content: [ VBox{spacing: 12 content: speellijst, We voegen hier een HBox en daarbinnen een VBox toe. Een HBox is niets anders dan een ruimte waarbinnen we nodes toe kunnen voegen en waarbij de nodes dan horizontaal naast elkaar worden geplaatst. En een VBox is dus een ruimte waar je nodes toe kunt voegen en waarbij de nodes dan verticaal naast elkaar worden geplaatst. HBox en VBox zijn flexibele layout mechanismen. Wanneer je bijvoorbeeld 5 nodes in een HBox wilt plaatsen maar er passen maar 4 nodes op een rij dan kun je hem zo instellen dat de vijfde node op de volgende rij komt. Maak je dan het window groter dan zal die vrijde node er wel netjes achter gaan staan. In dit voorbeeld hebben we 5 nodes, namelijk de 5 rechthoeken die aangemaakt zijn omdat we 5 liedjes hebben en die in de variabele speellijst zijn geplaatst. Die 5 rechthoeken zijn in de VBox geplaatst dus komen ze onder elkaar op het scherm. We hebben die VBox weer in een HBox geplaatst omdat we er straks horizontaal de knoppen aan toe willen voegen. En dan nog: spacing: 12 Dit betekent dat de ruimte tussen de nodes in de VBox 12 pixels bedraagt. Nu tijd om de knoppen te maken. Voeg de volgende import opdrachten toe: import javafx.scene.layout.hbox; import javafx.scene.shape.circle; import javafx.scene.shape.polygon; import javafx.scene.paint.radialgradient; De knop bestaat uit een cirkel met daarin een driehoekje. Je kunt daarvoor een plaatje gebruiken maar we kunnen hem ook zelf maken. Hier de code: fill: Color.BLACK content: liedje var speelknoppen = for (liedje in liedjes) Group { content:[ Circle { radius: 15 fill: RadialGradient { radius: 15 focusx: 0 focusy: 0 6
stops: [ Stop {offset: 0 color: Color.DARKGRAY, Stop {offset: 1 color: Color.WHITE, Polygon { points : [ -5,-8, -5,8, 8,0 fill: Color.RED stroke: Color.DARKBLUE Ook hier wordt weer, net als bij de rechthoeken, voor ieder liedje een group aangemaakt bestaande uit een cirkel en een driehoek. De driehoek is eigenlijk een veelhoek (Polygon) waarvan we drie punten definiëren. De eerste twee getallen zijn de x en y-waarden van de eerste hoek van de driehoek. En zo worden met de volgende paren steeds een hoekpunt gedefinieerd. JavaFX trekt door de punten die we zo aangeven een lijn en omdat we hier drie paren aangeven ontstaat er dus een driehoek. Voor de rest zou de code niet echt meer geheimen moeten hebben. Nu moeten we de knoppen nog toevoegen aan de scene. En dat is nu niet zo lastig meer. Voeg de volgende code maar toe: content: HBox { content: [ VBox{spacing: 12 content: speellijst, VBox{spacing: 7 content: speelknoppen En nu moet hij het doen. De laatst toegevoegde regel zal wel duidelijk zijn. We zetten ook de speelknoppen in een VBox. Omdat de knoppen iets groter zijn dan de rechthoeken, wordt de spacing hier iets kleiner. De beide VBox-en zijn hier in een HBox geplaatst. Daarmee zorgen we er voor dat ze naast elkaar worden afgebeeld. En dat is hier de bedoeling. De Tile layout De tile (tegels) layout, ook wel grid layout genoemd, verdeeld het window in even grote rechthoekjes. Een layout zoals je wel kent van een spreadsheet programma als Excel. In ieder rechthoekje kunnen we een node plaatsen en ze worden dan keurig naast en onder elkaar gerangschikt. Opdracht 11.2 Het bovenstaande voorbeeld gaan we nu in de tile layout uitvoeren. Hier de code in z n geheel. import javafx.stage.stage; import javafx.scene.scene; import javafx.scene.paint.color; import javafx.scene.group; 7
import javafx.scene.shape.rectangle; import javafx.scene.paint.lineargradient; import javafx.scene.paint.stop; import javafx.scene.text.text; import javafx.scene.text.font; import javafx.scene.text.fontweight; import javafx.scene.shape.circle; import javafx.scene.shape.polygon; import javafx.scene.paint.radialgradient; import javafx.scene.layout.tile; def liedjes = [ "You Raise Me Up", "No Surrender", "A Night Like This", "In Da Name Of Love", "Fireflies", "Blablabla" ; var aantal = (sizeof liedjes); var speellijst = for (liedje in liedjes) Group { content:[ Rectangle{ width: 200 height: 25 fill: LinearGradient { starty: 5 endx: 190 endy: 10 stops: [ Stop { offset: 0.0 color: if (indexof liedje mod 2 == 0) then Color.LIGHTGREY else Color.LIGHTBLUE Stop { offset: 1.0 color: Color.NAVY Text { font: Font.font(null, FontWeight.BOLD, 14) y: 20 x: 5 fill: Color.BLACK content: liedje 8
var speelknoppen = for (liedje in liedjes) Group { content:[ Circle { radius: 15 fill: RadialGradient { radius: 15 focusx: 0 focusy: 0 stops: [ Stop {offset: 0 color: Color.DARKGRAY, Stop {offset: 1 color: Color.WHITE, Polygon { points : [ -5,-8, -5,8, 8,0 fill: Color.RED stroke: Color.DARKBLUE Stage { title: "Liedjes" scene: Scene{ fill: Color.NAVY width: 330 height: 230 content: Tile { columns: 2 rows: aantal vgap: 5 content: for (i in [0..(aantal-1)) [speellijst[i, speelknoppen[i Zoals je ziet, heel veel code maar grotendeels gelijk aan de voorgaande. Als je het laat runnen zie je ook meteen een nadeel van de tile layout. Alle tegels zijn even breed en hoog. Je kunt wel de breedte en de hoogte van de tegels instellen (met de tilewidth en de tileheight variabele maar dan geldt dat meteen wel voor alle tegels. Nog even de belangrijkste veranderingen in de code: De HBox en VBox import opdrachten zijn verdwenen. Daarvoor in de plaats is de Tile importregel gekomen: import javafx.scene.layout.tile; Om het aantal rijen tegels in de scene te weten moeten we ze eerst gaan tellen. Het aantal elementen in een sequentie tel je met sizeof. var aantal = (sizeof liedjes); De rest van de code is identiek aan het eerste voorbeeld behalve het stukje in de scene. Daar 9
wordt als content de Tile layout aangegeven. content: Tile { columns: 2 rows: aantal vgap: 5 content: for (i in [0..(aantal-1)) [speellijst[i, speelknoppen[i Die definiëren we door het aantal kolommen en het aantal rijen in te voeren. Met vgap geven we de ruimte aan die verticaal tussen de nodes zit. De content bestaat daarna uit het steeds naast elkaar plaatsen van een item uit de speellijst en een item uit speelknoppen. Opdracht 11.3 Het plaatje hiernaast kun je zowel met de HBox, VBox layout uitvoeren als met de Tile layout. Nieuw daarin is de radiobutton groep. Hier een stukje van de code waarmee het met de Tile layout is gedaan:... import javafx.scene.control.radiobutton; import javafx.scene.control.togglegroup; import javafx.scene.layout.tile; var groep = ToggleGroup{; var kiestekst = ["Stoppen", "Remmen", "Rijden"; var kleuren = ["RED", "ORANGE", "GREEN"; var keuzes = for (tekst in kiestekst) RadioButton{ text: " {tekst" font: Font{name:"Tahoma" size: 15 togglegroup: groep... Nog even wat uitleg over de radiobuttons. Wanneer je radiobuttons gebruikt moet je die in een togglegroup plaatsen. Radiobuttons die in dezelfde toggelgroup zijn geplaatst reageren op elkaar. Er kan er dan maar één aangeklikt zijn (selected) Klik je op de ene dan gaan de andere die geselecteerd was weer uit. Door meerdere toggelgroups te maken kun je dus meerdere groepen van radiobuttons op één pagina gebruiken. Net als de teksten zul je ook de kleuren moeten doorlopen om die als cirkels in een variabele te plaatsen. Kijk hoe dat bij de mp3 speler layout gedaan is. Hieronder nog een lastig stukje code daarvan, het vullen van de verkeerslichten. Let op! Er wordt daar gebruik gemaakt van een variabele kleur. Wat voor soort variabele is dat? Waar komt die vandaan? Wat doet die variabele? 10
fill: bind RadialGradient { centerx: 8, centery: 8, radius: 12, stops: [ Stop {offset: 0.0 color: Color.WHITE Stop {offset: 1.0 color: if (keuzes[indexof kleur.selected) then Color.web(kleur) else Color.GREY Daarna kun je zowel de teksten met de radiobuttons (keuzes) als de verkeerslichten in de scene in Tiles plaatsen. Maar dat moet je nu zelf uit kunnen zoeken! Maak dus deze opdracht af met de Tile layout. Opdracht 11.4 Als opdracht 11.3 maar nu met de HBox en VBox layout. 11