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

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.

Related

Wait non-modal window to close to resume code

I have a program that in somepoint needs to open a dialog "anDialog" which contains buttons that opens more dialogs "B" and "C", some of those child dialogs are not modal, so if I set the dialog "anDialog" as modal it raises above them right after opening and blocks them. But if I set "anDialog" as not modal, the class which calls it keeps running and it should not.
Calling dialog "A"
OpenAttributesAssistentCommand attrAssistent = new OpenAttributesAssistentCommand((InternalInterfaceAttributes) parent, transcriptor);
attrAssistent.execute();
//... more stuff after
Execute
public void execute() {
AttributesAssistentDialog anDialog = new AttributesAssistentDialog(intFrame, transcriptor);
anDialog.setVisible(true);
}
I want that the caller waits the dialog "anDialog" finishing before keep running. It would be nice if it could understand the difference between closing and btnOk. Also if there is a way to make it don't block non-modal childs it would be ok.
I believe you can do this with a SecondaryLoop, which blocks a thread without blocking the user interface until its exit method is called:
private SecondaryLoop attrAssistantLoop;
// ...
OpenAttributesAssistentCommand attrAssistent =
new OpenAttributesAssistentCommand(
(InternalInterfaceAttributes) parent, transcriptor);
attrAssistantLoop = Toolkit.getSystemEventQueue().createSecondaryLoop();
attrAssistent.execute(attrAssistantLoop);
attrAssistantLoop.enter(); // Wait for dialog to close
// OpenAttributesAssistentCommand class
public void execute(SecondaryLoop loop) {
AttributesAssistentDialog anDialog = new AttributesAssistentDialog(intFrame, transcriptor);
anDialog.addWindowListener(new WindowAdapter() {
#Override
public void windowClosed(WindowEvent event) {
if (loop != null) {
loop.exit(); // Allow waiting code to proceed
}
}
});
anDialog.setVisible(true);
}

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;
}
}

JToggleButton addItemListener seems to repeat the ItemListener forever

I'm programming a JToggleButton to load to/discard from memory the configuration of an element (a telescope config), so I've added a JComboBox in a JFrame and near it the button to load the selected item. When the JToggleButton is selected, an hard disk icon is displayed, another icon if otherwise. I'm using the IntelliJ IDEA GUI editor for that. Of course, I've added an ItemListener (as suggested from the web) to that button:
loadTelescopeButton.setSelected(true);
System.out.println(loadTelescopeButton.isSelected());
loadTelescopeButton.addItemListener(new ItemListener() {
#Override
public void itemStateChanged(ItemEvent e) {
System.out.println("LAODACTION " + loadTelescopeButton.isSelected());
try {
if (e.getStateChange() == ItemEvent.SELECTED) {
String selected = telescopesList.getSelectedItem().toString();
if ((selected != null) && (!selected.equals("")) && (ObjUtils.isAlphaNumeric(selected))) {
//...
} else {
showErrorMessage("Invalid id selected!");
}
} else if (e.getStateChange() == ItemEvent.DESELECTED) {
if ((configurationActivity != null) && (configurationActivity.getManager() != null) &&
(configurationActivity.getTelescope() != null) && (configurationActivity.getTelescope().isConnected())) {
//...
} else {
//...
}
}
} catch (Exception e1) {
e1.printStackTrace();
}
}
});
Output:
true
-> When the window is displayed
LAOD_ACTION false
-> When I click the button
I've made some tests with some new toggle buttons and they gave me same error: the code inside itemStateChanged(ItemEvent e) {...} is repeated forever, without stopping! In that piece of code there are no for and while loops! The result is a great number of message dialogs (only one dialog should be displayed), and if I focus another window in my desktop the screen behind the dialogs becomes black (the area of the parent window). I changed the listener to ActionListener and now everything is executed one time/click.
Why this error? I've copied that code from https://stackoverflow.com/a/7524627/6267019, as you can see.
Full code on GitHub Here, I've highlighted the code for that toggle button. The same error happens with other JToggleButtons in my MainActivity.java file, and also when debugging IntelliJ lets me see that the code in the listener is repeated forever. After some thousand of dialogs Windows shows me a message and closes Java Platform Binary with an error.
EDIT:
The same problem in a new class:
import javax.swing.*;
import java.awt.*;
public class ErrorGUI extends JFrame {
public ErrorGUI() throws HeadlessException {
super("ciao");
JPanel panel1 = new JPanel();
setContentPane(panel1);
JToggleButton ciaoToggleButton = new JToggleButton("cajs");
ciaoToggleButton.setSelected(true);
ciaoToggleButton.addItemListener(e -> {
System.out.println("caiooasfsdvn");
try {
JOptionPane.showMessageDialog(panel1, "skjngksfnb");
} catch (Exception e2) {
e2.printStackTrace();
}
});
panel1.add(ciaoToggleButton);
pack();
setVisible(true);
}
public static void main(String[] args) {
new ErrorGUI();
}
}
Whenever you open a modal dialog, the opening method call will return only after the dialog has been closed. This is crucial for the dialogs that return an entered value or choice.
This implies that while the dialog is open, a new event handling loop has to be started to react on the input in the dialog.
So when you open a modal dialog from a listener, you are stopping the handling of the current event and start processing of subsequent events, which can disturb the handling of the current event significantly. Most notably, the button will suddenly loose the focus when the new dialog is opened.
The nested event handling can be easily demonstrated by changing the listener to
ciaoToggleButton.addItemListener(e -> {
System.out.println("entering");
JOptionPane.showMessageDialog(panel1,
e.getStateChange()==ItemEvent.SELECTED? "selected": "deselected");
System.out.println("leaving");
});
which will print sequences of
entering
entering
leaving
leaving
showing how the contradicting event is generated while the processing of the old one hasn’t completed.
As said by others, you can fix this by opening the dialog after the completion of the even handling, like
ciaoToggleButton.addItemListener(e -> {
System.out.println("entering");
EventQueue.invokeLater(() -> JOptionPane.showMessageDialog(panel1,
e.getStateChange()==ItemEvent.SELECTED? "selected": "deselected"));
System.out.println("leaving");
});
or you enforce a non-modal dialog:
ciaoToggleButton.addItemListener(e -> {
System.out.println("entering");
JDialog d = new JOptionPane(
e.getStateChange()==ItemEvent.SELECTED? "selected": "deselected",
JOptionPane.INFORMATION_MESSAGE)
.createDialog(panel1, UIManager.getString("OptionPane.messageDialogTitle"));
d.setModal(false);
d.setVisible(true);
System.out.println("leaving");
});
(in a real application you would either keep the dialog for later reuse or call dispose after use)
Unfortunately, the danger of opening modal dialogs (or doing anything else that creates a secondary event loop) hasn’t been emphasized enough in the documentation. You can read everywhere that accessing Swing components from other threads can create inconsistencies, but starting new event handling loop while there are incompletely processed events can have a similar impact.
I can't say that I understand why your code is misbehaving, but I agree that what you're seeing doesn't quite make sense, and is likely due to the JOptionPane call somehow affecting the JToggleButton's state change. One way to get around this is by wrapping the JOptionPane call in a Runnable and queuing it on the Swing event queue via SwingUtilities.invokeLater(...). For example:
import javax.swing.*;
import java.awt.*;
#SuppressWarnings("serial")
public class ErrorGUI extends JFrame {
public ErrorGUI() throws HeadlessException {
super("ciao");
JPanel panel1 = new JPanel();
setContentPane(panel1);
JToggleButton ciaoToggleButton = new JToggleButton("cajs");
ciaoToggleButton.setSelected(true);
ciaoToggleButton.addItemListener(e -> {
System.out.println("caiooasfsdvn");
SwingUtilities.invokeLater(() -> {
JOptionPane.showMessageDialog(panel1, "skjngksfnb");
});
// JOptionPane.showMessageDialog(panel1, "skjngksfnb");
});
panel1.add(ciaoToggleButton);
pack();
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
new ErrorGUI();
});
}
}
An interesting variation:
ciaoToggleButton.setSelected(true);
System.out.println("0:" + ciaoToggleButton.isSelected());
ciaoToggleButton.addItemListener(e -> {
System.out.println("1: " + ciaoToggleButton.isSelected());
if (e.getStateChange() == ItemEvent.SELECTED) {
JOptionPane.showMessageDialog(panel1, "skjngksfnb");
}
System.out.println("2: " + ciaoToggleButton.isSelected());
});
prints out:
0:true
1: false
2: false
1: true
1: false
2: false
2: false
1: true
1: false
2: false
2: false

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

Get values from modal JavaFX

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.

Categories