SlideShare a Scribd company logo
Construire une application JavaFX 8 avec 
gradle
Construire une application JavaFX 8 avec gradle 
Thierry Wasylczenko 
@twasyl 
La session trendy
3
Ce dont on va parler 
• JavaFX 8 
• gradle 
• Tooling 
• De code 
4
5 
#JavaFX 8: 
De 2 à 8
Les Properties
Les types 
8 
IntegerProperty intP = new SimpleIntegerProperty(); 
DoubleProperty doubleP = new SimpleDoubleProperty(); 
// ... 
BooleanProperty booleanP = new SimpleBooleanProperty(); 
StringProperty stringP = new SimpleStringProperty(); 
ObjectProperty<SoftShake> objectP = new SimpleObjectProperty();
Le binding 
IntegerProperty intP1 = new SimpleIntegerProperty(); 
IntegerProperty intP2 = new SimpleIntegerProperty(); 
intP1.bind(intP2); 
intP2.set(10); 
System.out.println("Et P1? " + intP1.get()); 
9
Le binding 
IntegerProperty intP1 = new SimpleIntegerProperty(); 
IntegerProperty intP2 = new SimpleIntegerProperty(); 
intP1.bindBidirectional(intP2); 
intP2.set(10); 
System.out.println("Et P1? " + intP1.get()); 
intP1.set(20); 
System.out.println("Et P2? " + intP2.get()); 
10
Les événements 
IntegerProperty intP1 = new SimpleIntegerProperty(); 
intP1.addListener((valueInt, oldInt, newInt) -> { 
System.out.println("Change"); 
}); 
intP1.set(10); 
11
POJO 2.0 
public class Conference { 
private StringProperty name = new SimpleStringProperty(); 
12 
public StringProperty nameProperty() { return this.name; } 
public String getName() { return this.name.get(); } 
public void setName(String name) { this.name.set(name); } 
} 
final Conference softShake = new Conference(); 
tf.textProperty().bindBidirectional(softShake.nameProperty());
Pas serializable
POJO 1.5 
public class Conference { 
private PropertyChangeStatus pcs = 
new PropertyChangeStatus(this); 
private String name; 
public void addPropertyChangeListener(PropertyChangeListener listener) 
this.pcs.addPropertyChangeListener(listener); 
} 
public void removePropertyChangeListener(PropertyChangeListener listener) 
this.pcs.removePropertyChangeListener(listener); 
14
POJO 1.5 
final Conference softShake = new Conference(); 
JavaBeanStringPropertyBuilder builder = new ... 
JavaBeanStringProperty nameProperty = builder.bean(softShake) 
.name("name") 
.build(); 
nameProperty.addListener((valueName, oldIName, newName) -> { 
// ... 
}); 
15
Vues
FXML 
<AnchorPane xmlns:fx="http://javafx.com/fxml" 
fx:controller="org.mycompany.Controller"> 
<stylesheets> 
<URL value="@/org/mycompany/css/Default.css" /> 
<URL value="@/org/mycompany/css/Specialized.css" /> 
</stylesheets> 
<Button fx:id="myButton" text="Button" onAction="#click" /> 
<TextField fx:id="myTextField" promptText="Enter something" /> 
</AnchorPane> 
17
Controller 
public class Controller implements Initializable { 
@FXML private Button myButton; 
@FXML private TextField myTextField; 
@FXML private void click(ActionEvent evt) { /* ... */ } 
@Override 
public void initialize(URL url, ResourceBundle resourceBundle) 
// ... 
} 
} 
18
CSS
Personnaliser les composants 
FXML 
<Button style="-fx-text-fill: white" styleClass="awesome" /> 
Java 
button.setStyle("-fx-background-color: red;"); 
button.getStyleClass().add("awesome"); 
20
Personnaliser les composants 
Feuille de style 
.button { 
-fx-text-fill: white; 
-fx-background-color: red; 
} 
.button:hover { -fx-background-color: blue; } 
.awesome { -fx-background-color: gray; } 
#myButton { -fx-text-fill: black; } 
21
PseudoState personnalisés 
PseudoClass ps = PseudoClass.getPseudoClass("awesome"); 
myButton.pseudoClassStateChanged(ps, true); 
.button:awesome { 
-fx-text-fill: orange; 
} 
#myButton:awesome { 
-fx-text-fill: green; 
} 
22
WebView
JS <> JFX 
webview.getEngine().getLoadWorker().stateProperty().addListener((observableValue, if (newState == Worker.State.SUCCEEDED) { 
JSObject window = (JSObject) webview.getEngine() 
.executeScript("window"); 
window.setMember("javaObj",this); 
} 
}); 
24
JS <> JFX 
Java 
WebEngine engine = webview.getEngine(); 
String result = engine.executeScript("return 'Hello';"); 
JavaScript 
javaObj.myWonderfulMethod("Hello"); 
25
WebSocket 
window.onload = function() { 
socket = new WebSocket("ws://mycompany.com/ws"); 
socket.onopen = function(event) { /* ... */ }; 
socket.onclose = function(event) { /* ... */ }; 
socket.onmessage = function(event) { /* ... */ }; 
}; 
26
Drag'n'drop
Drag'n'drop 
obj.setOnDragDetected(event -> { /* ... */ }); 
obj.setOnDragOver(event -> { /* ... */ }); 
obj.setOnDragEntered(event -> { /* ... */ }); 
obj.setOnDragExited(event -> { /* ... */ }); 
obj.setOnDragDropped(event -> { /* ... */ }); 
obj.setOnDragDone(event -> { /* ... */ }); 
28
Printing API
Print 
Chaque Node 
PrinterJob job = PrinterJob.createPrinterJob(); 
job.printPage(myTextField); 
Une page web 
WebView view = new WebView(); 
PrinterJob job = PrinterJob.createPrinterJob(); 
view.getEngine().print(job); 
30
3D
Objets prédéfinis 
Box b = new Box(width, height, depth); 
Cylinder c = new Cylinder(radius, height); 
Sphere s = new Sphere(radius); 
TriangleMesh tm = new TriangleMesh(); 
Les plus basiques 
32
Camera & Light 
Camera camera = new PerspectiveCamera(true); 
scene.setCamera(camera); 
PointLight point = new PointLight(Color.RED); 
AmbientLight ambient = new AmbientLight(Color.WHITE); 
scene.getRoot().getChildren().addAll(point, ambient); 
Ne pas oublier de positionner les objets 
33
34 
#gradle: 
Flexibilité
Task 
• Réponds à MON besoin 
• Dynamisme 
• Dépendances 
• Greffage au cycle de vie 
task sofshake << { 
println 'Hello Soft-Shake 2014' 
} 
tasks['sofshake'].dependsOn 'jar' 
36
Task: surcharge 
apply plugin: 'java' 
task jar(overwrite: true) << { 
// ... 
manifest { 
// ... 
} 
} 
37
Dépendances 
dependencies { 
compile (':my-project') 
runtime files('libs/a.jar', 'libs/b.jar') 
runtime "org.groovy:groovy:2.2.0@jar" 
runtime group: 'org.groovy', name: 'groovy', version: '2.2.0', ext: 
} 
38
Copy 
• Copier des fichiers facilement 
• La roue est déjà inventée 
copy { 
from file1 
from file2 
into folder 
} 
39
Gradle wrapper 
• Execution de gradle sans intallation préalable 
• Plateformes d’intégration continue friendly 
task buildGradleWrapper(type: Wrapper) { 
gradleVersion = '2.1' 
} 
40
Ouvert
Construire une application JavaFX 8 avec gradle
Construire une application JavaFX 8 avec gradle
Intégration de Ant 
• Variables 
• Tâches 
ant.importBuild "ant/project/file.xml" 
ant.conference = "Soft-Shake 2014" 
ant.location = "Genève" 
maTacheAnt.execute() 
44
45 
Tooling
SceneBuilder 46
ScenicView 47
TestFX 
public class DesktopTest extends GuiTest { 
public Parent getRootNode() { return new Desktop(); } 
@Test public void testMe() { 
// Given 
rightClick("#desktop").move("New").click("Text Document") 
.type("myTextfile.txt").push(ENTER); 
// When 
drag(".file").to("#trash-can"); 
// Then 
verifyThat("#desktop", contains(0, ".file")); 
48
49 
#Code
Codons ! 
• JavaFX 8 
• Properties 
• FXML 
• Styling 
• gradle 
• Dépendances 
• Build 
50
Ressources 51
Ressources 
• https://github.com/TestFX/TestFX 
• http://fxexperience.com/ 
• http://fxexperience.com/controlsfx/ 
• Source code of the demo: https://bitbucket.org/twasyl/weatherfx 
52
53

More Related Content

PDF
#JavaFX.forReal() - ElsassJUG
ODP
Jersey Guice AOP
PDF
Server1
PDF
LISA QooxdooTutorial Slides
DOCX
201913046 wahyu septiansyah network programing
PDF
GeeCON 2017 - TestContainers. Integration testing without the hassle
RTF
Easy Button
PDF
Software Testing - Invited Lecture at UNSW Sydney
#JavaFX.forReal() - ElsassJUG
Jersey Guice AOP
Server1
LISA QooxdooTutorial Slides
201913046 wahyu septiansyah network programing
GeeCON 2017 - TestContainers. Integration testing without the hassle
Easy Button
Software Testing - Invited Lecture at UNSW Sydney

What's hot (20)

PDF
JJUG CCC 2011 Spring
PDF
The Ring programming language version 1.10 book - Part 94 of 212
PDF
Riga DevDays 2017 - The hitchhiker’s guide to Java class reloading
PDF
Java Bytecode for Discriminating Developers - JavaZone 2011
PDF
Redux for ReactJS Programmers
PDF
JEEConf 2017 - The hitchhiker’s guide to Java class reloading
PDF
Concurrency Concepts in Java
PPTX
Unit testing without Robolectric, Droidcon Berlin 2016
PDF
Слава Бобик «NancyFx для самых маленьких»
PDF
The Ring programming language version 1.6 book - Part 184 of 189
PDF
Testing with Node.js
PPTX
Demystifying dependency Injection: Dagger and Toothpick
PDF
droidparts
PDF
Amazon Cognito使って認証したい?それならSpring Security使いましょう!
PPT
2012 JDays Bad Tests Good Tests
PDF
The Ring programming language version 1.5.2 book - Part 76 of 181
ZIP
Cleanup and new optimizations in WPython 1.1
DOCX
Exercícios Netbeans - Vera Cymbron
PDF
Java Performance Puzzlers
PDF
Csw2016 gawlik bypassing_differentdefenseschemes
JJUG CCC 2011 Spring
The Ring programming language version 1.10 book - Part 94 of 212
Riga DevDays 2017 - The hitchhiker’s guide to Java class reloading
Java Bytecode for Discriminating Developers - JavaZone 2011
Redux for ReactJS Programmers
JEEConf 2017 - The hitchhiker’s guide to Java class reloading
Concurrency Concepts in Java
Unit testing without Robolectric, Droidcon Berlin 2016
Слава Бобик «NancyFx для самых маленьких»
The Ring programming language version 1.6 book - Part 184 of 189
Testing with Node.js
Demystifying dependency Injection: Dagger and Toothpick
droidparts
Amazon Cognito使って認証したい?それならSpring Security使いましょう!
2012 JDays Bad Tests Good Tests
The Ring programming language version 1.5.2 book - Part 76 of 181
Cleanup and new optimizations in WPython 1.1
Exercícios Netbeans - Vera Cymbron
Java Performance Puzzlers
Csw2016 gawlik bypassing_differentdefenseschemes
Ad

Similar to Construire une application JavaFX 8 avec gradle (20)

PDF
#JavaFX.forReal()
PPTX
JavaFX 2.0 With Alternative Languages [Portuguese]
PPTX
JavaFX 2.0 and Alternative Languages
PPTX
JavaFX 2.0 With Alternative Languages - Groovy, Clojure, Scala, Fantom, and V...
PDF
JavaFXで開く新世代GUI
PPTX
Java Core | JavaFX 2.0: Great User Interfaces in Java | Simon Ritter
PPTX
JavaFX 2.0 With Alternative Languages - Groovy, Clojure, Scala, Fantom, and V...
PDF
Hacking JavaFX with Groovy, Clojure, Scala, and Visage: Stephen Chin
PDF
JavaFX Your Way: Building JavaFX Applications with Alternative Languages
PDF
JCD 2012 JavaFX 2
PDF
JDD 2013 JavaFX
PPTX
JavaFX Your Way - Devoxx Version
PDF
Effective JavaFX architecture with FxObjects
PDF
Don't panic in Fortaleza - ScalaFX
PDF
Moving to the Client - JavaFX and HTML5
ODP
Java Fx Overview Tech Tour
PDF
JavaFX for Business Application Developers
PDF
The JavaFX Ecosystem
PPT
Unit 1 informatica en ingles
PPT
Unit i informatica en ingles
#JavaFX.forReal()
JavaFX 2.0 With Alternative Languages [Portuguese]
JavaFX 2.0 and Alternative Languages
JavaFX 2.0 With Alternative Languages - Groovy, Clojure, Scala, Fantom, and V...
JavaFXで開く新世代GUI
Java Core | JavaFX 2.0: Great User Interfaces in Java | Simon Ritter
JavaFX 2.0 With Alternative Languages - Groovy, Clojure, Scala, Fantom, and V...
Hacking JavaFX with Groovy, Clojure, Scala, and Visage: Stephen Chin
JavaFX Your Way: Building JavaFX Applications with Alternative Languages
JCD 2012 JavaFX 2
JDD 2013 JavaFX
JavaFX Your Way - Devoxx Version
Effective JavaFX architecture with FxObjects
Don't panic in Fortaleza - ScalaFX
Moving to the Client - JavaFX and HTML5
Java Fx Overview Tech Tour
JavaFX for Business Application Developers
The JavaFX Ecosystem
Unit 1 informatica en ingles
Unit i informatica en ingles
Ad

More from Thierry Wasylczenko (6)

PDF
Du développement à la livraison avec JavaFX et le JDK9
PDF
JavaFX et le JDK9
PDF
#Polyglottisme, une autre manière de développer une application
PDF
Java goes wild, lesson 1
PDF
JavaFX, because you're worth it
PDF
Introduction to JavaFX 2
Du développement à la livraison avec JavaFX et le JDK9
JavaFX et le JDK9
#Polyglottisme, une autre manière de développer une application
Java goes wild, lesson 1
JavaFX, because you're worth it
Introduction to JavaFX 2

Recently uploaded (20)

PDF
BRKDCN-2613.pdf Cisco AI DC NVIDIA presentation
PPTX
Practice Questions on recent development part 1.pptx
PPTX
Ship’s Structural Components.pptx 7.7 Mb
PDF
Principles of Food Science and Nutritions
PPTX
TE-AI-Unit VI notes using planning model
PPTX
Fluid Mechanics, Module 3: Basics of Fluid Mechanics
PPTX
KTU 2019 -S7-MCN 401 MODULE 2-VINAY.pptx
PPTX
Unit 5 BSP.pptxytrrftyyydfyujfttyczcgvcd
PPT
Chapter 6 Design in software Engineeing.ppt
PDF
algorithms-16-00088-v2hghjjnjnhhhnnjhj.pdf
PDF
Introduction to Data Science: data science process
PDF
B.Tech (Electrical Engineering ) 2024 syllabus.pdf
PPTX
MET 305 MODULE 1 KTU 2019 SCHEME 25.pptx
PPTX
ANIMAL INTERVENTION WARNING SYSTEM (4).pptx
PPT
High Data Link Control Protocol in Data Link Layer
PPTX
Simulation of electric circuit laws using tinkercad.pptx
PDF
6th International Conference on Artificial Intelligence and Machine Learning ...
PPTX
Lesson 3_Tessellation.pptx finite Mathematics
PDF
Top 10 read articles In Managing Information Technology.pdf
PDF
Structs to JSON How Go Powers REST APIs.pdf
BRKDCN-2613.pdf Cisco AI DC NVIDIA presentation
Practice Questions on recent development part 1.pptx
Ship’s Structural Components.pptx 7.7 Mb
Principles of Food Science and Nutritions
TE-AI-Unit VI notes using planning model
Fluid Mechanics, Module 3: Basics of Fluid Mechanics
KTU 2019 -S7-MCN 401 MODULE 2-VINAY.pptx
Unit 5 BSP.pptxytrrftyyydfyujfttyczcgvcd
Chapter 6 Design in software Engineeing.ppt
algorithms-16-00088-v2hghjjnjnhhhnnjhj.pdf
Introduction to Data Science: data science process
B.Tech (Electrical Engineering ) 2024 syllabus.pdf
MET 305 MODULE 1 KTU 2019 SCHEME 25.pptx
ANIMAL INTERVENTION WARNING SYSTEM (4).pptx
High Data Link Control Protocol in Data Link Layer
Simulation of electric circuit laws using tinkercad.pptx
6th International Conference on Artificial Intelligence and Machine Learning ...
Lesson 3_Tessellation.pptx finite Mathematics
Top 10 read articles In Managing Information Technology.pdf
Structs to JSON How Go Powers REST APIs.pdf

Construire une application JavaFX 8 avec gradle

  • 1. Construire une application JavaFX 8 avec gradle
  • 2. Construire une application JavaFX 8 avec gradle Thierry Wasylczenko @twasyl La session trendy
  • 3. 3
  • 4. Ce dont on va parler • JavaFX 8 • gradle • Tooling • De code 4
  • 8. Les types 8 IntegerProperty intP = new SimpleIntegerProperty(); DoubleProperty doubleP = new SimpleDoubleProperty(); // ... BooleanProperty booleanP = new SimpleBooleanProperty(); StringProperty stringP = new SimpleStringProperty(); ObjectProperty<SoftShake> objectP = new SimpleObjectProperty();
  • 9. Le binding IntegerProperty intP1 = new SimpleIntegerProperty(); IntegerProperty intP2 = new SimpleIntegerProperty(); intP1.bind(intP2); intP2.set(10); System.out.println("Et P1? " + intP1.get()); 9
  • 10. Le binding IntegerProperty intP1 = new SimpleIntegerProperty(); IntegerProperty intP2 = new SimpleIntegerProperty(); intP1.bindBidirectional(intP2); intP2.set(10); System.out.println("Et P1? " + intP1.get()); intP1.set(20); System.out.println("Et P2? " + intP2.get()); 10
  • 11. Les événements IntegerProperty intP1 = new SimpleIntegerProperty(); intP1.addListener((valueInt, oldInt, newInt) -> { System.out.println("Change"); }); intP1.set(10); 11
  • 12. POJO 2.0 public class Conference { private StringProperty name = new SimpleStringProperty(); 12 public StringProperty nameProperty() { return this.name; } public String getName() { return this.name.get(); } public void setName(String name) { this.name.set(name); } } final Conference softShake = new Conference(); tf.textProperty().bindBidirectional(softShake.nameProperty());
  • 14. POJO 1.5 public class Conference { private PropertyChangeStatus pcs = new PropertyChangeStatus(this); private String name; public void addPropertyChangeListener(PropertyChangeListener listener) this.pcs.addPropertyChangeListener(listener); } public void removePropertyChangeListener(PropertyChangeListener listener) this.pcs.removePropertyChangeListener(listener); 14
  • 15. POJO 1.5 final Conference softShake = new Conference(); JavaBeanStringPropertyBuilder builder = new ... JavaBeanStringProperty nameProperty = builder.bean(softShake) .name("name") .build(); nameProperty.addListener((valueName, oldIName, newName) -> { // ... }); 15
  • 16. Vues
  • 17. FXML <AnchorPane xmlns:fx="http://javafx.com/fxml" fx:controller="org.mycompany.Controller"> <stylesheets> <URL value="@/org/mycompany/css/Default.css" /> <URL value="@/org/mycompany/css/Specialized.css" /> </stylesheets> <Button fx:id="myButton" text="Button" onAction="#click" /> <TextField fx:id="myTextField" promptText="Enter something" /> </AnchorPane> 17
  • 18. Controller public class Controller implements Initializable { @FXML private Button myButton; @FXML private TextField myTextField; @FXML private void click(ActionEvent evt) { /* ... */ } @Override public void initialize(URL url, ResourceBundle resourceBundle) // ... } } 18
  • 19. CSS
  • 20. Personnaliser les composants FXML <Button style="-fx-text-fill: white" styleClass="awesome" /> Java button.setStyle("-fx-background-color: red;"); button.getStyleClass().add("awesome"); 20
  • 21. Personnaliser les composants Feuille de style .button { -fx-text-fill: white; -fx-background-color: red; } .button:hover { -fx-background-color: blue; } .awesome { -fx-background-color: gray; } #myButton { -fx-text-fill: black; } 21
  • 22. PseudoState personnalisés PseudoClass ps = PseudoClass.getPseudoClass("awesome"); myButton.pseudoClassStateChanged(ps, true); .button:awesome { -fx-text-fill: orange; } #myButton:awesome { -fx-text-fill: green; } 22
  • 24. JS <> JFX webview.getEngine().getLoadWorker().stateProperty().addListener((observableValue, if (newState == Worker.State.SUCCEEDED) { JSObject window = (JSObject) webview.getEngine() .executeScript("window"); window.setMember("javaObj",this); } }); 24
  • 25. JS <> JFX Java WebEngine engine = webview.getEngine(); String result = engine.executeScript("return 'Hello';"); JavaScript javaObj.myWonderfulMethod("Hello"); 25
  • 26. WebSocket window.onload = function() { socket = new WebSocket("ws://mycompany.com/ws"); socket.onopen = function(event) { /* ... */ }; socket.onclose = function(event) { /* ... */ }; socket.onmessage = function(event) { /* ... */ }; }; 26
  • 28. Drag'n'drop obj.setOnDragDetected(event -> { /* ... */ }); obj.setOnDragOver(event -> { /* ... */ }); obj.setOnDragEntered(event -> { /* ... */ }); obj.setOnDragExited(event -> { /* ... */ }); obj.setOnDragDropped(event -> { /* ... */ }); obj.setOnDragDone(event -> { /* ... */ }); 28
  • 30. Print Chaque Node PrinterJob job = PrinterJob.createPrinterJob(); job.printPage(myTextField); Une page web WebView view = new WebView(); PrinterJob job = PrinterJob.createPrinterJob(); view.getEngine().print(job); 30
  • 31. 3D
  • 32. Objets prédéfinis Box b = new Box(width, height, depth); Cylinder c = new Cylinder(radius, height); Sphere s = new Sphere(radius); TriangleMesh tm = new TriangleMesh(); Les plus basiques 32
  • 33. Camera & Light Camera camera = new PerspectiveCamera(true); scene.setCamera(camera); PointLight point = new PointLight(Color.RED); AmbientLight ambient = new AmbientLight(Color.WHITE); scene.getRoot().getChildren().addAll(point, ambient); Ne pas oublier de positionner les objets 33
  • 36. Task • Réponds à MON besoin • Dynamisme • Dépendances • Greffage au cycle de vie task sofshake << { println 'Hello Soft-Shake 2014' } tasks['sofshake'].dependsOn 'jar' 36
  • 37. Task: surcharge apply plugin: 'java' task jar(overwrite: true) << { // ... manifest { // ... } } 37
  • 38. Dépendances dependencies { compile (':my-project') runtime files('libs/a.jar', 'libs/b.jar') runtime "org.groovy:groovy:2.2.0@jar" runtime group: 'org.groovy', name: 'groovy', version: '2.2.0', ext: } 38
  • 39. Copy • Copier des fichiers facilement • La roue est déjà inventée copy { from file1 from file2 into folder } 39
  • 40. Gradle wrapper • Execution de gradle sans intallation préalable • Plateformes d’intégration continue friendly task buildGradleWrapper(type: Wrapper) { gradleVersion = '2.1' } 40
  • 44. Intégration de Ant • Variables • Tâches ant.importBuild "ant/project/file.xml" ant.conference = "Soft-Shake 2014" ant.location = "Genève" maTacheAnt.execute() 44
  • 48. TestFX public class DesktopTest extends GuiTest { public Parent getRootNode() { return new Desktop(); } @Test public void testMe() { // Given rightClick("#desktop").move("New").click("Text Document") .type("myTextfile.txt").push(ENTER); // When drag(".file").to("#trash-can"); // Then verifyThat("#desktop", contains(0, ".file")); 48
  • 50. Codons ! • JavaFX 8 • Properties • FXML • Styling • gradle • Dépendances • Build 50
  • 52. Ressources • https://github.com/TestFX/TestFX • http://fxexperience.com/ • http://fxexperience.com/controlsfx/ • Source code of the demo: https://bitbucket.org/twasyl/weatherfx 52
  • 53. 53