JavaFX Beans Binding suddenly stops working - java

I use JavaFX NumberBindings in order to calculate certain values. Initially everything works as expected. After a rather small amount of time, however, the binding just stops working. I don't receive an Exception, either.
I've tried several bindings, as well as high- and low-level approaches. Even the calculation itself (when overridden) just stops and isn't called anymore. I've also updated to the latest JDK (1.8.0_05) and rebuilt/restarted everything.
The following Minimal Working Example illustrates the problem. It should System.out.println the current width of the main window to STDOUT. After resizing the window for about 10 seconds, the output simply stops. I've also tried to bind the resulting property to a JavaFX control, in order to ensure the Property's continued usage, but that was of no avail. I believe I'm missing some very basic behaviour of the Property/Bindings here, Google doesn't seem to know that behaviour at all.
import javafx.application.Application;
import javafx.beans.binding.NumberBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class BindingsProblem extends Application {
#Override
public void start(Stage primaryStage) {
// Initialization...
StackPane root = new StackPane();
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
// Binding - The problem occurrs here!
NumberBinding currentWidthPlusTen = primaryStage.widthProperty().add(10);
IntegerProperty boundNumberProperty = new SimpleIntegerProperty();
boundNumberProperty.bind(currentWidthPlusTen);
boundNumberProperty.addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
System.out.println(newValue.toString());
}
});
}
public static void main(String[] args) {
launch(args);
}
}

The binding uses a WeakListener to observe the value of currentWidthPlusTen. Since you don't keep a reference to the boundNumberProperty, it is eligible for garbage collection as soon as the start(...) method exits. When the garbage collector kicks in, the reference is lost entirely and the binding no longer works.
To see this directly, add the line
root.setOnMousePressed( event -> System.gc());
to the start(...) method. You can force the listener to "stop working" by clicking on the window.
Obviously, that's not what you want: the fix is to retain the reference to boundNumberProperty after start(...) exits. For example:
import javafx.application.Application;
import javafx.beans.binding.NumberBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class BindingsProblem extends Application {
IntegerProperty boundNumberProperty;
#Override
public void start(Stage primaryStage) {
// Initialization...
StackPane root = new StackPane();
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
// Binding - The problem occurrs here!
NumberBinding currentWidthPlusTen = primaryStage.widthProperty()
.add(10);
boundNumberProperty = new SimpleIntegerProperty();
boundNumberProperty.bind(currentWidthPlusTen);
boundNumberProperty.addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue) {
System.out.println(newValue.toString());
}
});
}
public static void main(String[] args) {
launch(args);
}
}
Update
Anyone running into this issue might also want to look at Tomas Mikula's ReactFX, which provides a cleaner workaround for this (at the expense of using a third-party library, which you would need to spend some time learning). Tomas explains this issue and how ReactFX resolves it in this blog and the subsequent post.

Related

How to achive the effect of UNDECORATED + UTILITY in javafx? [duplicate]

I need remove my javafx app from the taskbar. I tried StageStyle.UTILITY. This is works but I need both UNDECORATED and UTILITY stage styles or another solvings.
Thank you for your replies.
Sorry you've been waiting so long for some sort of an answer on this, the following is mainly for people who come to this in the future hoping to discover a way of achieving this.
Let me start of by saying I wouldn't consider the following a solution but more of a workaround.
Assigning more than one initStyle to a stage is not possible however hiding the application from the task-bar and assigning an initStyle other than utility to the stage that is shown is.
To achieve this one must create two stages, the stage they want the user to see, and an another stage that will be considered the parent of the main stage and will be of initStyle.UTILITY this will prevent the icon from showing in the task-bar.
Below you can see the hello world example from oracles documentation modified to allow for an undecorated window with no icon (Note if one wanted to achieve a transparent/decorated window they could do so by changing the style of mainStage).
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class MultipleStageStyles extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.initStyle(StageStyle.UTILITY);
primaryStage.setOpacity(0);
primaryStage.setHeight(0);
primaryStage.setWidth(0);
primaryStage.show();
Stage mainStage = new Stage();
mainStage.initOwner(primaryStage);
mainStage.initStyle(StageStyle.UNDECORATED);
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
});
StackPane root = new StackPane();
root.getChildren().add(btn);
mainStage.setScene(new Scene(root, 300, 250));
mainStage.show();
}
}

Relocating a node JavaFX

I'm creating a maze game where there will be multiple 'chasers' following a player round a maze, to do this I need to relocate the chasers however i've ran into an issue using .relocate() it only moves it once ,I understand it does this because it shifts the whole coordinates axis itself rather than just the nodes coordinates.
I cannot use chaser.setCentreX() unfortunately to move it as I will be dynamically creating the nodes and will all have the same name 'chaser' hence why I am using getChildren() to get the coordinates. To differentiate between the multiple chasers I will use .setId() .getId() so that I can access the LayoutBounds of a specific node. For example if there were three chasers the first one would be called 'chaser' with and Id of 1 then the next with and Id of 2 etc..
Is there an alternative way of relocating a node without having this issue? , the code below demonstrates the problem.
package sample;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Pane pane=new Pane();
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(pane, 600, 600));
primaryStage.show();
Circle chaser=new Circle();
chaser.setRadius(12);
chaser.setCenterX(100);
chaser.setCenterY(50);
chaser.setFill(Color.YELLOW);
pane.getChildren().add(chaser);
Timeline timeline = new Timeline(new KeyFrame(Duration.millis(30), new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("running");
pane.getChildren().get(0).relocate(chaser.getCenterX()+400,chaser.getCenterY());
}
}));
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
}
public static void main(String[] args) {
launch(args);
}
}
What relocate does is assign the layoutX and layoutY properties. Those are completely independent from centerX and centerY. Since the latter properties are never updated, the target location remains constant regardless of how often you execute relocate(chaser.getCenterX()+400,chaser.getCenterY()); on chaser.
To see updates, you need to refer to an value that is updated when updating the location, e.g.
chaser.relocate(chaser.getLayoutX() + 400, chaser.getLayoutY());
or
chaser.setCenterX(chaser.getCenterX() + 400);
// chaser.setCenterY(chaser.getCenterY());
or
chaser.setTranslateX(chaser.getTranslateX() + 400);
// chaser.setTranslateY(chaser.getTranslateY());
As for using ids: That seems completely unnecessary. You could keep track of the chasers without using the CSS id by simply storing them in a suitable data structure. If those are the only children of the parent, you don't even need an extra datastructure, since every chaser would be stored in the children list in that case...

DropShadow constructor is not defined

I am new to JavaFX, and I am following this tutorial, https://wiki.eclipse.org/Efxclipse/Tutorials/Tutorial1, to become familiar with it.
I am trying to set up a DropShadow for my text, and it says the constructor is not recognized. Yet, the tutorial uses the exact line of code with no errors, and according to the DropShadow JavaDoc a constructor of this type should exist.
Exact error that is shown
NOTE: I forgot to delete the line that says "DropShadow d", but this has no effect on the rest of the application.
I attempted to create a DropShadow named 'd' on a separate line of code that followed the exact scheme of the constructor, but this did not work either.
DropShadow d = new DropShadow(2,3,3,Color.RED);
t.setEffect(d);
Appending zeros does not allow the constructor to recognize the values as doubles.
I'm using JDK_1.8_144 and e(fx)clipse 3.0.0.2, i.e. the latest release of each.
So, does anybody know why the DropShadow cannot be constructed?
Thanks!
package myapp;
import com.sun.prism.paint.Color;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.effect.DropShadow;
import javafx.scene.layout.BorderPane;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import java.awt.*;
public class JavaFX extends Application {
#Override
public void start(Stage primaryStage) {
BorderPane p = new BorderPane();
Text t = new Text("Hello FX");
t.setFont(Font.font("Arial", 60));
t.setEffect(new DropShadow(2,3,3,Color.RED));
p.setCenter(t);
primaryStage.setScene(new Scene(p));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Horizontal Scrolling Listener

I feel like I have searched half the Web and found no solution...
I have a java application displaying a Map(different countries etc.).
currently you are able to scroll down and up using your mouse wheel...
I want it so, that you are able to scroll sideways (horizontally).
All I need is a Listener (in Swing or Javafx does not matter) triggering whenever the mousewheel gets tilted, without the need for a focus of the map (hovering with your mouse should be enough the windows should still be focused) and without any visible scrollbars.
Using the following code every time you scroll sideways a message gets printed out...
import javafx.application.Application;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.scene.Scene;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root,400,400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
scene.setOnScroll(new EventHandler<ScrollEvent>(){
#Override
public void handle(ScrollEvent event) {
System.out.println("Scroll:" + event.getDeltaX());
}
});
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
One thing to consider:
Apparently when embedding a JFXPanel into a JFrame the sideway scrolling event is not getting passed.

Sending notification with javafx without stage

You can send a desktop notification with JavaFx like this (requires jdk 8u20 or later):
package sample;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.Stage;
import org.controlsfx.control.Notifications;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
// Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
Button notifyButton = new Button("Notify");
notifyButton.setOnAction(e -> {
Notifications.create().title("Test").text("Test Notification!").showInformation();
});
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(notifyButton, 100, 50));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
But this way you have to create a main window (stage), is it possible to avoid this? I am looking for a way to send notification like using zenity in bash: most of the code is non-gui, but uses some gui elements for informing or interacting with user in a very basic fashion.
It looks like the ControlsFX notifications require a existing stage. You can create a hidden utility stage. Try something like this.
package sample;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import org.controlsfx.control.Notifications;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
}
public static void main(String[] args) {
new JFXPanel();
notifier("Good!", "It's working now!");
}
private static void notifier(String pTitle, String pMessage) {
Platform.runLater(() -> {
Stage owner = new Stage(StageStyle.TRANSPARENT);
StackPane root = new StackPane();
root.setStyle("-fx-background-color: TRANSPARENT");
Scene scene = new Scene(root, 1, 1);
scene.setFill(Color.TRANSPARENT);
owner.setScene(scene);
owner.setWidth(1);
owner.setHeight(1);
owner.toBack();
owner.show();
Notifications.create().title(pTitle).text(pMessage).showInformation();
}
);
}
}
new JFXPanel() initializes the JavaFX thread without having to extend Application. You have to call this before any calls to Platform.runLater() otherwise you will get a thread exception. You only need to call it once for the whole application though. Honestly it is probably better to create your own notification stage and display it directly. Create a stage like above and put your own contents. You can probably reuse some of the styling from the ControlsFX source.

Categories

Resources