I've been struggling for weeks to resolve the memory leaks within our JavaFX application, and thought today I was at a complete loss with it so decided to write the simplest application application I could think of to prove JavaFX could in fact release memory and therefore proving I was doing something wrong. To my surprise this seems to leak memory too.
Can anyone advise why the following application still has a javafx.scene.control.Label in the heap after the button is clicked? The way I checked it was there was with jprofiler.
public class MemoryTestApplication extends Application {
#Override
public void start(Stage primaryStage) {
//root pane
final VBox root = new VBox();
//label to remove when button is clicked
final Label label = new Label("Remove Me");
root.getChildren().add(label);
//button to remove label
Button btn = new Button("Remove label");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
root.getChildren().remove(label);
}
});
root.getChildren().add(btn);
//create scene and stage
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Your anonymous inner class for the event handler is holding a reference to the label, and your button is holding a reference to the event handler.
The anonymous inner class is going to have two synthetically generated fields, created by the compiler, like:
final Label val$label;
final MemoryTestApplication this$0;
Since these are final, you can't clear label after using it by label = null;, so I think your best bet is to change the event handler to a normal, named class, pass the label reference in to its constructor, and then clear it after using it to remove the label from the layout. (You would want to test that it was not null before removing, so that it would only be removed the first time the button is pressed).
Related
I'm beginner at JavaFx. I tried to run basic things i.e button but i'm not able due to some compilation error i.e. Button cannot be covert to node, method list add(node) is not applicable This is my simple code to run program in netbeans
Code
public class Hello extends Application{
Button btn;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("I'm beginner");
btn=new Button("Click me");
StackPane root=new StackPane();
root.getChildren().add(btn);
Scene scene=new Scene(root,350,400);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Most probably you used the button from
java.awt package. Make sure that the Button is from javafx.scene.control.Button package.
I had similar problem once and wasted an hour on that stupid mistake.
Check the imports. It should be javafx.scene.control.Button for Button and javafx.scene.layout.StackPane for StackPane.
Is it correct?
Check out http://code.makery.ch/library/javafx-8-tutorial/.
And http://gluonhq.com/products/scene-builder/
The method described in the tutorial combined with the new scene builder makes creating a javafx gui insanely easy.
I was assigned in school to build a small JAVAFX program with buttons that travels through different Scenes (layouts).
My program is a log in window, and details window. But I have to create the details window in a different class than Main class. How do I make the button call the 'details window'from Main when it's a class?
(See createAccountBtn.setOnAction)
Main Class
Detail window Class
So Here's the deal: I created an interface where I put this method:
void displayLayout(VBox layout);
And then It triggers this method in Main:
public void displayLayout(VBox layout){
Scene scene = new Scene(layout, 200, 200);
window.setScene(scene);
I also have in Main this:
Button createAccountBtn = new Button("Create new account");
createAccountBtn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
displayLayout(new DetailScreen(listener));
}
});
layout = new VBox();
layout.getChildren().addAll(accountNumberTF, loginBtn, createAccountBtn);
layout.setAlignment(Pos.CENTER);
displayLayout(layout);
window.show();
Which is the layout options and the button. the setOnAction button points to a new Class that inherits VBox and acts as a layout:
private ChangeScreenListener listener;
public DetailScreen(ChangeScreenListener listener) {
this.listener = listener;
Button exitBtn = new Button("EXIT");
TextField input = new TextField();
VBox layout = new VBox();
layout.getChildren().addAll(input, exitBtn);
However after I click the button, the window becomes empty and does not change layout.
Ideas?
public static variables can pass variables between classes. This is the easiest way i could think off to pull off the results you desire.
In the main class define the following variables:
public static Stage stage;
public static Scene sceneMain;
Assuming you have already set a scene and using a Stage in your main constructor write the following code after setting your Stage and Scene.
stage = primaryStage;
sceneMain = scene;
When you have done this you can easily call on to the stage in another class by and going back to your main scene like this: (this is what you can put under the event of your buttonclick())
Main.stage.setScene(sceneMain);
Or you can set another scene by simply putting the scene you want in the .setscene();
My title is badly worded because my problem is very hard to describe, so I drew an image for it:
I have an ImageView object which represents a pile of cards (not poker cards but just used them as an example). When this image view is clicked, I need a window to popup that features a ScrollPane and shows them all the card objects that are in the linked list. When the user clicks anywhere outside of the window (and later on, any right mouse button click) the scrollpane window needs to close.
Ways that I have already tried:
Scene with APPLICATION_MODAL. Then did Scene.showAndWait(). I didn't like this method because it made another application on the user's taskbar. It also felt clunky and just bad.
Changed my root pane to a StackPane, then added this Scrollpane to the stackpane when the user clicked on the deck. This for some reason was really slow and seemed really obtrusive. It was also annoying because my alternate class needed to have access to the root pane (since when it closes, it needs to go to the root StackPane and call .remove() on itself).
Are there any other better ways to accomplish this? My application is going to have many of these piles and so this framework needs to work very well.
I would still propose to open a new Stage with some restrictions to solve your issues with this approach.
You can use the initOwner property of the Stage to have another Stage as owner, therefore no other icon will appear on the taskbar.
You can use the initStyle property with TRANSPARENT or UNDECORATED StageStlye, this will ensure that only the content is visible.
And in the end you can use the focusedProperty to check whether the Stage lost focus to close it.
Example
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root,400,400);
Button b = new Button("Open deck");
b.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
Stage popupStage = new Stage();
popupStage.initOwner(primaryStage);
popupStage.initStyle(StageStyle.UNDECORATED);
Scene sc = new Scene(new ScrollPane(), 300, 300);
popupStage.setScene(sc);
popupStage.focusedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue,
Boolean newValue) {
if(!newValue)
popupStage.close();
}
});
popupStage.show();
}
});
root.setCenter(b);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
It is also possible of course to not open it in a new Stage, but draw a ScrollPane inside the current Stage which overlaps the content of the Stage using for example an AnchorPane or Group as root, but the first solution has the advantage that you are not bound to the dimensions of main Stage (the popup can have any size that you want).
You can achieve this with a low level system hook that catches the mouse events.
http://kra.lc/blog/2016/02/java-global-system-hook/ or https://github.com/kwhat/jnativehook/releases
I hope that is what you needed, otherwise i got your question wrong.
I tried to write my 'own' log in a tab and I experienced problems with updating the label with a naive solution that I had.
So after I googled I checked out this solution here:
Displaying changing values in JavaFx Label
I don't know if I did everything right but unfortunately this solution doesn't work for me.
final static Label logLabel = new Label();
final static SimpleStringProperty logString = new SimpleStringProperty("test");
...
...
public void start(Stage primaryStage) {
TabPane tabPane = new TabPane();
tabPane.getTabs().add(createSettingsTab());
tabPane.getTabs().add(createParticipantTab());
tabPane.getTabs().add(createSpectatorTab());
tabPane.getTabs().add(createOverviewTab());
tabPane.getTabs().add(createTournamentTab());
tabPane.getTabs().add(createLogTab());
tabPane.setTabClosingPolicy(TabClosingPolicy.UNAVAILABLE);
Scene scene = new Scene(tabPane, 1200, 800);
primaryStage.setScene(scene);
primaryStage.setResizable(true);
primaryStage.show();
}
private Tab createLogTab() {
// TODO Auto-generated method stub
logLabel.textProperty().bind(logString);
Tab tab = new Tab("Log");
tab.setContent(logLabel);
return tab;
}
I got this lines for initializing the Label and for setting the new text I do this:
logString.set(logString.get() + "foo");
The log tab keeps being blank...
I'd appreciate any help! Thanks!
edit// That's the only useful mcve I can think of. As I said the other create methods to create the other tabs are not making use of the label or the SimpleStringProperty
This is the button that doesn't work as supposed concerning the label.
buttonLoadConfig.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
System.out.println("test");
logString.set(logString.get() + "\ttest");
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Choose Config");
File config = fileChooser.showOpenDialog(new Stage());
}
});
Use of static members is often not a good idea and is probably contributing to your issue.
Your code as it is currently written can have many tabs but only one label. You are trying to add the same label to multiple tabs. But, when you do that, the label will be removed from the previous tabs because of rules on how the JavaFX Scenegraph works.
See the Node documentation:
If a program adds a child node to a Parent (including Group, Region, etc) and that node is already a child of a different Parent or the root of a Scene, the node is automatically (and silently) removed from its former parent.
So the binding is working, but, due to the above rule and the way you have written the rest of your code, the program as a whole is likely not working as you expected or wished.
If you are still having issues, update your question with an mcve.
How can I disable a JavaFX button while something is processing (to prevent user from spamming the button)?
primaryStage.setTitle("Update Stuff");
Label lbl = new Label();
lbl.setText("Nothing here yet");
Button btn = new Button();
btn.setText("Update");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
btn.setDisable(true); //I tried this, but to no avail
lbl.setText(getNumberOfViewers()); // this might take a few seconds, need to disable button during this time
btn.setDisable(false);
}
});
VBox root = new VBox();
root.setAlignment(Pos.CENTER);
root.getChildren().addAll(btn, lbl);
primaryStage.setScene(new Scene(root, 200, 100));
primaryStage.show();
Based off of your code you posted, it would be something like this in javafx 8:
primaryStage.setTitle("Update Stuff");
Label lbl = new Label();
lbl.setText("Nothing here yet");
Button btn = new Button();
btn.setText("Update");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
btn.setDisable(true);
new Thread(()->{
final String numberOfViews = getNumberOfViewers();
Platform.runLater(()->{
lbl.setText(numberOfViews);
btn.setDisable(false);
});
}).start();
lbl.setText(getNumberOfViewers()); // this might take a few seconds, need to disable button during this time
}
});
VBox root = new VBox();
root.setAlignment(Pos.CENTER);
root.getChildren().addAll(btn, lbl);
primaryStage.setScene(new Scene(root, 200, 100));
primaryStage.show();
The new thread will make it so that the UI thread is never actually blocked (and windows doesn't give you that nasty 'the application has stopped responding' business), and then inside the thread, anytime you want to change something on the GUI, use the Platform.runLater() syntax, technically you are making a new 'Runnable' object inside of the runLater method, but thanks to lambda expressions, you don't need to worry about that! If you don't use the Platform.runLater() you'll get a 'not on the Javafx application Thread' exception when you try to update stuff.
Also worth noting, I think in javafx 2.* (java 7) it's btn.setEnable(false) instead of btn.setDisable(true) just a change of how they went about it. same effect.
use the f.f.g code
btn.setEnabled(false);
within the handle method, then after:
btn.setEnabled(true);