Get values from modal JavaFX - java

I am working on a map application in JavaFX. The idea is that the user should be able to update details of areas on the map. The map is a static image with invisible panes layered over it. I have a button in a form which will open a view of the map as a modal with relevant areas highlighted. When I select an area, the ID of that area is stored in a different class to be accessed and the modal closes, but what I would really like is to return the value to the controller of the form and trigger an event to change a label on the form.
Method call to display the map (contained within the controller of the form):
#FXML
private void selectArea()
{
Main.viewLoader.displayRootSelection();
}
My view loader:
public void displayRootSelection(){
Stage window = new Stage();
currentWindow = window;
Main.setRootInSelection(true);
try {
BorderPane root = FXMLLoader.load(getClass().getResource("../views/root/Root.fxml"));
window.setResizable(false);
window.initModality(Modality.APPLICATION_MODAL);
window.setTitle("WIT Map");
Scene scene = new Scene(root, 1000, 600);
Main.setScene(scene);
window.setScene(scene);
window.showAndWait();
} catch (IOException e) {
e.printStackTrace();
}
}
And the event handler on the panels on the map:
#FXML
private void panelClicked(Event e)
{
if (Main.isRootInSelection()){
String tempId = AreaManagement.findArea((Node)e.getSource());
AreaManagement.setTempAreaId(tempId);
viewLoader.getCurrentWindow().close();
}
System.out.println(AreaManagement.findArea((Node) e.getSource()));
}
So what I am trying to do is get the tempId from the event handler in the controller for the map to the controller of the form and also trigger an event in the form.
Any help would be appreciated.

My understanding of your issue, correct me if I am wrong:
You open a modal window, using window.showAndWait(), then after closing the window you need to get the selected result from that modal window.
Under the assumption that AreaManagement is available within your displayRootSelection() Method, following solution should solve your problem.
Documentation for window.showAndWait():
Shows this stage and waits for it to be hidden (closed) before returning to the caller.
You can call any further handling right after that method call and safely assume that the modal window is closed. See:
public void displayRootSelection(Consumer<String> callback){//EDIT, usage see below
Stage window = new Stage();
currentWindow = window;
Main.setRootInSelection(true);
try {
BorderPane root = ...;
/*...*/
window.showAndWait();
// When the code reaches this position, your modal window is closed
String tempId = AreaManagement.getTempAreaId();
// You can call just about anything here
callback.accept(tempId);
} catch (IOException e) {
e.printStackTrace();
}
}
EDIT: get that method call back to the controller:
In the controller:
#FXML
private void selectArea()
{
Main.viewLoader.displayRootSelection((selectedId) -> {
// Do something with the ID..
});
}
Alternatively, you can create an anonymous class instead of using a lambda here.

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);
}
// ...
}

Updating the page view after closing the modal window (Vaadin 8)

I use Vaadin version 8.9.3. I need to show a modal window when I click a button. In this window, the user enters the information, clicks on the button, the information is saved and displayed in a table in the main window.
Main page:
Modal page:
To display the modal window I use BrowserWindowOpener. In order not to overload the question, I will give only a small piece of code. The FormLayout in which there is TextField("uid"), Grid and Button("Создать") - DeviceForm:
private BrowserWindowOpener opener = new BrowserWindowOpener(ButtlonClickUI.class);
private DeviceConfigsService configsService = DeviceConfigsService.getInstance();
private Grid<DeviceConfigs> grid = new Grid<>(DeviceConfigs.class);
public DeviceForm(MyUI myUI, Devices device) {
opener.extend(button);
opener.setFeatures("resizable");
configsService.setDevice(device);
configsService.addSaveEventListener(new OnSaveEventListener() {
#Override
public void SaveEvent() {
updateList();
}
});
grid.setColumns(NAME_COLUMN, VERSION_COLUMN, STATE_COLUMN);
grid.getColumn(NAME_COLUMN).setCaption(NAME_COLUMN_NAME).setExpandRatio(1);
grid.getColumn(STATE_COLUMN).setCaption(STATE_COLUMN_NAME).setExpandRatio(1);
grid.getColumn(VERSION_COLUMN).setCaption(VERSION_COLUMN_NAME).setExpandRatio(1);
updateList();
}
public void updateList() {
List<DeviceConfigs> configs = configsService.findAll();
if(configs.size() == 0) {
delete.setVisible(false);
}
grid.setItems(configs);
}
Here, config service is a service that allows you to save, delete and find the information displayed in the grid (DeviceConfigs), in this case, it does not matter which one. OnSaveEventListener is the listener I created, called when the save method in configsService is called:
public synchronized void save(DeviceConfigs entry) {
if(entry == null) {
LOGGER.log(Level.SEVERE,
"DeviceConfigs is null");
return;
}
if(entry.getName() == null || entry.getName().isEmpty()) {
LOGGER.log(Level.SEVERE,
"DeviceConfigs name is null");
}
try {
entry = (DeviceConfigs) entry.clone();
} catch (Exception e) {
throw new RuntimeException(e);
}
device.putConfig(entry);
if(listener != null) { listener.SaveEvent(); }
}
UI that is called in opener:
public class ButtlonClickUI extends UI {
private DeviceConfigsService configsService = DeviceConfigsService.getInstance();
private Button close = new Button("close", VaadinIcons.CLOSE);
#Override
protected void init(VaadinRequest request) {
VerticalLayout layout = new VerticalLayout();
layout.addComponent(close);
...
close.addClickListener(event ->{
configsService.save(new DeviceConfigs(requestStr.getValue(), true, typeOfClick.getValue()));
closeThis();
});
}
private void closeThis() {
JavaScript.eval("close()");
// Detach the UI from the session
getUI().close();
}
}
The problem is this - I couldn't think of a better way to track the event of writing new data and closing the modal window to update the values of the table until I got to creating a listener.
But now, after clicking the Close button in the modal window, it closes, the data is updated but not displayed until I interact with some element on the main page (by trial and error, I got to the point where the components on the main page will not update their visibility until the modal window closes and the main page returns focus).
But I can't think of any way to automatically update the table values in the main menu when the modal window is closed.
Any possible solution to the problem, please.

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 - Block user from changing stages without using MODAL

I have an application that looks like the following:
When a user clicks on the deck of cards, it opens up a new Stage.
This stage can be closed in one of two ways:
Right click the stage.
Click outside of the stage (it has a evenhandler for when it loses focus).
However, sometimes I NEED the user to select one or more cards from the deck using this window. I do not want to allow him to close the window until he has selected at least one card. This means I had to use MODAL to stop him from being able to access the stage underneath (My Applicaiton). The problem with MODAL is now he can never leave the window like he could before by clicking outside the stage, even when I want him to be able to. He is now only able to leave through right clicking. I could add a button but I'd really rather not.
I hope I explained my problem well enough. What would you guys recommend I do? Is there a way I could somehow block the user from going back to the previous stage without MODAL? I'm also not able to change Modality after the Stage has been shown, so that won't work.
Thanks!
The idea is to use the onCloseRequestProperty property of your pop-up Stage.
Called when there is an external request to close this Window. The
installed event handler can prevent window closing by consuming the
received event.
With this property you can interrupt the closing of the Stage if a condition (in your case at lest one card is selected) is not met by calling consume on the WindowEvent.
Note: As the documentation states: it is only valid if the request is external, so if you call the close method of the Stage, the attached listener will be not executed. As a solution rather than calling this method you can fire the WindowEvent.WINDOW_CLOSE_REQUEST event manually.
Example:
public class PopUpApp extends Application {
Stage popupStage;
Stage primaryStage;
#Override
public void start(Stage stage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root, 400, 400);
primaryStage = stage;
initPopUpStage();
// When the Pop-Up stage is showing, do not handle any action on the
// main GUI
root.disableProperty().bind(popupStage.showingProperty());
Button b = new Button("Open deck");
b.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
// Add some ToggleButtons to simulate the cards
VBox vbox = new VBox();
vbox.setAlignment(Pos.CENTER);
List<ToggleButton> toggles = new ArrayList<ToggleButton>();
for (int i = 0; i < 4; i++) {
ToggleButton tb = new ToggleButton("Card " + i + 1);
toggles.add(tb);
}
vbox.getChildren().addAll(toggles);
Scene sc = new Scene(vbox, 300, 300);
popupStage.setScene(sc);
// On close request check for the condition
popupStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent event) {
Boolean readytoClose = false;
for (ToggleButton toggle : toggles) {
if (toggle.isSelected()) {
readytoClose = true;
break;
}
}
// Consume the event a show a dialog
if (!readytoClose) {
event.consume();
Alert alert = new Alert(AlertType.INFORMATION,
"At least one card has be to be selected!");
alert.showAndWait();
}
}
});
popupStage.show();
}
});
root.setCenter(b);
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
private void initPopUpStage() {
popupStage = new Stage();
popupStage.initOwner(primaryStage);
popupStage.initStyle(StageStyle.UNDECORATED);
// On focus loss, close the window
popupStage.focusedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
// Rather than popupStage.close(); fire the event manually
if (!newValue)
popupStage.fireEvent(new WindowEvent(popupStage, WindowEvent.WINDOW_CLOSE_REQUEST));
}
});
}
public static void main(String[] args) {
launch(args);
}
}
Update:
To make the main Stage unavailable I have added this line:
root.disableProperty().bind(popupStage.showingProperty());
This will disable the root BorderPane while the pop-up stage is showing. As soon as the pop-up window closed, the main window is enabled again.

Best way to wait for input in a for loop?

In JavaFX 2 I have a TableView beeing populated by reading an Excel file. It looks like this:
identification cellcount calved
o0001 12345 false
o0002 65432 true
o0003 55555 false
...
When users press the 'Import' button, all records have to be added to a database. However, If the 'calved' field has 'true' as value, I show a Dialog window where the users have to select a date to specify when the calving happened. Now the big question is that I want my for loop beeing paused as soon as a Dialog window is open. With my current code, all Dialog windows are stacked on eachother.
This is the Dialog method which loads an FXML:
public void showDialog(String sURL){
final Stage myDialog = new Stage();
myDialog.initStyle(StageStyle.UTILITY);
myDialog.initModality(Modality.APPLICATION_MODAL);
URL url = getClass().getResource(sURL);
FXMLLoader fxmlloader = new FXMLLoader();
fxmlloader.setLocation(url);
fxmlloader.setBuilderFactory(new JavaFXBuilderFactory());
try {
Node n = (Node) fxmlloader.load(url.openStream());
Scene myDialogScene = new Scene(VBoxBuilder.create().children(n).alignment(Pos.CENTER).padding(new Insets(0)).build());
myDialog.setScene(myDialogScene);
myDialog.show();
} catch (Exception ex) {
System.out.println(ex);
}
}
And here is the for loop where I handle the tablerows:
#FXML
private void handle_ImportCowDataButton(ActionEvent event) {
Cows selectedCow;
for(ImportRow row: tblImport.getItems()){
selectedCow = null;
for (Cows cow : olCows) {
if (cow.getOfficial().equals(row.getCownumber())) {
selectedCow = cow;
}
}
if (selectedCow != null) {
if (row.getCalving()) {
//if cow exists and cow has calved, show dialog window loading addcalving.fxml
//then the for loop should wait until that dialog window is closed before continuing
Context.getInstance().setPassthroughObject(selectedCow);
Context.getInstance().showDialog("/GUI/calving/AddCalving.fxml");
}
} else {
//if cow does not exist, show dialog window loading addcow.fxml
//then the for loop should wait until that dialog window is closed before continuing
Context.getInstance().setPassthroughObject(selectedFarmer);
Context.getInstance().showDialog("/GUI/cow/AddCow.fxml");
}
}
}
Is working with setOnCloseRequest() in my showDialog() method an option?
If you copy the cows list into another data structure such as a queue and remove each cow as it is processed, it is relatively easy to resume processing it since only the cows that need to be processed remain.
It seems the answer was much easier then I thought, simply use the showAndWait() method instead of show(). How on earth could I have missed that... Thanks for the help thou.
Final code of the showDialog() method:
public void showDialog(String sURL){
final Stage myDialog = new Stage();
myDialog.initStyle(StageStyle.UTILITY);
myDialog.initModality(Modality.APPLICATION_MODAL);
URL url = getClass().getResource(sURL);
FXMLLoader fxmlloader = new FXMLLoader();
fxmlloader.setLocation(url);
fxmlloader.setBuilderFactory(new JavaFXBuilderFactory());
try {
Node n = (Node) fxmlloader.load(url.openStream());
Scene myDialogScene = new Scene(VBoxBuilder.create().children(n).alignment(Pos.CENTER).padding(new Insets(0)).build());
myDialog.setScene(myDialogScene);
myDialog.showAndWait();
} catch (Exception ex) {
System.out.println(ex);
}
}

Categories