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);
}
}
Related
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;
}
}
I want to automatically show an alert box again after closing it, this is based on a certain condition.
Here's my code:
protected void showInputDialog()
{
FXMLLoader loader = new FXMLLoader(getClass().getResource("AddRecordDialog.fxml"));
Parent root = loader.load();
Scene scene = new Scene(root);
AddRecordDialogController addRecordDialogController = (AddRecordDialogController)loader.getController();
addRecordDialogController.setAddNewSalesDialogController(this);
addRecordDialogController.setInvoice(this.invoice);
this.addRecordDialog = new Stage();
this.addRecordDialog.setTitle("Add Record");
this.addRecordDialog.initModality(Modality.APPLICATION_MODAL);
this.addRecordDialog.initOwner(root.getScene().getWindow());
this.addRecordDialog.setScene(scene);
this.addRecordDialog.sizeToScene();
this.addRecordDialog.setResizable(false);
//Event handler for when a Window is closed.
this.addRecordDialog.setOnHiding(new EventHandler<WindowEvent>()
{
#Override
public void handle(WindowEvent we)
{
if(nextItem == true)
showInputDialog();
nextItem = false;
}
});
this.addRecordDialog.showAndWait();
}
The second dialog shows up but the first dialog doesn't disappear.
The dialog is programmatically closed with a stage.close(); somewhere else.
I saw here that you need to call the setOnHiding method for a programmatically closing event.
If I remove the event handler the previous stage will close.
But I want to open a new instance of that stage again after it is closed.
Please help.
Edit: ...yes I checked for the nextItem variable it was true,
I used System.out.println("Next Item: " + nextItem);
Based on kendavidson's comment, I have found the solution.
I've changed the code to:
this.addRecordDialog.setOnHidden(new EventHandler<WindowEvent>()
{
#Override
public void handle(WindowEvent we)
{
System.out.println("Next Item: " + nextItem);
if(nextItem == true)
{
nextItem = false;
showInputDialog();
}
}
});
Thanks kendavidson.
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);
}
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.
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.