JavaFX updating controller property from another controller - java

I have a progress bar that I am trying to update within javaFX in a controller. I am updating the progress bar based on a function called Generate(), if it is called it should update the progress bar within the main controller. However, the code I have doesn't update it, rather it updates a new instance of the progress bar.
The Generate method in my DrawerContentController is :
try {
AnchorPane ap = fxmlLoader.load();
for(Node node: ap.getChildren()){
if(node.getId().equals("progressBar")){
progressBar = (ProgressBar) node;
progressBar.setProgress(50);
}
}
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
In my main controller I have the progressBar set up via fxml using scene builder, i'm trying to update the progressBar from DrawerContentController, which is essentially a sidebar menu that consists of 3 buttons, one of which is generate that calls the Generate() method. I know I should probably be using threads, I am a beginner to JavaFX and still learning on how to use it fully.
I've also tried :
FXMLLoader fxmlLoader = new FXMLLoader((getClass().getResource("layout.fxml")));
and then declaring the controller and instantiating it by
FXMLDocumentController fxmldc = fxmlLoader.getController();
and then assessing the property, however I get a npe this way.
My FXMLDocumentController
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
//Load Splash screen
if (!MainClass.isSplashLoaded)
loadSplashScreen();
//load drawer content
try {
VBox box = FXMLLoader.load(getClass().getResource("drawerContent.fxml"));
drawer.setSidePane(box);
HamburgerBasicCloseTransition transition = new HamburgerBasicCloseTransition(hamburger);
transition.setRate(-1);
hamburger.addEventHandler(MouseEvent.MOUSE_CLICKED, (e) -> {
transition.setRate(transition.getRate() * -1);
transition.play();
if (drawer.isShown()) {
drawer.close();
mainText.setVisible(true);
} else {
drawer.open();
mainText.setVisible(false);
}
});
} catch (IOException e1) {
e1.printStackTrace();
}
}

Just create an observable property in DrawerContentController for the progress:
public class DrawerContentController implements Initializable {
private final DoubleProperty progress = new SimpleDoubleProperty();
public DoubleProperty progressProperty() {
return progress ;
}
public final double getProgress() {
return progressProperty().get();
}
public final void setProgress(double progress) {
progressProperty().set(progress);
}
// existing code...
}
Now you can bind your progress bar's progress property to the controller's progress property:
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
//Load Splash screen
if (!MainClass.isSplashLoaded)
loadSplashScreen();
//load drawer content
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("drawerContent.fxml"));
VBox box = loader.load();
DrawerContentController drawerContentController = loader.getController();
progressBar.progressProperty().bind(drawerContentController.progressProperty());
drawer.setSidePane(box);
// ... existing code
}
// ...
}
Now in your DrawerContentController class, if you do this.setProgress(...) (updating the new progress property you defined), it will automatically update the progress bar.

Related

Cannot get JavaFx Stage from Node because class com.sun.javafx.stage.EmbeddedWindow cannot be cast to class javafx.stage.Stage

I am trying to close the current fxml to move to the next one. I followed the reply from this question: close fxml window by code, javafx:
#FXML private javafx.scene.control.Button closeButton;
#FXML
private void closeButtonAction(){
// get a handle to the stage
Stage stage = (Stage) closeButton.getScene().getWindow();
// do what you have to do
stage.close();
}
And I encountered the same problem as the unanswered comment below it:
Exception in thread "JavaFX Application Thread" java.lang.ClassCastException: com.sun.javafx.stage.EmbeddedWindow cannot be cast to javafx.stage.Stage
All of the other answers also doesn't help. There are little discussion on EmbeddedWindow so I have no clue on what to do next. The previous screen was made with javax.swing, not JavaFx, and the transition is as follow:
import javafx.embed.swing.JFXPanel;
// more code
JFXPanel fxPanel = new JFXPanel();
this.add(fxPanel);
this.setTitle("Title");
this.setSize(1024, 768);
this.setVisible(true);
Platform.runLater(new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("<String url to my fxml file>"));
ScreenController controller = new ScreenController();
loader.setController(controller);
Parent root = loader.load();
fxPanel.setScene(new Scene(root));
} catch (IOException e) {
e.printStackTrace();
}
}
});
// more code
By the time I'm done writing the context, I think the problem may lie in the usage of JFXPanel, but I can't find a solution either. So helps are appreciated. Thanks!
To close the containing JFrame in a mixed Swing and JavaFX application, from a JavaFX controller, you need to provide the controller with sufficient information to close the window. Probably the best way to decouple this properly is to just have a Runnable in the controller that knows how to close the window:
public class ScreenController {
private Runnable windowCloser ;
public void setWindowCloser(Runnable windowCloser) {
this.windowCloser = windowCloser;
}
// ...
#FXML
private void closeButtonAction(){
// do what you have to do
// close the current window:
windowCloser.run();
}
// ...
}
And then:
JFXPanel fxPanel = new JFXPanel();
this.add(fxPanel);
this.setTitle("Title");
this.setSize(1024, 768);
this.setVisible(true);
// Assuming this is a JFrame subclass
// (otherwise replace "this" with a reference to the JFrame):
Runnable windowCloser = () -> SwingUtilities.invokeLater(
() -> this.setVisible(false)
);
Platform.runLater(() -> {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("<String url to my fxml file>"));
ScreenController controller = new ScreenController();
controller.setWindowCloser(windowCloser);
loader.setController(controller);
Parent root = loader.load();
fxPanel.setScene(new Scene(root));
} catch (IOException e) {
e.printStackTrace();
}
});
You should also consider just loading the FXML into the current JFXPanel, which is much easier:
public class ScreenController {
// ...
#FXML
private Button closeButton;
#FXML
private void closeButtonAction(){
Parent root = /* load next FXML */ ;
closeButton.getScene().setRoot(root);
}
// ...
}

Create modal dialog from scratch

I'm trying to create my own implementation for an overlay dialog that opens up when the user clicks a button. The code you see below works perfectly fine but is not that pretty. I'm searching for an implementation where I don't have to create a Thread for each dialog I create. Is there any way to acchieve this?
I've been browsing through various Java source files like JOptionPane and JDialog to figure out what they do in order to block the thread until the user closes the dialog, but I didn't manage to understand it. Additionally I tried various code snippets including the EventQueue like for example EventQueue.invokeLater or EventQueue.invokeAndWait.
// MainViewController.java
#FXML
private void handleServerButton(ActionEvent evt){
Thread t = new Thread(() -> {
if (serverD.showDialog(overlay) == Dialog.OK_OPTION){
System.out.println("OK");
} else {
System.out.println("ABORT");
}
});
t.start();
}
// Dialog.java
public int showDialog(Pane parent) {
latch = new CountDownLatch(1);
this.result.set(NONE);
approveButton.setDefaultButton(true);
abortButton.setCancelButton(true);
container.setVisible(true);
parent.setVisible(true);
try {
latch.await();
} catch (InterruptedException ex){ }
approveButton.setDefaultButton(false);
abortButton.setCancelButton(false);
container.setVisible(false);
parent.setVisible(false);
return result.get();
}
#Override
public void changed(ObservableValue<? extends Integer> observable, Integer oldValue, Integer newValue) {
if (newValue != NONE)
latch.countDown();
}
This is what it looks like (please note: the overlay dialog is not a window itself but rather a pane within the main window):
Final Result
Look at the Alert and Dialog documentation. Both provide functionality similar to what you want, and both can be customised if they don't quite match your use case.
Quick example:
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setTitle("title");
alert.setContent("content");
...
// More customisation available, read the docs
alert.show();. // Or showAndWait()
I solved the problem by deriving a Dialog class from Stage and implementing the logic there. The only thing that is left, is to extract the values from the controls of the view controller. But I already noticed that the dialog is passed as a Window through the ActionEvent - so that should be a minor issue.
public class Dialog extends Stage {
public static int OK_OPTION = 0;
public static int ABORT_OPTION = -1;
private int result;
public Dialog(Scene parent, String url) throws IOException{
super(StageStyle.TRANSPARENT);
Parent root = FXMLLoader.load(getClass().getResource(url));
Scene scene = new Scene(root);
if (System.getProperty("os.name").equals("Mac OS X")){
root.setStyle("-fx-background-radius: 0 0 3px 3px;");
}
scene.setFill(Color.TRANSPARENT);
setScene(scene);
initOwner(parent.getWindow());
double titlebar = parent.getWindow().getHeight() - parent.getHeight();
setX(parent.getWindow().getX());
setY(parent.getWindow().getY() + titlebar + 50);
}
public int showDialog(){
showAndWait();
return result;
}
}

Java FX - Progress bar update

I have a method that takes a while to complete, when the jar application is started. To have some feedback, i created the form frmWaiting, that displays a simple indeterminate progress bar. I also have a controller for the form,
PrincipalController.
Entry point for the application
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
Stage stagePrincipal = new Stage();
Parent parentPrincipal = FXMLLoader.load(getClass().getClassLoader().getResource("frmPrincipal.fxml"));
Scene scenePrincipal = new Scene(parentPrincipal, 300, 275);
stagePrincipal.setScene(scenePrincipal);
stagePrincipal.setHeight(400);
stagePrincipal.setWidth(500);
stagePrincipal.setResizable(false);
stagePrincipal.setTitle("Instalador");
stagePrincipal.show();
} catch (Exception e) {
e.printStackTrace();
}
}
PrincipalController - frmPrincipal.fxml:
#Override
public void initialize(URL location, ResourceBundle resources) {
try {
Stage stageWaiting = new Stage();
Parent parentWaiting;
parentWaiting = FXMLLoader.load(getClass().getClassLoader().getResource("frmWaiting.fxml"));
Scene sceneWaiting = new Scene(parentWaiting, 300, 275);
stageWaiting.setScene(sceneWaiting);
stageWaiting.setHeight(300);
stageWaiting.setWidth(400);
stageWaiting.setResizable(false);
stageWaiting.setTitle("Instalador");
stageWaiting.show();
} catch (IOException e) {
}
}
WaitingController - frmWaiting.xml:
public class WaitingController implements Initializable {
#FXML private ImageView img;
#FXML private ProgressBar progressBar;
#FXML private ProgressIndicator pgIndicator;
private Task copyTask;
#Override
public void initialize(URL location, ResourceBundle resources) {
img.setImage(new Image(getClass().getClassLoader().getResourceAsStream("image.png")));
progressBar.setProgress(ProgressBar.INDETERMINATE_PROGRESS);
ArquivoController.getInstance().copiaArquivosPadrao(); //This is the method that takes a while.
}
public ProgressBar getProgressBar() {
return progressBar;
}
I want to initialize my main form, frmPrincipal, when my method that takes a while finishes. I also want to get the progress bar working. I have tried to do it on another Thread, but i could not get the response from it when the method finishes.
All the .fxml files are correct, ommited them to make things easier if possible.
The way it is, the application waits for the method to finish, then opens the other form. But, the progressBar does not update.
Inside ArquivoController.getInstance().copiaArquivosPadrao(); you have to update the progressBar's progress.
You could do it the nice way, using a Task to run copiaArquivosPadrao(), update the tasks progress accordingly and binding to the tasks progress property.
Or you could do it the ugly way, passing progressBar to copiaArquivosPadrao(), something like this:
public void initialize(URL location, ResourceBundle resources) {
img.setImage(new Image(getClass().getClassLoader().getResourceAsStream("image.png")));
progressBar.setProgress(ProgressBar.INDETERMINATE_PROGRESS);
ArquivoController.getInstance().copiaArquivosPadrao(progressBar); //This is the method that takes a while.
}
and
public void copiaArquivosPadrao(ProgressBar progressBar) {
// call progressBar.setProgress() in here to update the progress bar
// eg.
progressBar.setProgress(0.0F);
doSomething();
progressBar.setProgress(0.20F);
doSomething();
progressBar.setProgress(0.40F);
doSomething();
progressBar.setProgress(0.60F);
doSomething();
progressBar.setProgress(0.80F);
doSomething();
progressBar.setProgress(1.00F);
}
For sure, you can do this more fine-grained in a loop or similar.

Create an edit window that pulls info from previous window ListView

I am new to programming (6 months or so). I am working on a basic application for fun and GUI experience in JavaFX. I am currently looking for a way to open a "View/Edit Account" screen. I a previous window, I have a listview box that displays the names of accounts that i have in an arraylist (Im using text files as a way to save, as i havent ventured into SQL yet). The goal is to be able to click on the name of an array object, hit edit, and that new window opens up some GUI with more thorough details about the object you just clicked on, and even allow you to edit the variables. I currently utilize the selectionmode methods that are built in with javaFX to load the objext i click on into a person variable, i just dont know how to get that to carry over to a new dialog window. Here is some of my code (Is the listView windows controller) p.s. i apologize if its sloppy. Ive had a lot of trial and error:
public class accountController {
public List<accountObj> myList;
#FXML
private ListView<accountObj> test;
#FXML
AnchorPane newAccountPane;
public void initialize () { //initializes the code. Seems similar to a main class
test.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<accountObj>() {//adds a listener to update the info of what is selected
#Override
public void changed(ObservableValue<? extends accountObj> observable, accountObj oldValue, accountObj newValue) {
if (newValue != null) {//means if something is selected then it pulls in the info of what is selected in the list
accountObj person = test.getSelectionModel().getSelectedItem();
}
}
});
test.setItems(DataTwo.getInstanceTwo().getAccountObjs());
test.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
test.getSelectionModel().selectFirst();
}
#FXML
public void handleClicktest () {
accountObj person = (accountObj) test.getSelectionModel().getSelectedItem();
}
public void showViewAccount() {//shows the new account screen.
Dialog<ButtonType> dialog2 = new Dialog<>();
dialog2.initOwner(newAccountPane.getScene().getWindow());
FXMLLoader fxmlLoader2 = new FXMLLoader();
fxmlLoader2.setLocation(getClass().getResource("viewAccount.fxml"));
try {
dialog2.getDialogPane().setContent(fxmlLoader2.load());
} catch (IOException e) {
System.out.println("Couldnt load the dialog");
e.printStackTrace();
return;
}
dialog2.getDialogPane().getButtonTypes().add(ButtonType.OK);//these add the ok and cancel buttons to the window
dialog2.getDialogPane().getButtonTypes().add(ButtonType.CANCEL);
Optional<ButtonType> result = dialog2.showAndWait();
if (result.isPresent() && result.get() == ButtonType.OK) {
viewAccountController controller = fxmlLoader2.getController();
}}
public void showNewAccount() {//shows the new account screen.
Dialog<ButtonType> dialog = new Dialog<>();
dialog.initOwner(newAccountPane.getScene().getWindow());
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation(getClass().getResource("newAccount.fxml"));
try {
dialog.getDialogPane().setContent(fxmlLoader.load());
} catch (IOException e) {
System.out.println("Couldnt load the dialog");
e.printStackTrace();
return;
}
dialog.getDialogPane().getButtonTypes().add(ButtonType.OK);//these add the ok and cancel buttons to the window
dialog.getDialogPane().getButtonTypes().add(ButtonType.CANCEL);
Optional<ButtonType> result = dialog.showAndWait();
if (result.isPresent() && result.get() == ButtonType.OK) {
newAccountController controller = fxmlLoader.getController();
accountObj newPerson=controller.processResults2();
test.getSelectionModel().select(newPerson);
}
}
Change the declaration of the showViewAccount method to
public void showViewAccount(accountObj person)
Next, in the body of the handleClicktest method, you can pass the person argument to the showViewAccount method
It should look like this
public void handleClicktest () {
accountObj person = (accountObj) test.getSelectionModel().getSelectedItem();
showViewAccount(person);
}

JavaFX 2 Window event handling in controllers

So I am trying to handle WINDOW_SHOWN event from my controller with code like this:
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
initializeDatePickers();
System.out.println("payer number in initialize: " + payerNumber);
URL location = getClass().getResource("/createUser.fxml");
FXMLLoader loader = new FXMLLoader();
try {
Parent root = (Parent) loader.load(location.openStream());
root.getScene().getWindow().setOnShown(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent event) {
System.out.println("ONSHOWN");
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
But all I've got was endless cycle and program crash.
The code below didn't work either, it returns NullPointerException:
#FXML private AnchorPane createUserDialog; //my root pane
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
createUserDialog.getScene().getWindow().addEventHandler(WindowEvent.WINDOW_SHOWN,
new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent window) {
System.out.println("ONSHOWN");
}
});
}
Implementing WindowEvent interface didn't work at all, don't know why.
So, how could I handle this event? And why I've got NullPointerException? In docs said that initialize() calling only after root pane completely processed.
When the initialize() method is being executed, the root pane is completely constructed but is not added to a scene, or a window. (The initialize() method is executed as part of the execution of your FXMLLoader's load() method; check the code where you call that and you will see that you add the root to a scene and place it in a window after that.) So during the execution of intialize(), root.getScene() will return null.
You can use a Binding to check when the window changes and attach a listener to it:
final EventHandler<WindowEvent> shownHandler = new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent event) {
System.out.println("Shown");
}
};
Bindings.<Window>select(createUserDialog.sceneProperty(), "window").addListener(new ChangeListener<Window>() {
#Override
public void changed(ObservableValue<? extends Window> observable,
Window oldValue, Window newValue) {
if (oldValue != null) {
oldValue.removeEventHandler(WindowEvent.WINDOW_SHOWN, shownHandler);
}
if (newValue != null) {
newValue.addEventHandler(WindowEvent.WINDOW_SHOWN, shownHandler);
}
}
});
This code assumes the root is only ever added to one window; in the unlikely event you're taking the root out of one window and putting it in another during your application life cycle, you would need to remove the listener from the old window. If you need this I'll update the code, but it makes it more complex.

Categories