How to stay in the same fxml when calling an Alert Class? - javafx

I have created this methode for Email validation
The issue is that the Alert displayed goes on the top of the previous fxml instead of the same and the user have to fulfill all the fields again
I have this method
public boolean validateEmail() {
Pattern p = Pattern.compile("[a-zA-Z0-9][a-zA-Z0-9._]*#[a-zA-Z0-9]+([.][a-zA-Z]+)+");
Matcher m = p.matcher(emailField.getText());
if (m.find() && m.group().equals(emailField.getText())) {
return true;
} else {
Alert alert = new Alert(Alert.AlertType.WARNING);
alert.setTitle("Validation of Email");
alert.setHeaderText(null);
alert.setContentText("Please enter a valid Email");
alert.showAndWait();
return false;
}
}
I have a button which onAction calls the method below
public void showSubscription() throws IOException {
Dialog<ButtonType> dialog = new Dialog<>();
dialog.setTitle("New Subscription");
dialog.setHeaderText("Please Fulfill your information to subscribe");
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation(getClass().getResource("Registration.fxml"));
try {
dialog.getDialogPane().setContent(fxmlLoader.load());
} catch (IOException e) {
System.out.println("Couldn't load the Dialog");
e.printStackTrace();
return;
}
dialog.getDialogPane().getButtonTypes().add(ButtonType.OK);
dialog.getDialogPane().getButtonTypes().add(ButtonType.CANCEL);
Optional<ButtonType> result = dialog.showAndWait();
if (result.isPresent() && result.get()==ButtonType.OK) {
RegistrationController controller = fxmlLoader.getController();
if (controller.validateEmail()) {
controller.loadRegistration();
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("Information");
alert.setHeaderText(null);
alert.setContentText("Subscription Done Correctly");
alert.showAndWait();
}
}
else {
System.out.println("CANCEL pressed");
}
}
I don't know what to add to make this Alert popup in the same Registration.fxml and not go back to the previous one.

Alert inherits an initOwner(Window) method from Dialog. So you can call initOwner(...) and pass in the window responsible for showing the dialog. There's no direct reference to this, but you can get it from the scene containing the dialog's dialog pane:
alert.initOwner(dialog.getDialogPane().getScene().getWindow());
This line just needs to be called sometime before alert.showAndWait().
If you need the Alert created in the validateEmail() method to have the same owner, just pass a reference to the appropriate window to that method:
public boolean validateEmail(Window mainWindow) {
// ...
Alert alert = new Alert(Alert.AlertType.WARNING);
alert.initOwner(mainWindow);
// ...
alert.showAndWait();
// ...
}
and
RegistrationController controller = fxmlLoader.getController();
if (controller.validateEmail(dialog.getDialogPane().getScene().getWindow())) {
// ...
}

Related

JavaFx TableView - update items from another controller

I have a TableView that I am populating with 2 columns (Key and Value) from a database. When I click on a table row, I open a new scene which has a text area that shows the Value column and allows one to edit it. I choose to go with a separate UI for editing the contents of value column as it contains a prettyfied JSON document and having an in place edit in the table would have been cumbersome.
dataTable.setRowFactory(tv -> {
TableRow<Map.Entry<String, String>> row = new TableRow<>();
row.setOnMouseClicked(event -> {
showDataPopup(dataValue.getKey(), dataValue.getValue());
});
return row;
});
private void showDataPopup(String key, String value) {
try {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("tableDataPopup.fxml"));
Parent dataRoot = (Parent) fxmlLoader.load();
Stage stage = new Stage();
stage.setTitle("Data Viewer");
stage.setScene(new Scene(dataRoot, 800, 500));
DataPopupController dataPopupController = fxmlLoader.getController();
dataPopupController.loadDataTextArea(key, value, this);
stage.show();
} catch (Exception e) {
logger.error("Error loading tableDataPopup.fxml", e);
}
}
Now, in the tableDataPopup scene, I allow the value to be edited. I have a save button which should save the edited document back to the table and close the scene. Here's my save method
public void saveEditedDocument(ActionEvent event) {
//code to save document to db
mainController.refreshTable(docIdLabel.getText(), dataTextArea.getText());
Stage stage = (Stage) editCancelButton.getScene().getWindow();
stage.close();
}
...
I have a refreshTable method in my main controller. Main controller has all the TableView components and logic.
public void refreshTable(String docId, String docVal) {
logger.info(": {}", dataTable.getItems());
}
I need help figuring out how to update the cell value that was changed in the popup dialog. I'd rather avoid having to stream the whole table and look for the key column and update the value. I am looking for a way to pass the cell index to the data popup and have it pass it back to the refreshTable method. Then use it to directly update the cell and then call dataTable.refresh() method to refresh the data table.
I am struggling with where to even start on this. Any pointers would really help...
I guess i figured it out. May not be an elegant solution, but this works for me.
I am passing the index of the row that was clicked on to my dataPopupController
dataTable.setRowFactory(tv -> {
TableRow<Map.Entry<String, String>> row = new TableRow<>();
row.setOnMouseClicked(event -> {
showDataPopup(dataValue.getKey(), dataValue.getValue(),row.getIndex());
});
return row;
});
private void showDataPopup(String key, String value, int index) {
try {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("tableDataPopup.fxml"));
Parent dataRoot = (Parent) fxmlLoader.load();
Stage stage = new Stage();
stage.setTitle("Data Viewer");
stage.setScene(new Scene(dataRoot, 800, 500));
DataPopupController dataPopupController = fxmlLoader.getController();
dataPopupController.loadDataTextArea(key, value, this, **index**);
stage.show();
} catch (Exception e) {
logger.error("Error loading tableDataPopup.fxml", e);
}
}
And I updated the refreshTable method in main controller to update the item based on the index
public void refreshTable(String docVal, int tableIndex) {
logger.info(": {}", dataTable.getItems());
logger.info("Table value is: {}", dataTable.getItems().get(tableIndex).setValue(docVal));
dataTable.refresh();
}
And in my popup controller, I just pass back the index when I call refreshTable method
public void saveEditedDocument(ActionEvent event) {
// Todo - refresh table after save
try {
mainController.refreshTable(minifyData(dataTextArea.getText()), tableIndex);
Stage stage = (Stage) editCancelButton.getScene().getWindow();
stage.close();
} catch (JsonSyntaxException exception) {
saveStatusLabel.setText("Malformed JSON, please correct");
logger.error("Malformed JSON - {}", exception.getMessage());
}
}
May not be the most elegant way to do this, but in a pinch, it works for me.

How to activate the OK button by pressing enter on TextField?

I have this class that generates a Alert Dialog with a field to enter a password, and want to activate the OK button when pressing Enter on the password field.
public class PasswordDialog extends Dialog<String> {
private PasswordField passwordField;
public PasswordDialog(boolean usuario) {
setTitle("Senha");
if (usuario == true){
setHeaderText("Por favor insira a senha do usuário.");
}else{
setHeaderText("Por favor insira a senha do administrador.");
}
ButtonType passwordButtonType = new ButtonType("OK", ButtonData.OK_DONE);
getDialogPane().getButtonTypes().addAll(passwordButtonType, ButtonType.CANCEL);
passwordField = new PasswordField();
passwordField.setPromptText("Password");
HBox hBox = new HBox();
hBox.getChildren().add(passwordField);
hBox.setPadding(new Insets(20));
HBox.setHgrow(passwordField, Priority.ALWAYS);
getDialogPane().setContent(hBox);
Platform.runLater(() -> passwordField.requestFocus());
setResultConverter(dialogButton -> {
if (dialogButton == passwordButtonType) {
return passwordField.getText();
}
return null;
});
}
public PasswordField getPasswordField() {
return passwordField;
}
}
Actually this should happen by default (at least that's the behaviour on JavaFX 11/Win 10), but you can also close the Dialog yourself by calling setResult and close.
Example closing on arrow keys:
// in constructor
passwordField.setOnKeyPressed(evt -> {
if (evt.getCode().isArrowKey()) {
setResult(passwordField.getText());
close();
}
});
For closing on pressing enter, use the onAction event of the PasswordField:
// in constructor
passwordField.setOnAction(evt -> {
setResult(passwordField.getText());
close();
});
For more complicated behaviour of the resultConverter, you could also use it for setting the result to avoid duplicate code:
setResult(getResultConverter().call(passwordButtonType));

How can I disable ButtonType.OK in a DialogPane?

How do you disable the OK button until certain conditions are met?
Here im itializing my DialogPane with OK and CANCEL buttons.
Dialog<ButtonType> testDialog = new Dialog<>();
testDialog.initOwner(mainBorderPane.getScene().getWindow());
testDialog.setTitle("Test Dialog");
testDialog.setHeaderText(null);
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation(getClass().getResource("testDialog.fxml"));
try{
testDialog.getDialogPane().setContent(fxmlLoader.load());
} catch(IOException e){
System.out.println("Couldn't load the dialog");
e.printStackTrace();
return;
}`
testDialog.getDialogPane().getButtonTypes().add(ButtonType.OK);
testDialog.getDialogPane().getButtonTypes().add(ButtonType.CANCEL);
Optional<ButtonType> result = testDialog.showAndWait();
Then I am trying to bind the conditions to the OK button.
Conditions being: TextField inputs must match time format (0-7hours, 0-59minuts)
#FXML
private TextField hoursField;
#FXML
private TextField minutesField;
public void initialize(){
testDialogPane.lookupButton(ButtonType.OK).disableProperty()
.bind(Bindings.createBooleanBinding(
() -> !hoursField.getText().matches("[0-7]") ||
!minutesField.getText().matches("^[0-5]?[0-9]$"),
hoursField.textProperty(),
minutesField.textProperty()
));
}
Ofc, I'm getting a java.lang.NullPointerException because buttons or not existing at the initialize runtime.
Is there any other way around?
You can do the binding after you add the OK button to the dialog.
You have to add a getter for each TextField in the controller, then after you
load the testDialog.fxml you can assign the its controller to a reference like: DialogController controller = fxmlLoader.getController(); (swap DialogController with your controller)
Then after testDialog.getDialogPane().getButtonTypes().add(ButtonType.OK);
do the binding:
testDialogPane.lookupButton(ButtonType.OK).disableProperty()
.bind(Bindings.createBooleanBinding(
() -> !controller.getHoursField.getText().matches("[0-7]") ||
!controller.getMinutesField.getText().matches("^[0-5]?[0-9]$"),
controller.getHoursField.textProperty(),
controller.getMinutesField.textProperty()
));
Create a method in your controller class that returns a BooleanExpression for valid/invalid input.
Controller
private BooleanExpression invalidInput;
public BooleanExpression invalidInputProperty() {
if (invalidInput == null) {
invalidInput = Bindings.createBooleanBinding(
() -> !hoursField.getText().matches("[0-7]") ||
!minutesField.getText().matches("^[0-5]?[0-9]$"),
hoursField.textProperty(),
minutesField.textProperty()
);
}
return invalidInput;
}
Dialog creation:
fxmlLoader.setLocation(getClass().getResource("testDialog.fxml"));
testDialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
try{
testDialog.getDialogPane().setContent(fxmlLoader.load());
Controller controller = fxmlLoader.getController(); // depending on the name of the controller class you may need to adjust this
testDialog.getDialogPane().lookupButton(ButtonType.OK)
.disableProperty()
.bind(controller.invalidInputProperty());
} catch(IOException e){
System.out.println("Couldn't load the dialog");
e.printStackTrace();
return;
}

Deleting a file(s) through FileChooser?

I have a JavaFX app using JRE1.8.0 u40. I converted my Swing JFileChooser Open and Save to the newer JavaFX FileChooser Open and Save, Windows7 style Dialog box. But I have not found an equivalent JavaFX FileChooser method to replace the JFileChooser method I'm using for deleting a file(s) as shown below:
public static void deleteFile() throws IOException {
JFileChooser fileDialog = new JFileChooser("C:\\ProgramData\\L1 Art Files\\");
File[] selectedFiles;
fileDialog.setSelectedFiles(null);
// Set frame properties
fileDialog.setDialogTitle("Delete Pixel Art File(s)");
//fileDialog.setLayout(new FlowLayout());
fileDialog.setSize(400, 400);
fileDialog.setVisible(true);
fileDialog.setMultiSelectionEnabled(true); // Allow multiple selection
fileDialog.setVisible(true);
int option = fileDialog.showDialog(null, "Delete");
if (option != JFileChooser.APPROVE_OPTION)
return; //user canceled the
selectedFiles = fileDialog.getSelectedFiles();
if (selectedFiles != null) { //ask the user to replace this file
int response = JOptionPane.showConfirmDialog(null, "Are you sure want to delete this file?",
"Confirm Delete",
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE);
if (response != JOptionPane.YES_OPTION) return;
}
for (File f : selectedFiles) {
Files.delete(f.toPath());
}
Is there a similar solution to the above for JavaFX using FileChooser or do I use the showOpenDialog(null) with setTitle("Delete Pixel Art File")?
You can easily perform that task using javafx like below :
#FXML
private void onDeleteAction(ActionEvent event) {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Your_title_here");
List<File> selectedFiles = fileChooser.showOpenMultipleDialog(null);
if (selectedFiles != null) {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("Confirmation Dialog");
alert.setHeaderText("Warning !");
alert.setContentText("Are you sure you want to delete these files ?");
Optional<ButtonType> result = alert.showAndWait();
if (result.get() == ButtonType.OK) {
for (File selectedFile : selectedFiles) {
selectedFile.delete();
}
}
} else {
System.out.println("Error Selection");
}
}
The above code was very helpful and works best with the other suggestion of checking for null and adding throws IOException to the deleteFile() method.

JavaFX Cursor wait while loading new anchorPane

I am trying to load a new anchorPane in the existing scene by removing the old anchorPane.Now i need to show the user that loading cursor and the action performed by the user should not happen(like button click or key press).
I have used but Cursor.WAIT but still the action can be performed.
anchorPane.getScene().SetCursor(Cursor.WAIT);
HomeController controller=new HomeController(stage,anchorPane);
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/fxml/home.fxml"));
loader.setController(controller);
Parent root = null;
try {
root=(Parent) loader.load();
} catch (IOException e) {
LOGGER.error("Message is " + e);
e.printStackTrace();
}
anchorPane.getChildren().remove(0);
anchorPane.getChildren().add(root);
I have added Cursor.WAIT before this code.but i doesn't work.
Cursor.WAIT change only the icon of your cursor but it doesn't prevent you to interact with your view. If you want to prevent an user to interact with your view your could disable the elements like btn.setEnabled(false).
To show the user that you are peforming some background actions and he/she should wait until it's complete use tasks and dialogs.
Task task = new Task(new Runnable() { ... do stuff ... });
Dialog dialog = new Alert(AlertType.INFORMATION);
dialog.setTitle("Wait");
dialog.disableButton(ButtonType.OK);
task.stateProperty().addListener(
(observableValue, oldState, newState) -> {
if (newState == Worker.State.SUCCEEDED
|| newState == Worker.State.FAILED
|| newState == Worker.State.CANCELLED) {
dialog.close();
}
});
new Thread(task).start();
dialog.showAndWait();

Resources