JavaFX How to Handle Input With Very High Framerate? - javafx

I'm on Ubuntu 20.04 using OpenJavaFX. I want to have the user press the escape key to toggle the display of a menu. Due to the very high frame rate, I'm struggling to achieve this.
The simple program:
class
AutoScalingCanvas extends Region {
private final Canvas canvas;
public AutoScalingCanvas(double canvasWidth, double canvasHeight) {
this.canvas = new Canvas(canvasWidth, canvasHeight);
getChildren().add(canvas);
}
public GraphicsContext getGraphicsContext2D() {
return canvas.getGraphicsContext2D();
}
#Override
protected void layoutChildren() {
double x = getInsets().getLeft();
double y = getInsets().getTop();
double w = getWidth() - getInsets().getRight() - x;
double h = getHeight() - getInsets().getBottom() - y;
// preserve aspect ratio while also staying within the available space
double sf = Math.min(w / canvas.getWidth(), h / canvas.getHeight());
canvas.setScaleX(sf);
canvas.setScaleY(sf);
positionInArea(canvas, x, y, w, h, -1, HPos.CENTER, VPos.CENTER);
}
}
public class
Gui extends Application
{
long target_ns_per_frame = 1_000_000_00 / 60;
boolean in_menu;
boolean esc_down;
#Override
public void
start(Stage primary_stage) throws Exception
{
primary_stage.setTitle("GUI");
AutoScalingCanvas canvas = new AutoScalingCanvas(1280, 720);
Scene scene = new Scene(canvas);
scene.setFill(Color.BLACK);
primary_stage.setScene(scene);
GraphicsContext gc = canvas.getGraphicsContext2D();
scene.setOnKeyPressed(new EventHandler<KeyEvent>(){
#Override
public void handle(KeyEvent event)
{
esc_down = (event.getCode() == KeyCode.ESCAPE);
}
});
scene.setOnKeyReleased(new EventHandler<KeyEvent>(){
#Override
public void handle(KeyEvent event)
{
if (event.getCode() == KeyCode.ESCAPE)
{
esc_down = false;
}
}
});
new AnimationTimer()
{
#Override
public void
handle(long total_elapsed_time_ns)
{
gc.setFill(Color.WHITE);
gc.fillRect(0, 0, 1280, 720);
if (esc_down)
{
in_menu = !in_menu;
}
if (in_menu)
{
gc.setFill(Color.BLUE);
gc.fillRect(300, 300, 200, 200);
}
else
{
gc.setFill(Color.RED);
gc.fillRect(100, 100, 100, 100);
}
long elapsed_time_ns = System.nanoTime() -
total_elapsed_time_ns;
if (elapsed_time_ns < target_ns_per_frame)
{
long time_remaining_ms = (target_ns_per_frame - elapsed_time_ns)
/ 1000;
try {
Thread.sleep(time_remaining_ms);
}
catch (InterruptedException e)
{
}
}
}
}.start();
primary_stage.show();
}
}
If run without Thread.sleep() the framerate is around 600fps. As a result, pressing the escape key once will be seen as down for a number of frames (due to the speed limit of my human finger) thereby triggering the toggle multiple times. This is obviously not intended. So, I tried to cap the framerate at 60fps. However, with the sleeping, the program runs very slow (perhaps I'm sleeping on the wrong thread?)
How best to keep track of the input to achieve this toggling behavior?

First, you should never block the FX Application Thread by calling Thread.sleep() on it. That will prevent the UI from being updated, or events being handled, until the sleep() is complete.
If the intention is simply that each time the user presses the ESCAPE key that the menu is toggled, then your code is way too complex. Simply toggle a flag indicating if the menu should be painted in the onReleased handler, and check the flag in AnimationTimer.handle():
public class Gui extends Application {
boolean inMenu;
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("GUI");
AutoScalingCanvas canvas = new AutoScalingCanvas(1280, 720);
Scene scene = new Scene(canvas);
scene.setFill(Color.BLACK);
primaryStage.setScene(scene);
GraphicsContext gc = canvas.getGraphicsContext2D();
scene.setOnKeyReleased(event -> {
if (event.getCode() == KeyCode.ESCAPE) {
inMenu = ! inMenu;
}
});
new AnimationTimer() {
#Override
public void handle(long now) {
gc.setFill(Color.WHITE);
gc.fillRect(0, 0, 1280, 720);
if (inMenu) {
gc.setFill(Color.BLUE);
gc.fillRect(300, 300, 200, 200);
} else {
gc.setFill(Color.RED);
gc.fillRect(100, 100, 100, 100);
}
}
}.start();
primaryStage.show();
}
}
If you want to optimize repaints only to when they are needed, simply introduce another flag indicating a repaint is necessary:
public class Gui extends Application {
private boolean inMenu;
private boolean repaintRequested = true ;
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("GUI");
AutoScalingCanvas canvas = new AutoScalingCanvas(1280, 720);
Scene scene = new Scene(canvas);
scene.setFill(Color.BLACK);
primaryStage.setScene(scene);
GraphicsContext gc = canvas.getGraphicsContext2D();
scene.setOnKeyReleased(event -> {
if (event.getCode() == KeyCode.ESCAPE) {
inMenu = ! inMenu;
repaintRequested = true ;
}
});
new AnimationTimer() {
#Override
public void handle(long now) {
if (repaintRequested) {
gc.setFill(Color.WHITE);
gc.fillRect(0, 0, 1280, 720);
if (inMenu) {
gc.setFill(Color.BLUE);
gc.fillRect(300, 300, 200, 200);
} else {
gc.setFill(Color.RED);
gc.fillRect(100, 100, 100, 100);
}
repaintRequested = false ;
}
}
}.start();
primaryStage.show();
}
}

I didn't try any of this, so I can only nudge you in a general direction.
You could add another boolean variable esc_handled you set to true at the end of your handle method. Then you can add one more check to the method if the event has already been handled and if it has, you skip the handling step.
The following code achieves this:
add variable
boolean in_menu;
boolean esc_down;
boolean esc_handled;
check for esc_handled (inside handle) and set event to handled
if (esc_down && !esc_handled)
{
in_menu = !in_menu;
esc_handled = true;
}
on release esc set esc_handled to false
scene.setOnKeyReleased(new EventHandler<KeyEvent>(){
#Override
public void handle(KeyEvent event)
{
if (event.getCode() == KeyCode.ESCAPE)
{
esc_down = false;
esc_handled = false;
}
}
});

It looks like you're using the animation timer to do some kind of sampling of the state of the in or out and the press/release of the escape key. You don't need to do that at all.
You're trying to turn the key press/release events into a state, which makes sense, but you simply need to toggle that state in the event handler. Since the show/hide action is in response to an event, you can just call the draw routine directly from the event. So then the event will toggle the state and call the screen redraw:
public class Gui extends Application {
boolean in_menu;
GraphicsContext gc;
#Override
public void start(Stage primary_stage) throws Exception {
primary_stage.setTitle("GUI");
AutoScalingCanvas canvas = new AutoScalingCanvas(1280, 720);
primary_stage.setScene(new Scene(canvas));
gc = canvas.getGraphicsContext2D();
showOrHideMenu();
new Scene(canvas).setOnKeyPressed(event -> {
if (event.getCode() == KeyCode.ESCAPE) {
in_menu = !in_menu;
showOrHideMenu();
}
});
primary_stage.show();
}
private void showOrHideMenu() {
gc.setFill(Color.WHITE);
gc.fillRect(0, 0, 1280, 720);
if (in_menu) {
gc.setFill(Color.BLUE);
gc.fillRect(300, 300, 200, 200);
} else {
gc.setFill(Color.RED);
gc.fillRect(100, 100, 100, 100);
}
}
}
Alternatively (and this is probably more "JavaFX"), you can make the In/Out Menu state observable, and then put a change listener on the state to do the repaint:
public class Gui extends Application {
BooleanProperty in_menu = new SimpleBooleanProperty(false);
GraphicsContext gc;
#Override
public void start(Stage primary_stage) throws Exception {
primary_stage.setTitle("GUI");
AutoScalingCanvas canvas = new AutoScalingCanvas(1280, 720);
Scene scene = new Scene(canvas);
primary_stage.setScene(scene);
gc = canvas.getGraphicsContext2D();
showOrHideMenu(false);
scene.setOnKeyPressed(event -> {
if (event.getCode() == KeyCode.ESCAPE) {
in_menu.set(!in_menu.get());
}
});
in_menu.addListener(((observable, oldValue, newValue) -> showOrHideMenu(newValue)));
primary_stage.show();
}
private void showOrHideMenu(boolean inMenu) {
gc.setFill(Color.WHITE);
gc.fillRect(0, 0, 1280, 720);
if (inMenu) {
gc.setFill(Color.BLUE);
gc.fillRect(300, 300, 200, 200);
} else {
gc.setFill(Color.RED);
gc.fillRect(100, 100, 100, 100);
}
}
}

Related

How to use Javafx and scenebuilder to draw on canvas (Group element)

i am new to scenebuilder and fx and need some help.
I have the class Puffer , TestMain and MainViewController.
I tried to paint on the canvas and it worked.I tried Puffer and it worked too.
Now I wanted to use scenebuilder and have my problems.I would like to draw the Group from Puffer on the canvas with a button and don't know what's the best solution.I already tried to modify Puffer without any result.
public class Puffer {
Group root = new Group();
public void draw() {
for (int x = 0; x < 800; x++) {
for (int y = 0; y < 600; y++) {
Complex z0 = new Complex(0.11, 0.123); //!!!!!!!!!!!!! under 1
Complex c = new Complex(x / 400.0 - 2, 1 - y / 400.0);
if (isBounded(iterate(z0, c))) {
drawPoint(x, y, root);
}
}
}
// primaryStage.setScene(new Scene(root, 800, 600));
// primaryStage.show();
}
public void drawPoint(int x, int y, Group root) {
int max = 255;
int min = 0;
int range = max - min + 1;
Line line = new Line(x, y, x, y);
// line.setStroke(Color.rgb((int)(Math.random() * range) + min,(int)(Math.random() * range) + min,(int)(Math.random() * range) + min));
root.getChildren().add(line);
}
public Complex iterate(Complex z0, Complex c) {
Complex zn = z0;
for (int i = 0; i < 20; i++) {
zn.square().add(c);
}
return zn;
}
public boolean isBounded(Complex zn) {
return zn.module() < 2;
}
public class MainViewController {
#FXML
Canvas canvas;
// Event Listener on Button.onAction
#FXML
public void btnOkClicked(ActionEvent event) {
System.out.println("Test");
}
#FXML
public void drawCanvas(){
// GraphicsContext gc = canvas.getGraphicsContext2D();
// gc.setLineWidth(3);
// gc.setStroke(Color.BLACK);
// System.out.println("drawCanvas");
//
// try {
// canvas.setOnMousePressed(event -> {
// System.out.println("Mouse click");
// gc.beginPath();
// gc.lineTo(event.getSceneX(), event.getSceneY());
// gc.stroke();
// });
//
// canvas.setOnMouseDragged(event -> {
// System.out.println("Mouse dragged");
// gc.lineTo(event.getSceneX(), event.getSceneY());
// gc.stroke();
// });
// }catch (Exception e){
// System.out.println(e);
// System.exit(0);
// }
}
public class TestMain extends Application {
#Override
public void start(Stage s1) throws Exception {
try {
Parent root = FXMLLoader.load(getClass().getResource("MainView.fxml"));
Scene scene = new Scene(root);
s1.setTitle("Test");
s1.setScene(scene);
s1.show();
}catch(Exception ex) {
ex.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}

JavaFX detect when scrollbar movement is complete

Swing provided java.awt.event.AdjustmentEvent.getValueIsAdjusting() which could be used to detect when a JScrollBar movement was completed. This was used in a legacy application so that it only requested data once rather than on every little scroll movement.
JFrame frame = new JFrame();
JScrollBar comp = new JScrollBar();
comp.addAdjustmentListener(e -> {
if (!e.getValueIsAdjusting()) {
System.out.println("Finished " + e.getValue());
}
});
frame.add(comp);
comp.setPreferredSize(new Dimension(25, 300));
frame.pack();
frame.setVisible(true);
I'm looking for an equivalent method in JavaFX with ScrollBar. It appears that ScrollBar only has a way of monitoring the current value. I've reviewed the code in com.sun.javafx.scene.control.behavior.ScrollBarBehavior but can see no obvious way to do this.
My workaround for now is fairly ugly. It uses a pair of event listeners to drag if the mouse is pressed, this state is used to maintain another valueProperty() which is only updated when the mouse is released.
Is there a better/easier way to do this?
#Override
public void start(Stage primaryStage) throws Exception {
ScrollBar scrollbar = new ScrollBar();
new ScrollBarFinishedAdjusting(scrollbar).valueProperty().addListener((obs, oldValue, newValue) -> {
System.out.println("Finished " + newValue);
});
primaryStage.setScene(new Scene(scrollbar));
primaryStage.show();
}
private class ScrollBarFinishedAdjusting {
private boolean mouseDown;
private DoubleProperty value = new SimpleDoubleProperty();
private ScrollBar scrollbar;
public ScrollBarFinishedAdjusting(ScrollBar scrollbar) {
this.scrollbar = scrollbar;
scrollbar.valueProperty().addListener((obs, oldValue, newValue) -> {
if (!mouseDown) {
update();
}
});
scrollbar.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
mouseDown = true;
});
scrollbar.addEventFilter(MouseEvent.MOUSE_RELEASED, event -> {
mouseDown = false;
update();
});
update();
}
private void update() {
value.set(scrollbar.getValue());
}
public ReadOnlyDoubleProperty valueProperty() {
return value;
}
}

JavaFX button unselect

I'm trying to make a sprite editor using JavaFX using buttons as the pixels. I am able to change the colour of each button on press, but I'm trying to get it so if I click and drag I can paint multiple pixels.
The problem I'm finding is that after clicking and holding onto a button, I am unable to select the new button when I move the mouse over new button to select that one too. If I click and drag re-entering that button I can get the "Paint Dragged Pixel" debug message, but not if I enter a new pixel with the mouse down, which is what I want. Also I can get the pixel button to print "Entered Pixel" when the mouse enters any button, but not for when I click and drag to a new pixel.
I think the problem is that when I click on one pixel I am locked in, and unable to select a new pixel by hovering over a new one. Is there a way to unbind this selection, or is the problem a different one.
Main Application:
public class Main extends Application {
boolean mousePressed = false;
public boolean isMousePressed() {
return mousePressed;
}
#Override
public void start(Stage primaryStage) throws Exception{
BorderPane borderPane = new BorderPane();
primaryStage.setTitle("SpriteSheet");
Group root = new Group();
Scene scene = new Scene(borderPane, 500,200);
scene.setFill(Color.BLACK);
primaryStage.setScene(scene);
GridPane gridPane = new GridPane();
borderPane.setCenter(root);
for(int x = 0; x < 10; x++)
{
for(int y = 0; y < 10; y++) {
PixelButton button = new PixelButton();
button.setParentMain(this);
button.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
mousePressed = true;
System.out.println("mouseDown");
}
});
button.setOnMouseReleased(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
mousePressed = false;
System.out.println("mouseUp");
}
});
gridPane.add(button, x, y);
}
}
root.getChildren().add(gridPane);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The class for the PixelButton.
public class PixelButton extends Button {
Main parentMain;
public void setParentMain(Main parent) {
parentMain = parent;
}
public PixelButton() {
this.setMinSize(10, 10);
this.setPrefSize(10, 10);
this.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
}
});
this.setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
System.out.println("Entered Pixel");
if(parentMain.isMousePressed()){
System.out.println("Paint Dragged Pixel");
}
}
});
}
}
Thank you in advance.
Ok so I have been thinking about this one for a bit and have finally come up with a solution I simplified my solution a bit and used rectangles instead of buttons but you can transfer most of the code to the buttons as well so to start with this is not the exact functionality you were looking for but as close as I could get basically I fire an event on mouse press that releases the mouse click and as long as that event is not coming from the rectangle then dont flip the painting boolean paint and so you basically click to enter a "Paint Mode" and click again to get out of coloring tiles
public class Main extends Application {
private boolean mousePressed;
#Override
public void start(Stage primaryStage) throws Exception{
BorderPane borderPane = new BorderPane();
primaryStage.setTitle("SpriteSheet");
Group root = new Group();
Scene scene = new Scene(borderPane, 500,200);
// scene.setFill(Color.BLACK);
primaryStage.setScene(scene);
GridPane gridPane = new GridPane();
borderPane.setCenter(root);
for(int x = 0; x < 10; x++) {
for(int y = 0; y < 10; y++) {
Rectangle rectangle = new Rectangle(10, 10);
rectangle.setOnMousePressed(event -> {
mousePressed = true;
System.out.println("mouseDown");
rectangle.fireEvent(new MouseEvent(MouseEvent.MOUSE_RELEASED,
rectangle.getLayoutX(), rectangle.getLayoutY(), rectangle.getLayoutX(), rectangle.getLayoutY(),
MouseButton.PRIMARY, 1,
false, false, false, false,
false, false, false, false,
false, false, null));
});
rectangle.setOnMouseReleased(event -> {
System.out.println(event.getSource());
if(!event.getSource().toString().equals("Rectangle[x=0.0, y=0.0, width=10.0, height=10.0, fill=0x000000ff]")) {
mousePressed = false;
System.out.println("mouseUp");
}
});
rectangle.setOnMouseMoved(event -> {
if(mousePressed) {
rectangle.setFill(Color.BLUE);
}
});
gridPane.add(rectangle, x, y);
}
}
root.getChildren().add(gridPane);
primaryStage.show();
}
public static void main(String[] args) { launch(args); }
}

JavaFX 2 Timeline setcyclecount()

I'm using javafx in javase8 and netbeans 8.0.2, I made randomly generated shape images and show them sequentially with timeline. But last image isn't shown. timeline.setcyclecount(12) i use java generate 12 images but doesn't show 12. image in timeline.
public class JavaFXApplication3 extends Application {
int k;
Timeline timeline;
class ResizableCanvas extends Canvas {
private void draw() {
int[] uyaran = {3, 7, 12};
boolean[] type = new boolean[12];
for (int i = 0; i < 12; i++) {
type[i] = false;
}
for (int v : uyaran) {
type[v - 1] = true;
}
double w = getWidth();
double h = getHeight();
GraphicsContext gc = getGraphicsContext2D();
gc.clearRect(0, 0, w, h);
gc.setFill(Color.RED);
System.out.println(k);
if (type[k]) {
gc.fillOval(0, 0, w, h);
}
k++;
}
}
#Override
public void start(Stage stage) throws Exception {
k = 0;
ResizableCanvas canvas = new ResizableCanvas();
timeline = new Timeline(new KeyFrame(Duration.millis(1000), ae -> canvas.draw()));
timeline.setCycleCount(12);
timeline.setOnFinished(ActionEvent -> {
try {
Thread.sleep(10000);
} catch (InterruptedException ex) {}
stage.close();
});
timeline.play();
Pane pane = new Pane();
pane.getChildren().add(canvas);
canvas.widthProperty().bind(pane.widthProperty());
canvas.heightProperty().bind(pane.heightProperty());
stage.setScene(new Scene(pane));
stage.show();
}
}
You are calling Thread.sleep(...) on the FX Application Thread, which blocks the thread and prevents it from updating. The final ellipse is only actually rendered when the pause is over, but of course you then close the window so you never see it.
Use a PauseTransition to pause, and use its onFinished handler to do something when the pause is over:
timeline.setOnFinished(ae -> {
PauseTransition pause = new PauseTransition(Duration.seconds(10));
pause.setOnFinished(event -> stage.close());
pause.play();
});

Change between two FXML files in JavaFX2.2 on change Listener

i am new in JavaFX programming. I have an Application, with a simple login page as described in the example here, and i add a StringProperty to the actiontarget element. So when the text changes inside the actiontarget i want a new FXML file with a webview inside, to load from the FXMLLoader and be dipslayed on the screen. Below is the exception i get. I can load any other fxml file, without a webview inside it, without a problem. Thanks in advance.Code samples below
The exception :
java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-3
at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:237)
at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:397)
at com.sun.webpane.sg.prism.InvokerImpl.checkEventThread(InvokerImpl.java:33)
at com.sun.webpane.platform.WebPage.<init>(WebPage.java:189)
at com.sun.webpane.sg.ImplementationManager.createPage(ImplementationManager.java:57)
at com.sun.webpane.sg.ImplementationManager.createPage(ImplementationManager.java:51)
at javafx.scene.web.WebEngine.<init>(WebEngine.java:704)
at javafx.scene.web.WebEngine.<init>(WebEngine.java:691)
at javafx.scene.web.WebView.<init>(WebView.java:245)
at student.WebBrowser.<init>(WebBrowser.java:31)
at Login.Login.replaceSceneContent(Login.java:171)
at Login.Login.access$000(Login.java:66)
at Login.Login$2.changed(Login.java:143)
at Login.Login$2.changed(Login.java:137)
at com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:196)
at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:100)
at javafx.beans.property.StringPropertyBase.fireValueChangedEvent(StringPropertyBase.java:121)
at javafx.beans.property.StringPropertyBase.markInvalid(StringPropertyBase.java:128)
at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:161)
at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:67)
at javafx.scene.text.Text.setText(Text.java:188)
at Login.Client.run(Client.java:66)
First my listener:
// Add change listener
sp.addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> ov, String t, String t1) {
if(t1.equalsIgnoreCase("user authenticated successfully")){
try {
replaceSceneContent(cb.getSelectionModel().getSelectedItem().toString()+".fxml",primaryStage);
System.out.println("everything ok");
} catch (Exception ex) {
System.out.println("something went wrong");
ex.printStackTrace();
}
}
}
});
Second my method : replaceSceneContent(String fxml, Stage stage)
private Parent replaceSceneContent(String fxml, Stage stage) throws Exception {
Parent page = (Parent) FXMLLoader.load(getClass().getResource("/FXML_Files/"+fxml), null, new JavaFXBuilderFactory());
Scene scene = stage.getScene();
if (scene == null) {
scene = new Scene(page, 700, 450);
stage.setScene(scene);
} else {
stage.getScene().setRoot(page);
}
if(fxml.equalsIgnoreCase("Student.fxml")){
Pane spane = (Pane) page.lookup("#pane");
WebBrowser wb = new WebBrowser();
spane.getChildren().add(wb);
}
return page;
}
And my WebBrowser class similar to the example in NetBeans7.2:
public class WebBrowser extends Pane {
public WebBrowser() {
WebView view;
final WebEngine eng;
view = new WebView();
view.setMinSize(10, 10);
view.setPrefSize(500, 400);
eng = view.getEngine();
eng.load("http://www.oracle.com/us/index.html");
VBox.setVgrow(this, Priority.ALWAYS);
setMaxWidth(Double.MAX_VALUE);
setMaxHeight(Double.MAX_VALUE);
final TextField locationField = new TextField("http://www.oracle.com/us/index.html");
locationField.setMaxHeight(Double.MAX_VALUE);
Button goButton = new Button("Go");
goButton.setDefaultButton(true);
EventHandler<ActionEvent> goAction = new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent event) {
eng.load(locationField.getText().startsWith("http://") ? locationField.getText() :
"http://" + locationField.getText());
}
};
goButton.setOnAction(goAction);
locationField.setOnAction(goAction);
eng.locationProperty().addListener(new ChangeListener<String>() {
#Override public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
locationField.setText(newValue);
}
});
GridPane grid = new GridPane();
ButtonsEvents be = new ButtonsEvents();
TilePane tp = be;
tp.setAlignment(Pos.CENTER);
grid.setVgap(5);
grid.setHgap(5);
GridPane.setConstraints(locationField, 0, 0, 1, 1, HPos.CENTER, VPos.CENTER, Priority.ALWAYS, Priority.SOMETIMES);
GridPane.setConstraints(goButton,1,0);
GridPane.setConstraints(view, 0, 1, 2, 1, HPos.CENTER, VPos.CENTER, Priority.ALWAYS, Priority.ALWAYS);
GridPane.setConstraints(tp, 0, 2, 2, 1, HPos.CENTER, VPos.CENTER, Priority.ALWAYS, Priority.NEVER);
grid.getColumnConstraints().addAll(
new ColumnConstraints(100, 100, Double.MAX_VALUE, Priority.ALWAYS, HPos.CENTER, true),
new ColumnConstraints(40, 40, 40, Priority.ALWAYS, HPos.CENTER, true)
);
grid.getChildren().addAll(locationField, goButton,view, tp);
getChildren().add(grid);
}
#Override
protected void layoutChildren() {
List<Node> managed = getManagedChildren();
double width = getWidth();
double height = getHeight();
double top = getInsets().getTop();
double right = getInsets().getRight();
double left = getInsets().getLeft();
double bottom = getInsets().getBottom();
for (int i = 0; i < managed.size(); i++) {
Node child = managed.get(i);
layoutInArea(child, left, top,
width - left - right, height - top - bottom,
0, Insets.EMPTY, true, true, HPos.CENTER, VPos.CENTER);
}
}
}
As stated in the exception, changes in the JavaFX scene graph can only be made in the JavaFX application thread. To make sure your code is run in this thread, try the following:
Platform.runLater(new Runnable() {
#Override
public void run() {
// This method is invoked on JavaFX thread
replaceSceneContent(cb.getSelectionModel().getSelectedItem().toString()+".fxml",primaryStage);
}
});
The javafx manager will run this code at some point in the future on the correct thread.
More information:
http://docs.oracle.com/javafx/2/threads/jfxpub-threads.htm

Resources