All I wanted is to update a label as my program is running. I am reading some files and I wanted it to display the name of the file is was reading.
However, it only displays the last file using the code below (basically GUI doesn't respond until the whole process is completed):
static Text m_status_update = new Text(); //I declared this outside the function so dont worry
m_status_update.setText("Currently reading " + file.getName());
I got around 4-5 files and I just want to display the name.
I saw a similar question Displaying changing values in JavaFx Label
, the best answer recommended the following:
Label myLabel = new Label("Start"); //I declared this outside the function so dont worry
myLabel.textProperty().bind(valueProperty);
However the valueProperty is a StringProperty and I am stuck converting a string into a string property.
Also, I saw this Refresh label in JAVAFX , but the OP could update the label based on action. I really dont have any action going on?
If you run the entire process on the FX Application thread, then that is (effectively) the same thread that is being used to display the UI. If both the display of the UI, and your file iteration process are running in the same thread, only one can happen at once. So you prevent the UI from updating until the process is complete.
Here's a simple example where I just pause for 250 milliseconds between each iteration (simulating reading a reasonably large file). One button launches this in the FX Application thread (notice how the UI is unresponsive while this runs - you can't type in the text field). The other button uses a Task to run it in the background, properly scheduling updates to the UI on the FX Application thread.
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class UpdateTaskDemo extends Application {
#Override
public void start(Stage primaryStage) {
Label label = new Label();
Button runOnFXThreadButton = new Button("Update on FX Thread");
Button runInTaskButton = new Button("Update in background Task");
HBox buttons = new HBox(10, runOnFXThreadButton, runInTaskButton);
buttons.setPadding(new Insets(10));
VBox root = new VBox(10, label, buttons, new TextField());
root.setPadding(new Insets(10));
runOnFXThreadButton.setOnAction(event -> {
for (int i=1; i<=10; i++) {
label.setText("Count: "+i);
try {
Thread.sleep(250);
} catch (InterruptedException exc) {
throw new Error("Unexpected interruption");
}
}
});
runInTaskButton.setOnAction(event -> {
Task<Void> task = new Task<Void>() {
#Override
public Void call() throws Exception {
for (int i=1; i<=10; i++) {
updateMessage("Count: "+i);
Thread.sleep(250);
}
return null ;
}
};
task.messageProperty().addListener((obs, oldMessage, newMessage) -> label.setText(newMessage));
new Thread(task).start();
});
primaryStage.setScene(new Scene(root, 400, 225));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Related
I am new to JAVA FX, I want to 'live' update one TextField based on another TextField value.
This is my snippet code:
#FXML
private void initialize() {
tf_code.textProperty().addListener((observable, oldValue, newValue) -> {
System.out.println(newValue.substring(2, 6));
tf_newCode.setText(newValue.substring(2, 6));
});
}
Should I add another listener to my second TextField ?
Works for me. Note that the below code does not require a .fxml file. Perhaps the call to method substring() in the code you posted is throwing an Exception that you are unaware of because you are catching it in an empty catch block? Of-course I'm only guessing since you only posted part of your code.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class JfxTst00 extends Application {
public void start(Stage mainStage) throws Exception {
mainStage.setTitle("JfxTst00");
BorderPane root = new BorderPane();
TextField tf_NewCode = new TextField();
TextField tf_Code = new TextField();
tf_Code.textProperty().addListener((observable, oldVal, newVal) -> tf_NewCode.setText(newVal));
root.setTop(tf_Code);
root.setBottom(tf_NewCode);
Scene scene = new Scene(root, 220, 70);
mainStage.setScene(scene);
mainStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Your questions does not actually explain the problem you're facing, though I see a few that you should be having.
First of all, you only need one listener for the first TextField because that is the one we are watching for changes.
Then you need to account for input into the TextField that is less than 2 characters and more than 6. Since you have set hard limits in your subString(2, 6) call, we only want our listener to work within those constraints.
Here is a simple text application that demonstrates:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TextFieldBinding extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
// Simple interface
VBox root = new VBox(5);
root.setPadding(new Insets(10));
root.setAlignment(Pos.CENTER);
TextField txt1 = new TextField();
TextField txt2 = new TextField();
txt1.textProperty().addListener((observable, oldValue, newValue) -> {
// First, only update txt2 if the new value entered in txt1 is greater than 2, otherwise
// our substring() method will throw an exception
if (newValue.length() > 2) {
// We also need to prevent trying to get a substring that exceeds the remaining length
// of the txt1 input
int maxIndex;
if (newValue.length() < 6) {
maxIndex = newValue.length();
} else {
maxIndex = 6;
}
// Now set the text for txt2
txt2.setText(newValue.substring(2, maxIndex));
}
});
root.getChildren().addAll(txt1, txt2);
// Show the Stage
primaryStage.setWidth(300);
primaryStage.setHeight(300);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
really liking JavaFX but have come across this problem and wondered if it was a bug.
The ScrollBar.setOnMousePressed() doesn't seem to fire when it has been initialised with a handler. The code below demonstrates the problem:-
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollBar;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Play extends Application {
public static void main(String[] args) {
launch(args);
}
private static int cnt;
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Bug?");
Button btn = new Button("This text will get replaced by the event handlers");
ScrollBar scrollBar = new ScrollBar();
// When pressing and releasing the ScrollBar thumb, we only get decrements
// If you replace the ScrollBar with say a Button, then the code below works as you might expect.
scrollBar.setOnMousePressed( event -> btn.setText("X" + cnt++));
scrollBar.setOnMouseReleased( event -> btn.setText("X" + cnt--));
VBox root = new VBox();
root.getChildren().add(btn);
root.getChildren().add(scrollBar);
primaryStage.setScene(new Scene(root, 350, 250));
primaryStage.show();
}
}
Note, Im running on JDK 1.8.0_66 64 Bit on Microsoft Windows 10.
A simple workaround, as suggested by James_D, is to use EventFilters instead of setOnMousePressed(), as follows:-
So,
scrollBar.addEventFilter(MouseEvent.MOUSE_PRESSED,
event -> btn.setText("X" + cnt++));
instead of
scrollBar.setOnMousePressed( event -> btn.setText("X" + cnt++));
I believe .setOnMousePressed() should work, but doesn't because of a bug in the library. I've raised with oracle and will update the answer once oracle clarifies.
I have trouble when closing a window in JavaFX.
I define my setOnCloseRequest as I wanted and it works when I click the x in the window. However, I also need a button to close the window and this onCloseRequest has to work, the problem is it does not. The event does not fire at all.
I am using JavaFX 2.2 (Java 7) and I notice that the reference for setOnCloseRequest says close the window on external request
Solution
Fire an event from your internal close request (on the button push), so that the application thinks it received an external close request. Then your close request logic can be identical whether the request came from an external event or an internal one.
private EventHandler<WindowEvent> confirmCloseEventHandler = event -> {
// close event handling logic.
// consume the event if you wish to cancel the close operation.
}
...
stage.setOnCloseRequest(confirmCloseEventHandler);
Button closeButton = new Button("Close Application");
closeButton.setOnAction(event ->
stage.fireEvent(
new WindowEvent(
stage,
WindowEvent.WINDOW_CLOSE_REQUEST
)
)
);
Note
This is a Java 8+ solution, for JavaFX 2, you will need to convert the lambda functions in anonymous inner classes and will be unable to use the Alert dialog box but will need to provide your own alert dialog system as JavaFX 2 does not feature an in-built one. I strongly recommend upgrading to Java 8+ rather than staying with JavaFX 2.
Sample UI
Sample Code
The sample code will show the user a close confirmation alert and cancel the close request if the user does not confirm the close.
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.StackPane;
import javafx.stage.*;
import javafx.stage.WindowEvent;
import java.util.Optional;
public class CloseConfirm extends Application {
private Stage mainStage;
#Override
public void start(Stage stage) throws Exception {
this.mainStage = stage;
stage.setOnCloseRequest(confirmCloseEventHandler);
Button closeButton = new Button("Close Application");
closeButton.setOnAction(event ->
stage.fireEvent(
new WindowEvent(
stage,
WindowEvent.WINDOW_CLOSE_REQUEST
)
)
);
StackPane layout = new StackPane(closeButton);
layout.setPadding(new Insets(10));
stage.setScene(new Scene(layout));
stage.show();
}
private EventHandler<WindowEvent> confirmCloseEventHandler = event -> {
Alert closeConfirmation = new Alert(
Alert.AlertType.CONFIRMATION,
"Are you sure you want to exit?"
);
Button exitButton = (Button) closeConfirmation.getDialogPane().lookupButton(
ButtonType.OK
);
exitButton.setText("Exit");
closeConfirmation.setHeaderText("Confirm Exit");
closeConfirmation.initModality(Modality.APPLICATION_MODAL);
closeConfirmation.initOwner(mainStage);
// normally, you would just use the default alert positioning,
// but for this simple sample the main stage is small,
// so explicitly position the alert so that the main window can still be seen.
closeConfirmation.setX(mainStage.getX());
closeConfirmation.setY(mainStage.getY() + mainStage.getHeight());
Optional<ButtonType> closeResponse = closeConfirmation.showAndWait();
if (!ButtonType.OK.equals(closeResponse.get())) {
event.consume();
}
};
public static void main(String[] args) {
launch(args);
}
}
Is there any possible way to wait for the scene to repaint?
My problem is, that i want to add a Note to a Pane with getChildren().add() and then to fire an event on this Node with Node.fireEvent(event).
But the event is not performed. I think the problem is, that the scene was not repainted at the point of the fireevent and so the Node is not a part of the new Scene at this time.
So the best way would be to wait for the scene to repaint and then fire the event i think.
I do not know which is the UI Component you has been used here, but, try to find out the invalidate() method (or something like that) of your component to make it update the screen after all.
Can you post code? This works fine for me:
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class NewNodeEventTest extends Application {
#Override
public void start(Stage primaryStage) {
HBox root = new HBox(5);
Button newNodeButton = new Button("Add button");
newNodeButton.setOnAction(event -> {
Button newButton = new Button("Button");
newButton.setOnAction(e -> System.out.println("New button pressed"));
root.getChildren().add(newButton);
ActionEvent evt = new ActionEvent(newButton, newButton);
newButton.fireEvent(evt);
});
root.getChildren().add(newNodeButton);
Scene scene = new Scene(root, 250, 100);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I could use some help debugging a memory (leak?) issue. I've made a simple example below. There's some kind of bug in Javafx related to TextFields. The below code adds 2000 TextFields to a FlowPane within a ScrollPane. According to task manager, Java uses ~420mb at this point.
Pressing the add button adds another 2000 TextFields every time it is pressed. Each time adds maybe 80-200 mb (somehow it's not always the same amount of memory??). The remove button removes the TextFields, but memory is never freed. This is with Java jdk 9 where - as far as I understand things - the GC should free up memory that is no longer in use and return it to the OS. Changing TextFields into Texts solves the issue, takes far less memory and actually returns it to the OS when appropriate, but I would prefer to have TextFields. Does anyone know how to fix/work around this? :-)
import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class main extends Application
{
private ScrollPane scroll;
private FlowPane pane;
private Scene scene;
private Stage stage;
#Override
public void start(Stage stage) throws Exception
{
try
{
this.stage=stage;
pane = new FlowPane();
Button b1 = new Button("Add 2000");
Button b = new Button("Remove 2000");
b1.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent e) {
addTextFields();
}});
b.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent e) {
removeTextFields();
System.gc();
}});
pane.getChildren().add(b);
pane.getChildren().add(b1);
scroll = new ScrollPane();
scroll.setContent(pane);
addTextFields();
scene = new Scene(scroll,800,600);
stage.setScene(scene);
stage.show();
}
catch(Exception e)
{
e.printStackTrace();
}
}
private void addTextFields()
{
for(int i=0; i < 2000; i++)
{
//Text text = new Text("T " + i);
TextField textField = new TextField("T "+i);
this.pane.getChildren().add(textField);
}
}
private void removeTextFields()
{
for(int i=2001; i>1; i--)
{
// Text f = (Text) this.pane.getChildren().get(i);
TextField f = (TextField) this.pane.getChildren().get(i);
this.pane.getChildren().remove(f);
}
}
public static void main(String[] args)
{
launch(args);
}
}
Apparently, it's perfectly normal for java 9 to e.g. keep taking up over a GB in heap space memory with only a basic stage, scrollpane and an emptied flowpane in play (that used to contain several thousand TextFields). The memory is never released back to the OS unless forced within VisualVM even after calling the GC explicitly from code.
This GC behaviour doesn't make any sense to me, particularly on a poor man's system with only 4 gb of ram that is already mostly in use before I run Java, but I'll work around it by either using Texts or using a TableView.