This is something I like that I have seen in a few different softwares. I don't know where it originates from or what it really is called but here is an example of the pane system in Visual Studio.
Note how I can easily attach the pane anywhere with ease. Is such a thing possible with Javafx?
I recognize this question is old but others may be interested to know. I created a lightweight docking library for JavaFX for both proprietary and non-proprietary uses under the LGPL license.
https://github.com/RobertBColton/DockFX
There is no built-in docking framework for JavaFX 8.
There are some 3rd party solutions such as Drombler FX. I haven't used any of them.
A simple home-built system to dock and undock panels is pretty easy to create, but a comprehensive system would be quite difficult. The following code is adapted from zonski's answer to a docking framework discussion is in the Oracle JavaFX forum threads.
import javafx.application.Application;
import javafx.geometry.Orientation;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.*;
public class SimpleDocking extends Application {
public void start(final Stage stage) throws Exception {
final SplitPane rootPane = new SplitPane();
rootPane.setOrientation(Orientation.VERTICAL);
final FlowPane dockedArea = new FlowPane();
dockedArea.getChildren().add(new Label("Some docked content"));
final FlowPane centerArea = new FlowPane();
final Button undockButton = new Button("Undock");
centerArea.getChildren().add(undockButton);
rootPane.getItems().addAll(centerArea, dockedArea);
stage.setScene(new Scene(rootPane, 300, 300));
stage.show();
final Dialog dialog = new Dialog(stage);
undockButton.disableProperty().bind(dialog.showingProperty());
undockButton.setOnAction(actionEvent -> {
rootPane.getItems().remove(dockedArea);
dialog.setOnHidden(windowEvent -> {
rootPane.getItems().add(dockedArea);
});
dialog.setContent(dockedArea);
dialog.show(stage);
});
}
private class Dialog extends Popup {
private BorderPane root;
private Dialog(Window parent) {
root = new BorderPane();
root.setPrefSize(200, 200);
root.setStyle("-fx-border-width: 1; -fx-border-color: gray");
root.setTop(buildTitleBar());
setX(parent.getX() + 50);
setY(parent.getY() + 50);
getContent().add(root);
}
public void setContent(Node content) {
root.setCenter(content);
}
private Node buildTitleBar() {
BorderPane pane = new BorderPane();
pane.setStyle("-fx-background-color: burlywood; -fx-padding: 5");
final Delta dragDelta = new Delta();
pane.setOnMousePressed(mouseEvent -> {
dragDelta.x = getX() - mouseEvent.getScreenX();
dragDelta.y = getY() - mouseEvent.getScreenY();
});
pane.setOnMouseDragged(mouseEvent -> {
setX(mouseEvent.getScreenX() + dragDelta.x);
setY(mouseEvent.getScreenY() + dragDelta.y);
});
Label title = new Label("My Dialog");
title.setStyle("-fx-text-fill: midnightblue;");
pane.setLeft(title);
Button closeButton = new Button("X");
closeButton.setOnAction(actionEvent -> hide());
pane.setRight(closeButton);
return pane;
}
}
private static class Delta {
double x, y;
}
public static void main(String[] args) throws Exception {
launch(args);
}
}
If you have extensive need for such a framework, you might want to look into the NetBeans platform, which is a Swing based framework into which you can embed JavaFX.
A simple docking framework for JavaFX:
https://github.com/andy-goryachev/FxDock
public void start(Stage s) throws Exception
{
// plug in custom windows and dockable panes.
FxDockFramework.setGenerator(new DemoPanes());
// load saved layout
int ct = FxDockFramework.loadLayout();
if(ct == 0)
{
// when no saved layout exists, open the first window
DemoWindow.openBrowser("https://github.com/andy-goryachev/FxDock");
}
}
As the previous answer says, JavaFX does not have built-in support for dockable tabs. There is an OpenJDK issue requesting support for draggable and dockable tabs.
A recent third-party solution that may be worth looking into is DockFX which is in active development at the time of writing (September 2015)
Related
I have some JavaFX Popup in my application. And when any of these popups is foucsed, I need it bring on top of every other popups regardless of it's index in Window.getWindows().
I've tried to call method like toFront but it's not in Popup class. I've also tried to change index of focused Popup in Window.getWindows() but that also didn't worked because I don't know how to interchange index of two elements in a ObservableList.
e.g.
Let's say I have two Popup called p1 and p2 and in each I have nodes n1 and n2 respectively which are used to move these popup, So whenever n1 is dragged p1 should come on top and when n2 is dragged p2 should come on top.
Here is my minimal example:
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.stage.Popup;
import javafx.stage.Stage;
public class Example extends Application{
public static void main(String... arguments){
launch(arguments);
}
public void applyTo(Pane node, Popup parent){
final double[] dragDelta = new double[2];
node.setOnMousePressed(e -> {
dragDelta[0] = parent.getX() - e.getScreenX();
dragDelta[1] = parent.getY() - e.getScreenY();
//code to bring parent Popup to front
});
node.setOnMouseDragged(e -> {
parent.setX(e.getScreenX() + dragDelta[0]);
parent.setY(e.getScreenY() + dragDelta[1]);
});
}
#Override
public void start(Stage primaryStage) throws Exception{
Button b1 = new Button("Open p1");
Button b2 = new Button("Open p2");
HBox n1 = new HBox(new Label("This is p1"));
HBox n2 = new HBox(new Label("This is p2"));
n1.setMinSize(200, 120);
n2.setMinSize(200, 120);
n1.setStyle("-fx-background-color: blue; -fx-background-radius: 4px;");
n2.setStyle("-fx-background-color: red; -fx-background-radius: 4px;");
n1.setAlignment(Pos.CENTER);
n2.setAlignment(Pos.CENTER);
Popup p1 = new Popup();
Popup p2 = new Popup();
p1.getContent().add(n1);
p2.getContent().add(n2);
applyTo(n1, p1);
applyTo(n2, p2);
b1.setOnAction(event -> {
if(!p1.isShowing()) p1.show(primaryStage);
else p1.hide();
});
b2.setOnAction(event -> {
if(!p2.isShowing()) p2.show(primaryStage);
else p2.hide();
});
HBox root = new HBox(10, b1, b2);
root.setAlignment(Pos.CENTER);
primaryStage.setScene(new Scene(root, 500, 200));
primaryStage.show();
}
}
So what is the solution for this problem?
For some reason I don't understand, toFront/back is only implemented on Stage, not on its parent classes even though the actual collaborator that manages the stacking is already available in Window:
The implementation in Stage:
/**
* Bring the {#code Window} to the foreground. If the {#code Window} is
* already in the foreground there is no visible difference.
*/
public void toFront() {
if (getPeer() != null) {
getPeer().toFront();
}
}
getPeer() is a package-private method in Window that returns the internal class TKStage. So if you are allowed to go dirty (because accessing an internal class and having to access via reflection - all with the usual loud "Beware"!) would be:
protected void toFront(Popup popup) {
// use your favorite utility method to invoke a method
TKStage peer = (TKStage) FXUtils.invokeGetMethodValue(Window.class, popup, "getPeer");
if (peer != null) {
peer.toFront();
}
}
Requires to export/open not-exported packages in javafx.graphics - compiler and runtime errors will guide you (my context is heavily tweaked anyway, so don't know exactly which are added by this)
Here is the solution with stages it is the only work around I have found at all even though you hate the idea of having multiple stages if you want the functionality this is it. If you decide to stick with leaving them in the background thats cool too. An idea to solve your too may stages dilemma is to use a queue of stages remove when in use and if all are shown add a new one when a stage is hidden send to the end of the queue
public class Example extends Application {
public void applyTo(Pane node, Stage parent, Stage primaryStage){
final double[] dragDelta = new double[2];
node.setOnMousePressed(e -> {
dragDelta[0] = parent.getX() - e.getScreenX();
dragDelta[1] = parent.getY() - e.getScreenY();
//code to bring parent Popup to front
});
node.setOnMouseDragged(e -> {
parent.setX(e.getScreenX() + dragDelta[0]);
parent.setY(e.getScreenY() + dragDelta[1]);
primaryStage.requestFocus();
});
node.setOnMouseReleased(event -> {
primaryStage.requestFocus();
});
}
#Override
public void start(Stage primaryStage) throws Exception{
Button b1 = new Button("Open p1");
Button b2 = new Button("Open p2");
HBox n1 = new HBox(new Label("This is p1"));
HBox n2 = new HBox(new Label("This is p2"));
n1.setMinSize(200, 120);
n2.setMinSize(200, 120);
n1.setStyle("-fx-background-color: blue; -fx-background-radius: 4px;");
n2.setStyle("-fx-background-color: red; -fx-background-radius: 4px;");
n1.setAlignment(Pos.CENTER);
n2.setAlignment(Pos.CENTER);
Stage p1 = new Stage(StageStyle.UNDECORATED);
Stage p2 = new Stage(StageStyle.UNDECORATED);
p1.setAlwaysOnTop(true);
p2.setAlwaysOnTop(true);
p1.setScene(new Scene(n1));
p2.setScene(new Scene(n2));
applyTo(n1, p1, primaryStage);
applyTo(n2, p2, primaryStage);
b1.setOnAction(event -> {
if(!p1.isShowing()) {
p1.show();
primaryStage.requestFocus();
}
else
p1.hide();
});
b2.setOnAction(event -> {
if(!p2.isShowing()) {
p2.show();
primaryStage.requestFocus();
}
else
p2.hide();
});
HBox root = new HBox(10, b1, b2);
root.setAlignment(Pos.CENTER);
primaryStage.setScene(new Scene(root, 500, 200));
primaryStage.show();
}
public static void main(String[] args) { launch(args); }
}
I am trying to make a program for visual analyzing Fractal sets. I choose Processing 3 as drawing library and JavaFX for the user interface. There are some screenshots of the current state:
My GUI:
there is Launcher code:
import Graphics.Canvas2D;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import processing.core.PGraphics;
import java.io.IOException;
public class Launcher extends Application {
private static Stage primaryStage;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
Parent root = loadFXML("MainUI.fxml");
Scene scene = new Scene(root, 500, 400);
primaryStage.setTitle("Fractal Analyzer");
primaryStage.setScene(scene);
primaryStage.show();
primaryStage.setMaximized(true);
Launcher.primaryStage = primaryStage;
}
#Override
public void init() {
}
#Override
public void stop() {
System.exit(0);
}
public static Stage getPrimaryStage() {
return primaryStage;
}
public void setCanvas(Canvas2D canvas){
}
private Parent loadFXML(String path) {
try {
return FXMLLoader.load(getClass().getResource(path));
} catch (IOException e) {
e.printStackTrace();
}
System.exit(1);
return null;
}
}
Testing fractal PAplet:
There is a code of this PAplet:
package Fractal;
import processing.core.PApplet;
public class SirpenskiTriangle extends PApplet {
public static void main(String[] args) {
PApplet.main("Fractal.SirpenskiTriangle");
}
public void settings() {
size(640, 640);
smooth();
if (frame != null) {
frame.setResizable(true);
}
}
public void draw() {
drawTriangle(new Position(300, 20), new Position(620, 620), new Position(20, 620), 0);
noLoop();
scale(10f);
}
public void setup(){}
public void drawTriangle(Position top, Position right, Position left, int depth) {
if (depth > 10) return;
line(top.x, top.y, right.x, right.y);
line(right.x, right.y, left.x, left.y);
line(left.x, left.y, top.x, top.y);
drawTriangle(top, top.middleWith(right), top.middleWith(left), depth + 1);
drawTriangle(top.middleWith(left), left.middleWith(right), left, depth + 1);
drawTriangle(top.middleWith(right), right, left.middleWith(right), depth + 1);
}
class Position {
final float x;
final float y;
Position(float x, float y) {
this.x = x;
this.y = y;
}
Position middleWith(Position other) {
return new Position((x + other.x) / 2, (y + other.y) / 2);
}
}
}
Is there any way to put processing PAplet into JavaFX scene like canvas or something similar?
I hope it can work like this, but this code is invalid:
I have devised two approaches: in the first, we bypass Processing's JavaFX stage creation and point Processing to draw into a JavaFX stage loaded from an FXML file; in the second, we replace Processing's default JavaFX scene with one loaded from an FXML file during runtime.
1. Launching from an FXML
With the first approach we launch the application like we would a JavaFX app (using Application.launch(Launcher.class);), completely bypassing Processing's JavaFX stage creation code.
You'll have to download a slightly modified core.jar for this approach to work, where I've changed the visibility of a few members of the PSurfaceFX and PGraphicsFX2D classes from Protected to Public. The changes allow us to launch JavaFX from our own ... extends Application class, while maintaining access to the members that Processing needs to set during the launch to function.
Processing 3 crashes in FX2D mode when the JDK in use is above Java 8, so I've also made a working version for 8+, since the FXML files usually need at least Java 9 to work.
Download core.jar (Java 8 & below)
Download core.jar (Above Java 8)
This is the FXML file I am working with in this example:
With the modified core.jar added to your project's classpath, override initSurface() in your PApplet class with the following snippet. With this code, we bypass the PApplet's call to initFrame() - this is where processing creates its own JavaFX stage, which we do not want it to do.
#Override
protected PSurface initSurface() {
g = createPrimaryGraphics();
PSurface genericSurface = g.createSurface();
PSurfaceFX fxSurface = (PSurfaceFX) genericSurface;
fxSurface.sketch = this;
Launcher.surface = fxSurface;
new Thread(new Runnable() {
public void run() {
Application.launch(Launcher.class);
}
}).start();
while (fxSurface.stage == null) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
}
}
this.surface = fxSurface;
return fxSurface;
}
Set the PApplet's renderering mode to FX2D like so:
#Override
public void settings() {
size(0, 0, FX2D);
}
Put the following, or similar, in your Launcher class. In this example, I have manually found the Node that I want to add the canvas object into. There are better, more programmatic, ways of doing this (such as .lookup() using the fx:id of the desired node -- this can be defined in the FXML file). I have also bound the dimensions of the canvas to those of its parent, so when the divisor separating the Master and View panes is dragged, the Processing canvas resizes accordingly.
public class Launcher extends Application {
public static PSurfaceFX surface;
#Override
public void start(Stage primaryStage) throws Exception {
Canvas canvas = (Canvas) surface.getNative(); // boilerplate
GraphicsContext graphicsContext = canvas.getGraphicsContext2D(); // boilerplate
surface.fx.context = graphicsContext; // boilerplate
primaryStage.setTitle("FXML/Processing");
VBox root = FXMLLoader.load(new File("c:/Users/Mike/desktop/test.fxml").toURI().toURL());
SplitPane pane = (SplitPane) root.getChildren().get(1); // Manually get the item I want to add canvas to
AnchorPane pane2 = (AnchorPane) pane.getItems().get(0); // Manually get the item I want to add canvas to
pane2.getChildren().add(canvas); // Manually get the item I want to add canvas to
canvas.widthProperty().bind(pane2.widthProperty());
canvas.heightProperty().bind(pane2.heightProperty());
Scene scene = new Scene(root, 800, 800);
primaryStage.setScene(scene);
primaryStage.show();
surface.stage = primaryStage; // boilerplate
}
}
This is the result:
Also see this Github project -- a basic project showing how a Processing sketch and a FXML JavaFX stage may be integrated using this first approach, but includes a JavaFX Controller to populate #FXMLannotated fields (providing an easy way to first get, and then reference, JavaFX objects in code).
2. Launching, then loading a FXML
This approach works with vanilla Processing. Here, we launch Processing like normal and then replace the default scene with new scene loaded from an FXML file during runtime. This is a simpler approach (and doesn't require using a modified .jar!) but will make JavaFX/Processing interoperability more difficult because we can't use a JavaFX Controller to get fields via FXML injection.
Example PDE code:
import java.util.Map;
import java.nio.file.Paths;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.canvas.Canvas;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
import processing.javafx.PSurfaceFX;
public void setup() {
size(800, 800, FX2D);
strokeWeight(3);
}
protected PSurface initSurface() {
surface = (PSurfaceFX) super.initSurface();
final Canvas canvas = (Canvas) surface.getNative();
final Scene oldScene = canvas.getScene();
final Stage stage = (Stage) oldScene.getWindow();
try {
FXMLLoader loader = new FXMLLoader(Paths.get("C:\\path--to--fxml\\stage.fxml").toUri().toURL()); // abs path to fxml file
final Parent sceneFromFXML = loader.load();
final Map<String, Object> namespace = loader.getNamespace();
final Scene newScene = new Scene(sceneFromFXML, stage.getWidth(), stage.getHeight(), false,
SceneAntialiasing.BALANCED);
final AnchorPane pane = (AnchorPane) namespace.get("anchorPane"); // get element by fx:id
pane.getChildren().add(canvas); // processing to stackPane
canvas.widthProperty().bind(pane.widthProperty()); // bind canvas dimensions to pane
canvas.heightProperty().bind(pane.heightProperty()); // bind canvas dimensions to pane
Platform.runLater(new Runnable() {
#Override
public void run() {
stage.setScene(newScene);
}
}
);
}
catch (IOException e) {
e.printStackTrace();
}
return surface;
}
public void draw() {
background(125, 125, 98);
ellipse(200, 200, 200, 200);
line(0, 0, width, height);
line(width, 0, 0, height);
}
Result:
…using this FXML file:
To make it work, you have to launch your Processing sketch, not the JavaFX Application.
Simply do
PApplet.main(Launcher.class.getName());
Also thanks so much for your help! I had NO idea how I should use the JavaFX stuff that comes with Processing!
Okay, this is my code, that works! I copied everything and changed the names.
!!!I have not tested this modified code, so don't copy paste everything!!!
The raw principle should definetly work though.
If you still have issues or questions, just comment.
Main
public class Main {
public static void main(String[] args) {
// Your code starts here, and runs Processing.
// This is also, how you normally start Processing sketches.
PApplet.main(Sketch.class.getName());
}
}
Sketch
public class Sketch extends PApplet{
#Override
public void settings() {
size(200, 200, FX2D); // Size doesn't really matter
}
#Override
public void setup() {
}
#Override
public void draw() {
}
// Processing uses this function to determine,
// how to display everything, how to open the canvas...
// We override the code, that would normally open a window with the normal Processing stuff,
// to open start new JavaFX application in a new Thread.
// micycle's code
#Override
protected PSurface initSurface() {
g = createPrimaryGraphics();
PSurface genericSurface = g.createSurface();
PSurfaceFX fxSurface = (PSurfaceFX) genericSurface;
fxSurface.sketch = this;
// Because the JavaFX App is being launched by reflection,
// we can't pass variables to it via constructor, so
// we have to access it in static context.
// Here, we give JavaFX the surface.
ExampleApp.surface = fxSurface;
// New thread started, so JavaFX and Processing don't interrupt each other.
new Thread(new Runnable() {
public void run() {
// JavaFX way of launching a new Application
Application.launch(ExampleApp.class);
}
}).start();
while (fxSurface.stage == null) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
}
}
this.surface = fxSurface;
return fxSurface;
}
}
ExampleApp
public class ExampleApp extends Application {
public Canvas canvas; // The Canvas you will be drawing to
public static PSurfaceFX surface; // The Processing surface
// JavaFX started, this method is being run to set everything up.
#Override
public void start(Stage primaryStage) {
// This sets up the canvas, and the drawing region.
canvas = (Canvas) surface.getNative();
surface.fx.context = canvas.getGraphicsContext2D();
surface.stage = primaryStage;
// I'm just loading my FXML file. You can do all JavaFX stuff via code, if you want
try {
// !!My root Container is a BorderPane!!
BorderPane root = FXMLLoader.load(getClass().getClassLoader().getResource("application.fxml"));
} catch(IOException e) {
e.printStackTrace();
}
// Getting the Anchor pane, that is in the center of my BorderPane
AnchorPane pane = (AnchorPane) root.getCenter();
// The Anchor pane is being used, so the canvas can fill the parent (Center)
// Canvases don't have a property to fill it's parent, like most Containers do (Because it isn't a container)
canvas.widthProperty().bind(pane.widthProperty());
canvas.heightProperty().bind(pane.heightProperty());
// Adding the canvas to your App
root.getChildren().add(canvas);
// Launching the Stage
primaryStage.setTitle("Example App");
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
Okay, since last time , I changed some elements.
The canvas's parent is now just a Pane instead of an AnchorPane.
The FXML won't help you much... It is just a BorderPane with another Pane in it, but alright...
<center>
<VBox prefHeight="200.0" prefWidth="100.0" BorderPane.alignment="CENTER">
<children>
<Pane maxHeight="1.7976931348623157E308" VBox.vgrow="ALWAYS" />
</children>
</VBox>
So, what I'm doing is taking the Canvas element, Processing creates and just adding it to the Pane.
My application allows users to use custom CSS themes to style the interface. I have several pre-built "themes" available to choose from that are very simple, with only 3 properties.
Sample CSS:
.root{
-fx-background: #325c81;
-fx-default-button: #77a3ca;
-fx-base: #a7c4dd;
}
The application has 3 ColorPicker controls that need to allow users to select a color for each of those properties and save back to the CSS file.
I have no problem with actually writing the CSS file, but I cannot find a way to parse the .css file in order to set the values of the ColorPicker controls with the values from the .css file.
Basic Program Flow
1) User selects a premade theme from ComboBox:
cboPresetTheme.valueProperty().addListener((observable, priorTheme, newTheme) -> {
Utility.applyTheme(cboPresetTheme.getScene(), newTheme);
});
2) The associated .css file is loaded and applied to the current Scene:
public static void applyTheme(Scene scene, Theme theme) {
scene.getStylesheets().clear();
File css = new File("themes/" + theme.getFileName());
File fontFile = new File("themes/Font.css");
scene.getStylesheets().addAll(
css.toURI().toString(),
fontFile.toURI().toString());
}
3) The 3 ColorPicker controls are updated with the values from the applied StyleSheet:
cpBackground.setValue(Color.valueOf(cssFileBackground));
cpBase.setValue(Color.valueOf(cssFileBase));
cpDefaultButton.setValue(Color.valueOf(cssFileDefaultButton));
While I have no problem with steps 1 & 2, I do not know how to process step 3.
I have looked at other CSS Parser libraries (thank you, Google) but they seem more geared toward stand CSS and don't support FX properties. The StackExchange question edit or parse FX-CSS file programmatically appears to be asking the same question but it was never successfully answered.
One answer suggests using CSS Parser to accomplish this, but as there is little to know documentation (and what is there is beyond my current comprehension level), I don't know where to begin.
I understand there may not be a standard API currently available to accomplish this, but I was hoping there may be a simple library or solution out there that I have been unable to find.
There are several ways you can tackle the conversion of a CSS declaration into a Color.
Style an auxiliar node
This is quite simple, but effective: The idea is that you could just style the background color of a node with the same css, and then set the colorPicker value with that color.
The only thing you need to take into account in this case is that the node is styled only when is added to a scene.
So you have to add the node to the scene. Adding a node with 0x0 size won't cause any issue, but maybe you don't want it to be there, so you can use an auxiliar scene.
public class CSSParsingApp extends Application {
#Override
public void start(Stage primaryStage) {
ColorPicker cpBackground = new ColorPicker(retrieveColor("value1"));
ColorPicker cpBase = new ColorPicker(retrieveColor("value2"));
ColorPicker cpDefaultButton = new ColorPicker(retrieveColor("value3"));
VBox root = new VBox(10, cpBackground, cpDefaultButton, cpBase);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 300, 250);
scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
private Color retrieveColor(String value) {
Pane pane = new Pane();
pane.getStyleClass().add(value);
Scene sceneAux = new Scene(pane);
sceneAux.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
pane.applyCss();
return (Color) pane.getBackground().getFills().get(0).getFill();
}
public static void main(String[] args) {
launch(args);
}
}
where style.css is:
.root {
-fx-background: #325c81;
-fx-default-button: #77a3ca;
-fx-base: #a7c4dd;
}
.value1 {
-fx-background-color: -fx-background;
}
.value2 {
-fx-background-color: -fx-default-button;
}
.value3 {
-fx-background-color: -fx-base;
}
Use StylableProperties
A similar, more elegant solution is found here. It uses StylableProperties to create a node, that you can style with a custom -named-color property, and then adds this helper node to the main scene.
Basically it is the same idea as the one above, maybe more clean, as you don't need to modify your css file.
Using CssToColorHelper, your code will be like this:
public class CSSParsingApp extends Application {
private CssToColorHelper helper = new CssToColorHelper();
#Override
public void start(Stage primaryStage) {
ColorPicker cpBackground = new ColorPicker();
ColorPicker cpBase = new ColorPicker();
ColorPicker cpDefaultButton = new ColorPicker();
VBox root = new VBox(10, cpBackground, cpDefaultButton, cpBase, helper);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 300, 250);
scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
cpBackground.setValue(getNamedColor("-fx-background"));
cpDefaultButton.setValue(getNamedColor("-fx-default-button"));
cpBase.setValue(getNamedColor("-fx-base"));
primaryStage.setScene(scene);
primaryStage.show();
}
private Color getNamedColor(String name) {
helper.setStyle("-named-color: " + name + ";");
helper.applyCss();
return helper.getNamedColor();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
where style.css is your css file:
.root {
-fx-background: #325c81;
-fx-default-button: #77a3ca;
-fx-base: #a7c4dd;
}
Use JavaFX CSSParser
If you are looking for a CSSParser, why don't you just use the one included in JavaFX, the one you actually use to apply styling to your app?
It is under com.sun.javafx.css.parser.CSSParser, and if the answer is you don't want to use private API, the good news is that it will be public API in JavaFX 9.
With it you can parse the css file and retrieve any parsed value easily.
public class CSSParsingApp extends Application {
#Override
public void start(Stage primaryStage) {
ColorPicker cpBackground = new ColorPicker();
ColorPicker cpBase = new ColorPicker();
ColorPicker cpDefaultButton = new ColorPicker();
VBox root = new VBox(10, cpBackground, cpDefaultButton, cpBase);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 300, 250);
scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
cpBackground.setValue(parseColor("-fx-background"));
cpDefaultButton.setValue(parseColor("-fx-default-button"));
cpBase.setValue(parseColor("-fx-base"));
primaryStage.setScene(scene);
primaryStage.show();
}
private Color parseColor(String property) {
CSSParser parser = new CSSParser();
try {
Stylesheet css = parser.parse(getClass().getResource("style.css").toURI().toURL());
final Rule rootRule = css.getRules().get(0); // .root
return (Color) rootRule.getDeclarations().stream()
.filter(d -> d.getProperty().equals(property))
.findFirst()
.map(d -> ColorConverter.getInstance().convert(d.getParsedValue(), null))
.get();
} catch (URISyntaxException | IOException ex) { }
return Color.WHITE;
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
where style.css is your css file:
.root {
-fx-background: #325c81;
-fx-default-button: #77a3ca;
-fx-base: #a7c4dd;
}
I have a JavaFX application that uses a preloader. What I'd like to do is package it up as a native bundle (Mac app or Windows exe file that contains a copy of the Java JDK) so users who don't have the right version of Java on their computers can still run the app. I've followed Oracles instructions for creating native bundles and for adding preloaders. What I get is exactly what you'd expect—a native bundle that runs my program.
The problem is that the bundle completely ignores my preloader. It just runs the main program (after a long load time). I know the preloader is included because, when I run the jar file alone, it shows up.
Has anyone successfully bundled a JavaFX app with a preloader? Can you guide me through how to do so? I'm using Netbeans.
EDIT:
Here is the Preloader:
import javafx.application.Preloader;
import javafx.application.Preloader.ProgressNotification;
import javafx.application.Preloader.StateChangeNotification;
import javafx.scene.Scene;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class Splash extends Preloader {
ProgressIndicator bar;
ImageView Background;
Stage stage;
private Scene createPreloaderScene() {
bar = new ProgressIndicator();
bar.setLayoutX(380);
bar.setLayoutY(250);
bar.setPrefSize(60, 60);
Background = new ImageView("Images/Splash.png");
Background.setEffect(null);
Pane p = new Pane();
p.setStyle("-fx-background-color: transparent;");
p.getChildren().addAll(Background, bar);
Scene scene = new Scene(p, 794, 587);
scene.setFill(null);
scene.getStylesheets().add(Scrap2.class.getResource("CSS/Progress.css").toExternalForm());
bar.setId("myprogress");
return scene;
}
#Override
public void start(Stage stage) throws Exception {
this.stage = stage;
stage.setScene(createPreloaderScene());
stage.initStyle(StageStyle.TRANSPARENT);
stage.show();
}
#Override
public void handleStateChangeNotification(StateChangeNotification scn) {
if (scn.getType() == StateChangeNotification.Type.BEFORE_START) {
stage.hide();
}
}
#Override
public void handleProgressNotification(ProgressNotification pn) {
bar.setProgress(pn.getProgress());
}
#Override
public void handleApplicationNotification(PreloaderNotification arg0) {
if (arg0 instanceof ProgressNotification) {
ProgressNotification pn= (ProgressNotification) arg0;
bar.setProgress(pn.getProgress());
}
}
}
And here is the first part of my main program:
#Override
public void init(){
/*Root*/
root = new Pane();
root.setStyle("-fx-background-color: transparent;");
root.setLayoutX(150);
notifyPreloader(new Preloader.ProgressNotification(0.1));
/*Create Background*/
createBinding(stage);
createContents();
createSaveMessages();
createFlipBook();
notifyPreloader(new Preloader.ProgressNotification(0.2));
/*Add Pages*/
createOverview();
createAccounts();
notifyPreloader(new Preloader.ProgressNotification(0.3));
createCounselors();
createInsurance();
notifyPreloader(new Preloader.ProgressNotification(0.4));
createAssets();
createPapers();
notifyPreloader(new Preloader.ProgressNotification(0.5));
createLoans();
createFuneral();
notifyPreloader(new Preloader.ProgressNotification(0.6));
createWills();
addAllPages();
notifyPreloader(new Preloader.ProgressNotification(0.7));
/*Add Toolbar on top*/
createToolBar();
notifyPreloader(new Preloader.ProgressNotification(0.9));
/*Create Opening Instructions*/
opening();
/*Load Saved Data*/
load();
notifyPreloader(new Preloader.ProgressNotification(1.0));
}
#Override
public void start(Stage stage) {
/*Scene*/
scene = new Scene(root, 1200, 700);
stage.setScene(scene);
scene.setFill(null);
/*Stage*/
this.stage = stage;
stage.initStyle(StageStyle.TRANSPARENT);
stage.centerOnScreen();
stage.show();
}
This example will work with installers exe/msi/image only (have no Mac to test dmg). This step by step assumes, that you already installed the needed tools like InnoSetup, Wix Toolset, etc. It also assumes, that you have configured the tools to run with netbeans (setting paths, edit config files, etc.).
Prerequirements:
Inno Setup for .exe package, download the unicode version: http://www.jrsoftware.org/isdl.php
Wix Toolset for .msi package: http://wixtoolset.org/
Set Windows Paths for Inno Setup and Wix Toolset
Step 1:
I've made a new JavaFX Application Project in Netbeans like this:
Step 2:
Then I gave the project a name and said, that the wizard should create a preloader project with the given name too. Additionally it should create an application class in given package name.
Step 3:
After that I right clicked on the application project and select under deployment "Enable Native Packaging".
Step 4:
In step 4 I've created the code for the application. The preloader will be updated in the init() method and only there. All your work for initialization the application should go here.
JavaFXPreloaderApp.java
import javafx.application.Application;
import javafx.application.Preloader;
import javafx.event.ActionEvent;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class JavaFXPreloaderApp extends Application {
#Override
public void start(Stage primaryStage) {
Scene scene = new Scene(createContent(), 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
public Parent createContent() {
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction((ActionEvent event) -> {
System.out.println("Hello World!");
});
StackPane root = new StackPane();
root.getChildren().add(btn);
return root;
}
#Override
public void init() throws Exception {
// A time consuming task simulation
final int max = 10;
for (int i = 1; i <= max; i++) {
notifyPreloader(new Preloader.ProgressNotification(((double) i) / max));
Thread.sleep(500);
}
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Step 5:
The only missing part was the preloader code. Look for the only needed method handleApplicationNotification, all the other methods, like handleProgressNotification or handleStateChangeNotification, you can safely delete, or make them empty stubs.
JavaFXPreloader.java
import javafx.application.Preloader;
import javafx.application.Preloader.ProgressNotification;
import javafx.application.Preloader.StateChangeNotification;
import javafx.scene.Scene;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
/**
* Simple Preloader Using the ProgressBar Control
*/
public class JavaFXPreloader extends Preloader {
ProgressBar bar;
Stage stage;
private Scene createPreloaderScene() {
bar = new ProgressBar();
BorderPane p = new BorderPane();
p.setCenter(bar);
return new Scene(p, 300, 150);
}
#Override
public void start(Stage stage) throws Exception {
this.stage = stage;
stage.setScene(createPreloaderScene());
stage.show();
}
#Override
public void handleApplicationNotification(PreloaderNotification info) {
// Check if info is ProgressNotification
if (info instanceof ProgressNotification) {
// if yes, get the info and cast it
ProgressNotification pn = (ProgressNotification) info;
// update progress
bar.setProgress(pn.getProgress());
// if this was the last progress (progress reached 1), hide preloader
// this is really important, if preloader isn't hide until app loader
// reaches the start method of application and tries to open the stage of
// the main app with the show() method, it will not work.
if (pn.getProgress() == 1.0) {
stage.hide();
}
}
}
}
Step 6:
Now it was time to bundle the application to native packages (image only/exe/msi). I right clicked on the applicaton project and selected the packages to create one by one.
Step 7:
After choosen to package as image only your directory should look like this:
Step 8:
After digging deeper in your directory you should find the image:
Step 9:
A double click on the .exe file should start your application:
Remarks:
The biggest mistake you could do is, to call things in your application start methods. Normaly all have to be done in the application init method, there you load the huge files, there you will connect to the db, or there you load a huge custom layout with a lot of css or fxml files. And there is the place to say good bye to the preloader (progress = 1). Try not to do things at the preloader in your application start method. Don't think in Thread's, the preloader is there to do things before the main stage is shown, so load all in sequence.
I'm attempting to make a modal dialog similar to Swing's JOptionPane. I want to present a confirmation dialog which makes the users explicitly say "yes" before I perform some action in code.
I've shamelessly stolen this example from: https://gist.github.com/jewelsea/1887631
And modded it to be:
public class ModalConfirmDialog {
private boolean confirm = false;
private boolean isDialogOpen = false;
public boolean isOpen() {
return isDialogOpen;
}
public boolean getConfirmation() {
return confirm;
}
public void showConfirmationDialog(final Stage primaryStage) {
// initialize the confirmation dialog
final Stage dialog = new Stage(StageStyle.TRANSPARENT);
dialog.initModality(Modality.WINDOW_MODAL);
dialog.initOwner(primaryStage);
dialog.setScene(
new Scene(
HBoxBuilder.create().styleClass("modal-dialog").children(
LabelBuilder.create().text("Confirm Action?").build(),
ButtonBuilder.create().text("Yes").defaultButton(true).onAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent actionEvent) {
// take action and close the dialog.
primaryStage.getScene().getRoot().setEffect(null);
dialog.close();
confirm = true;
isDialogOpen = false;
}
}).build(),
ButtonBuilder.create().text("No").cancelButton(true).onAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent actionEvent) {
// abort action and close the dialog.
primaryStage.getScene().getRoot().setEffect(null);
dialog.close();
confirm = false;
isDialogOpen = false;
}
}).build()
).build()
, Color.TRANSPARENT
)
);
dialog.getScene().getStylesheets().add(getClass().getResource("modal-dialog.css").toExternalForm());
// allow the dialog to be dragged around.
final Node root = dialog.getScene().getRoot();
final Delta dragDelta = new Delta();
root.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
// record a delta distance for the drag and drop operation.
dragDelta.x = dialog.getX() - mouseEvent.getScreenX();
dragDelta.y = dialog.getY() - mouseEvent.getScreenY();
}
});
root.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent mouseEvent) {
dialog.setX(mouseEvent.getScreenX() + dragDelta.x);
dialog.setY(mouseEvent.getScreenY() + dragDelta.y);
}
});
primaryStage.getScene().getRoot().setEffect(new BoxBlur());
dialog.show();
isDialogOpen = true;
}
//records relative x and y co-ordinates.
class Delta { double x, y; }
}
My problem is, when this dialog pops up, the program execution continues even though the user has not selected an option (yes or no).
I've tried setting a boolean value and checking it in a loop to know when I should check for the users choice, but this ends up causing other programs (race condition and/or just blocks everything including the dialog).
ModalConfirmDialog dialog = new ModalConfirmDialog();
dialog.showConfirmationDialog((Stage) relatedTransTable.getScene().getWindow());
while (dialog.isOpen()) {
// wait for it to close?
}
if (dialog.getConfirmation()) {
System.out.println("Confirmed choice!");
} else {
System.out.println("User denied choice!");
}
I'm unsure how to replicate JOptionPane without needing to embed Swing into my entirely JavaFX application.
Solution
Use showAndWait to display your dialog.
Shows this stage and waits for it to be hidden (closed) before returning to the caller. This method temporarily blocks processing of the current event, and starts a nested event loop to handle other events.
I'm attempting to make a modal dialog similar to Swing's JOptionPane.
The javafx.scene.control.Alert class from Java 8u40 offers functionality similar to Swing's JOptionPane and its design and implementation was heavily influenced by JOptionPane.
Current Recommendation
For basic common dialogs like alerts or confirmations, use the built-in dialog functionality in the JavaFX 8u40+ toolkits. Early access release for 8u40 is available and the dialog API there is stable and will map to the projected March 2015 production release.
If you need a specific kind of dialog box, like for tasks such as login, font selection, progress feedback and wizards, use ControlsFX (which supports a greater range of default dialog implementations than the core JavaFX platform).
If you need a completely customized dialog, then use the techniques outlined later in this answer.
Background
Don't reinvent the wheel. Use the ControlsFX library for JOptionPane type dialogs rather than writing your own implementation (and JavaFX 8u40 will introduce common dialog boxes into the platform).
The code you reference for modal dialog display is quite old now. There have been numerous features (including the addition of showAndWait) implemented both in the JavaFX core platform and 3rd party libraries, which mean that if you are doing the task today, it would probably be done in a different way.
The referenced code in the question makes use of Builders which have been deprecated in Java 8, so it is no longer recommended to use them (they no longer even show up in the JavaFX javadoc).
Code Demonstrating the built-in JavaFX 8u40+ Alert class
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.stage.Stage;
public class ShowAndWaitBuiltInAlert extends Application {
#Override public void start(Stage stage) {
Button showDialog = new Button("Show Dialog");
showDialog.setOnAction(event -> {
Alert dialog = new Alert(
Alert.AlertType.CONFIRMATION,
"Are you sure you want to exit the Dialog Demo Application?"
);
dialog.showAndWait()
.filter(response -> response.equals(ButtonType.OK))
.ifPresent(response -> stage.close());
});
stage.setScene(new Scene(showDialog));
stage.show();
}
public static void main(String[] args) { launch(args); }
}
An alert extends from Dialog, so it can be customized using all the customization methods of the Dialog class.
Code Demonstrating the built-in JavaFX 8u40+ Dialog class
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.stage.Stage;
public class ShowAndWaitBuiltInDialog extends Application {
#Override public void start(Stage stage) {
Button showDialog = new Button("Show Dialog");
showDialog.setOnAction(e -> {
Dialog<ButtonType> dialog = new Dialog<>();
dialog.setTitle("Dialog Demo");
dialog.setHeaderText("Confirm Exit");
dialog.setContentText("Are you sure you want to exit the Dialog Demo Application?");
ButtonType exit = new ButtonType("Exit", ButtonBar.ButtonData.OK_DONE);
dialog.getDialogPane().getButtonTypes().addAll(
exit, ButtonType.CANCEL
);
dialog.showAndWait()
.filter(response -> response.equals(exit))
.ifPresent(response -> stage.close());
});
stage.setScene(new Scene(showDialog));
stage.show();
}
public static void main(String[] args) { launch(args); }
}
Custom Dialog Code Demonstrating showAndWait
The following code will work in Java 8 versions predating Java 8u40.
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.geometry.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.*;
public class ShowAndWaitDialog extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
Button showDialog = new Button("Show Dialog");
showDialog.setOnAction(e -> {
ConfirmationDialog dialog = new ConfirmationDialog(
"Would you like to exit the application?"
);
dialog.setY(stage.getY() + stage.getHeight() + 10);
dialog.setX(stage.getX());
dialog.showAndWait();
if (dialog.isSelected()) {
stage.close();
}
});
stage.setScene(new Scene(showDialog));
stage.show();
}
class ConfirmationDialog extends Stage {
private VBox layout = new VBox();
private ReadOnlyBooleanWrapper selected = new ReadOnlyBooleanWrapper();
public boolean isSelected() {
return selected.get();
}
public ReadOnlyBooleanProperty selectedProperty() {
return selected.getReadOnlyProperty();
}
public ConfirmationDialog(String question) {
initStyle(StageStyle.UTILITY);
initModality(Modality.APPLICATION_MODAL);
layout.setSpacing(10);
layout.setPadding(new Insets(10));
createControls();
layout.getChildren().addAll(
new Label(question),
createControls()
);
setScene(new Scene(layout));
sizeToScene(); // workaround because utility stages aren't automatically sized correctly to their scene.
}
private HBox createControls() {
final Button ok = new Button("OK");
ok.setOnAction(e -> {
selected.set(true);
close();
});
final Button cancel = new Button("Cancel");
cancel.setOnAction(e -> {
selected.set(false);
close();
});
final HBox controls = new HBox(10, ok, cancel);
controls.setAlignment(Pos.CENTER_RIGHT);
return controls;
}
}
}
Update for comment question on Dec 19, 2014
Isn't this some basic UI functionality expected in every UI framework? Why did JavaFX lacked this native functionality until now?
showAndWait has been in JavaFX for about two and a half years and was added about 8 months after the initial JavaFX 2 release until showAndWait appeared as public API in JavaFX 2.2. During that period, the majority of the showAndWait functionality could be simulated using event handlers similar to the code in the asker's question.
In the more general case of built-in common dialog box API for things like alerts and confirmations (now added to Java 8u40), there is extensive history, design discussion and initial deferral reasons provided in the JavaFX issue tracker for the feature RT-12643.
dialog.setModal(true);
dialog.add(stuff here);
dialog.setVisible(true);
if i'm reading your problem correctly then the above code will make the dialog like a joptionpane & stop the user from clicking on anything but that dialog while the dialog is open