JavaFX Updating UI from another thread - java

I have a main class
public class Main {
public static void main(String[] args) {
Application.launch(View.class);
View view = new View();
Platform.runLater(() -> view.changeTitle());
}
}
and a view JavaFX class
public class View extends Application {
Stage primaryStage;
public View() {
}
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 300, 275));
this.primaryStage = primaryStage;
primaryStage.show();
}
public void changeTitle() {
primaryStage.setTitle("YEA!");
}
}
I want the main class to do something in JavaFX thread but my code doesn't work. In documentation said that I can call Platform.runLater() from any thread I want. If I call Platform.runLater() from JavaFX thread (in start(), for example), everything is OK.

First, main(String) is blocked on Application.launch(Class).
From Oracle Javadocs:
The launch method does not return until the application has exited, either via a call to Platform.exit or all of the application windows have been closed.
Second, you're creating a new View instance on the second line. That will NOT be same instance Application created, so your Platform.runLater wouldn't affect the launched application even if that code were reachable before it exited.

Related

How to separate the javafx GUI structures and its implementation?

For example this code:
public class VendorManagementSystem extends Application {
#Override
public void start(Stage primaryStage) {
VBox main_pane = new VBox(15);
main_pane.setPadding(new Insets(10,10,10,10));
Button addItem_mainPane = new Button("Add Item");
addItem_mainPane.setPrefWidth(120);
Button cancelation_button = new Button("Close");
cancelation_button.setPrefWidth(120);
main_pane.getChildren().addAll(addItem_mainPane,sellItem_mainPane,cancelation_button);
main_pane.setAlignment(Pos.CENTER);
Scene scene = new Scene(main_pane);
primaryStage.setHeight(450);
primaryStage.setWidth(650);
primaryStage.setTitle("Vendor Management System");
primaryStage.setScene(scene);
primaryStage.show();
Then I wanna to separate this in another class:
cancelation_button.setOnAction(e->{
primaryStage.close();
});
I tried to create another class and inherit the javafx class to create the implementation in another class:
public class ControlsImplementation extends VendorManagementSystem{
cancelation_button.setOnAction(e->{
primaryStage.close();
});
}
but it doesnt work i got those errors:
error: < identifier > expected
cancelation_button.setOnAction(e->{
error: < identifier > expected
cancelation_button.setOnAction(e->{
error: illegal start of type
});
Any suggestion how to separate the javafx gui structures and the controls implementation.
You can create separate classes for the view and for the controller. You should make both of these independent of the Application class, which has responsibility for starting the application and managing its lifecycle:
View class:
public class View extends VBox {
private final Controller controller ;
public View(Controller controller) {
this.controller = controller ;
buildUI();
}
private void buildUI() {
setPadding(new Insets(10,10,10,10));
Button addItem = new Button("Add Item");
addItem.setPrefWidth(120);
Button cancelation = new Button("Close");
cancelation.setPrefWidth(120);
cancelation.setOnAction(e -> controller.exit(this));
getChildren().addAll(addItem, /* sellItem, */ cancelation);
setAlignment(Pos.CENTER);
}
}
Controller class:
public class Controller {
public void exit(Node view) {
view.getScene().getWindow().hide();
}
}
Application class:
public class VendorManagementSystem extends Application {
#Override
public void start(Stage primaryStage) {
Controller controller = new Controller();
View view = new View(controller);
Scene scene = new Scene(view);
primaryStage.setHeight(450);
primaryStage.setWidth(650);
primaryStage.setTitle("Vendor Management System");
primaryStage.setScene(scene);
primaryStage.show();
}
}
Obviously in a real application you would have a data model in addition to the view and controller classes.
You can vary the relationship between the view and the controller according to how you want things set up, e.g. instead of giving the view a reference to the controller, you could give the controller a reference to the view, define a getCancelationButton() method in the view, and call getCancelationButton().setOnAction(...) in the controller. It just depends which variant of MVC you are trying to implement.

How to get primary Stage in Controller for mouse click? [duplicate]

This question already has answers here:
How do I open the JavaFX FileChooser from a controller class?
(4 answers)
Passing data to the controller JAVAFX
(1 answer)
Closed 5 years ago.
I have a TableView item that when clicked will launch a Toast. The first parameter is the primary stage from the Main application class.
tableViewPriority.setOnMouseClicked(event -> {
if (tableViewPriority.getSelectionModel().getSelectedItem() != null) {
Toast.makeText(primaryStage, "Toast!", 0, 1, 1);
}
});
How can I get the primaryStage from my Main class into my controller?
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
// Select main layout file
Parent root = FXMLLoader.load(getClass().getResource("/fxml/scene.fxml"));
// Add custom stylesheet URL
root.getStylesheets().add(getClass().getResource("/css/stylesheet.css").toExternalForm());
// Set Scene window params
primaryStage.setTitle("React");
primaryStage.setScene(new Scene(root, 1150, 600));
primaryStage.setResizable(false);
// Set task bar primary icon
primaryStage.getIcons().add(new javafx.scene.image.Image("/images/react-app-icon.png"));
// Show Scene
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

JavaFX stage issue with hide and show

Im building an application that shows a window that ask the user if he want to suspend the computer with two button options, one of them its a YES and the PC suspends.
The other button named "Later" supposed to hide the window and after an hour it appears again and ask the same question.
Code for the "later buttton"
noButton.setOnAction(event -> {
Done=false; //boolean to close and open the frame
Gui gui = new Gui();
try {
gui.start(classStage);
} catch (Exception e) {
e.printStackTrace();
}
});
The boolean that you see in the code is bc it was the way i think i could control that, trust me i tried in different ways but no one just help me with the issue, here is the code of the GUI class
public class Gui extends Application {
public Stage classStage = new Stage();
public static boolean Done=true;
public static boolean flag=true;
public Gui() {
}
#Override
public void start(Stage primaryStage) throws Exception {
Done = Controller.isDone();
classStage = primaryStage;
Rectangle2D primaryScreenBounds = Screen.getPrimary().getVisualBounds();
primaryStage.setX(primaryScreenBounds.getMaxX() - primaryScreenBounds.getWidth());
primaryStage.setY(primaryScreenBounds.getMaxY() - primaryScreenBounds.getHeight());
primaryStage.setAlwaysOnTop(true);
Parent root = FXMLLoader.load(getClass().getResource("MainWindow.fxml"));
primaryStage.setTitle("Alerta suspencion de equipo");
primaryStage.setScene(new Scene(root));
primaryStage.setResizable(false);
primaryStage.initStyle(StageStyle.UNDECORATED);
if (Controller.isDone() == true) {
primaryStage.show();
} else if(Controller.isDone() == false) {
primaryStage.hide();
Platform.exit(); // this is the only way that the windows close
}
}
i know that Platform.exit(); kills the program but when i only use .hide(); of the Stage nothing happens, the window never closed, the worst part is that when i use the Platform.exit() command i cant make the frame appear again...
Anyone knows a way maybe easier to hide and show a window after certain time? maybe im doing this wrong.
Regards.
It's not really clear what's going on in the code in your question. The bottom line is that you should never create an instance of Application yourself; the only Application instance should be the one created for you.
I don't actually see any need to have a separate class for the functionality you've shown (though you could, of course). All you need to do is hide classStage if the no button is pressed, and open it again in an hour:
noButton.setOnAction(event -> {
Done=false; //boolean to close and open the frame
classStage.hide();
PauseTransition oneHourPause = new PauseTransition(Duration.hours(1));
oneHourPause.setOnFinished(e -> showUI(classStage));
oneHourPause.play();
});
// ...
private void showUI(Stage stage) {
Rectangle2D primaryScreenBounds = Screen.getPrimary().getVisualBounds();
stage.setX(primaryScreenBounds.getMaxX() - primaryScreenBounds.getWidth());
stage.setY(primaryScreenBounds.getMaxY() - primaryScreenBounds.getHeight());
stage.setAlwaysOnTop(true);
Parent root = FXMLLoader.load(getClass().getResource("MainWindow.fxml"));
stage.setTitle("Alerta suspencion de equipo");
stage.setScene(new Scene(root));
stage.setResizable(false);
stage.initStyle(StageStyle.UNDECORATED);
stage.show();
}
Note that the FX Application will exit if the last window is closed, by default. So you should call Platform.setImplicitExit(false); in your init() or start() method.
I am assuming you are reloading the FXML file because the UI might have changed since it was previously loaded. Obviously if that's not the case, all you have to do is show the stage again as it is:
noButton.setOnAction(event -> {
Done=false; //boolean to close and open the frame
classStage.hide();
PauseTransition oneHourPause = new PauseTransition(Duration.hours(1));
oneHourPause.setOnFinished(e -> classStage.show());
oneHourPause.play();
});

JavaFX modal stage behaving strangely on MAC

I have a JavaFX application that has child windows or stages. The way I handle how all stages interact with each other is like this: I have a static collection of classes called GuiContainer in my Main class. The GuiContainer looks like this:
public class GuiContainer {
private final Stage stage;
private final Scene scene;
private final FXMLLoader loader;
private final Controller controller;
private final Parent parent;
public GuiContainer(Stage stage, Scene scene, FXMLLoader loader, Controller controller, Parent parent) {
this.stage = stage;
this.scene = scene;
this.loader = loader;
this.controller = controller;
this.parent = parent;
}
public Stage getStage() {
return stage;
}
public Scene getScene() {
return scene;
}
public FXMLLoader getLoader() {
return loader;
}
public Controller getController() {
return controller;
}
public Parent getParent() {
return parent;
}
public static final GuiContainer createMain(Stage stage, String title, int width, int height, String pathToView) throws Exception {
FXMLLoader loaderInner = new FXMLLoader(GuiContainer.class.getResource(pathToView));
Parent parentInner = loaderInner.load();
final Controller controllerInner = loaderInner.getController();
Scene sceneInner = new Scene(parentInner, width, height);
stage.setTitle(title);
stage.setScene(sceneInner);
GuiContainer guiContainer = new GuiContainer(
stage, sceneInner, loaderInner, controllerInner, parentInner
);
controllerInner.start(guiContainer);
return guiContainer;
}
public static final GuiContainer createModal(Window owner, String title, int width, int height, String pathToView) throws Exception {
if (owner == null) {
Log.error(GuiContainer.class.getSimpleName(), "Unable to create instance, missing window owner!");
return null;
}
Stage stageInner = new Stage();
FXMLLoader loaderInner = new FXMLLoader(GuiContainer.class.getResource(pathToView));
Parent parentInner = loaderInner.load();
Controller controllerInner = loaderInner.getController();
Scene sceneInner = new Scene(parentInner, width, height);
stageInner.setTitle(title);
stageInner.setScene(sceneInner);
stageInner.initStyle(StageStyle.DECORATED);
stageInner.initModality(Modality.WINDOW_MODAL);
stageInner.initOwner(owner);
GuiContainer guiContainer = new GuiContainer(
stageInner, sceneInner, loaderInner, controllerInner, parentInner
);
controllerInner.start(guiContainer);
return guiContainer;
}
}
This way I have access to any stage or controller from anywhere.
All possible GuiContainers are created only once at boot (in static main(String[] args)) and can then be statically accessed from anywhere, by anyone.
Now... in the main application (scene inside the primary stage) I have a TreeView that has a custom cell factory, and when a cell is right clicked an appropriate context menu is shown. Inside this context menu I open a child/modal stage, like so:
String containerName = "guiName";
GuiContainer container = Main.getGuiContainers().getOrDefault(containerName, null);
if(container == null) {
Log.error(getClass().getSimpleName(), "Unable to find GuiContainer: " + containerName);
return;
}
container.getStage().showAndWait();
Now, here comes the problem. JavaFX doesn't request focus on the child stage. For instance, I can't type into a TextField (on the child stage) because I have an onKeyPressed event registered on the primary stage and it captures all the keys I press. On this child stage I also have a ComboBox, and when I select an item from that ComboBox, the child stage finally comes into focus and the primary stage no longer captures the keys I press.
I also tried to change the modality to all posible values and read what they do on oracle.com, but none of the information helped me...
Is there something wrong with my code? Or could this be an issue with JavaFX? Any ideas?
EDIT: Here is my Application overriden start method: (I hope this provides more information)
#Override
public void start(Stage stage) throws Exception {
GuiContainer guiWindow1 = GuiContainer.createMain(stage, "Window1", 900, 460,
"/com/project/app/gui/views/Window1.fxml");
GuiContainer guiWindow2 = GuiContainer.createModal(stage, "Window2", 320, 240,
"/com/project/app/gui/views/Window2.fxml");
GuiContainer guiWindow3 = GuiContainer.createModal(stage, "Window3", 320, 240,
"/com/project/app/gui/views/Window3.fxml");
GuiContainer guiWindow4 = GuiContainer.createModal(stage, "Window4", 420, 360,
"/com/project/app/gui/views/Window4.fxml");
GuiContainer guiWindow5 = GuiContainer.createModal(stage, "Window5", 380, 460,
"/com/project/app/gui/views/Window5.fxml");
guiWindow5.getStage().setResizable(false);
guiContainers.put("guiWindow1", guiWindow1);
guiContainers.put("guiWindow2", guiWindow2);
guiContainers.put("guiWindow3", guiWindow3);
guiContainers.put("guiWindow4", guiWindow4);
guiContainers.put("guiWindow5", guiWindow5);
guiWindow1.getStage().show();
}
EDIT2: This is how I register the onKeyPressed listener: (in my parent controller init() method)
getGuiContainer().getScene().setOnKeyPressed((e) -> {
Log.info(getClass().getSimpleName(), "Key pressed: " + e.getCode().getName());
Macro macro = Main.getProject().getSelectedMacro();
if(macro == null) {
Log.error(getClass().getSimpleName(), "Unable to find selected macro");
return;
}
if (e.getCode() == KeyCode.ESCAPE) {
macro.getNodePlacement().reset();
macro.cancelSelect();
macro.repaint();
} else if(e.getCode() == KeyCode.DELETE) {
macro.deleteSelectedNodes();
}
});
EDIT3: This is how my abstract controller class looks like - I hope this provides a better insight
public abstract class Controller {
private GuiContainer guiContainer;
public final GuiContainer getGuiContainer() {
return guiContainer;
}
public final void start(GuiContainer guiContainer) {
this.guiContainer = guiContainer;
init();
}
public abstract void init(); // this is where the onKeyPressed is registered, when extending the abstract class
public abstract void reset();
public abstract void refresh();
}
IMPORTANT EDIT: This only happens on the MAC platform, I ran the same application on windows and there were no such problems.
could you please post the code of the constructor of your stage?
Just a note, from the doc:
Note that showing a modal stage does not necessarily block the caller.
The show() method returns immediately regardless of the modality of
the stage. Use the showAndWait() method if you need to block the
caller until the modal stage is hidden (closed). The modality must be
initialized before the stage is made visible.
EDIT - My mistake, I didn't notice the createModal() method.
What I ended up doing in some of my custom stages is:
Platform.runLater(() -> somecontrol.requestFocus());
(java 8, obviously, override run() method if lambda is not available with your current language level (< 8)).
But I only do this for non-modal stage. Modal stage should take the focus automatically.
According to the doc of Modality, APPLICATION_MODAL is supposed to do what you expect (including Blocking the events for other windows):
APPLICATION_MODAL Defines a modal window that blocks events from being
delivered to any other application window.
You call initModality with WINDOW_MODAL before calling initOwner. Please see:
WINDOW_MODAL public static final Modality WINDOW_MODAL Defines a modal
window that block events from being delivered to its entire owner
window hierarchy. Note: A Stage with modality set to WINDOW_MODAL, but
its owner is null, is treated as if its modality is set to NONE.
If requestFocus() (called after the modal stage has been displayed, and in the UI thread right?) doesn't take the focus, I guess your main window is automatically taking it back.

Reach a variable from another class (which is controller class of JavaFX)

I'm trying to implement a game on JavaFX. Moreover, I'm dealing with an FXML file so I have a main class and controller class. My question is how can I reach the objects of the main class from the controller class. To be more clear I will share a simple code.
This is main class:
public class JavaFXApplication1 extends Application {
#Override
public void start(Stage primaryStage) throws IOException {
Parent root = FXMLLoader.load(getClass().getResource("Risk3.fxml"));
// Main Pane
BorderPane borderPane = new BorderPane();
borderPane.setCenter(root);
// Main scene
Scene scene = new Scene(borderPane);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
So for example I want to reach root or borderPane from controller class which is:
public class SampleController implements Initializable {
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
}
Should I make root and borderPane global and static or is there any another way to reach them ?.
The root panel can simply reached from the FXML controller using
#FXML tag like any component.
<BorderPane xmlns:fx="http://javafx.com/fxml" fx:id="root">
...
</BorderPane>

Categories