Search code examples
javamultithreadingjavafxwait

What is the good use to create a wait / notify window with javafx?


I want to create a popup only once and change the content via the for loop; (for each files dropped). I want to answer yes/no/noAll/yesAll. So I need a wait() just after to show the popup with the first movie content. And if I said "no" for example, I want notify() the thread to be unlock in the aim to reach until the second movie. In this case, The popup content changes with the second movie and I have again a wait() until I answer via the buttons etc.

I tried a lot of things without successfull :/ and I get this error:

Exception in thread "JavaFX Application Thread" java.lang.IllegalMonitorStateException
    at java.lang.Object.notifyAll(Native Method)
    at controllers.HomeController.notifyPopup(HomeController.java:150)
    at controllers.HomeController.lambda$1(HomeController.java:208)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)

I meet a problem on dialog.notify() Maybe I don't apply the notify() on the same dialog object but it's the case, no ?

Thank you in advance for your help

This is my partial code : Search // LOOK HERE in the code to see the main lines

public class HomeController {


  @FXML
  private Label home;

  @FXMLViewFlowContext
  private ViewFlowContext context;

  RenameFiles film;
  SimpleBooleanProperty  noAllProperty = new SimpleBooleanProperty(false);
  SimpleBooleanProperty  yesAllProperty = new SimpleBooleanProperty(false);
  SimpleBooleanProperty  continuePopup = new SimpleBooleanProperty(false);
  ArrayList<Path> arrayFile = new ArrayList<Path>();
  Stage stage;
  Boolean firstFile = false;
  JFXDialogLayout content;
  JFXDialog dialog;

  @PostConstruct
  public void init() throws FlowException, VetoException {
    Settings settings = (Settings) context.getRegisteredObject("Settings");
    content = new JFXDialogLayout();
    dialog = new JFXDialog((StackPane) context.getRegisteredObject("ROOT"), content, JFXDialog.DialogTransition.CENTER);

    home.setOnDragOver(new EventHandler<DragEvent>() {
      @Override
      public void handle(DragEvent event) {
        Dragboard dragboard = event.getDragboard();
        if (dragboard.hasFiles()) {
          event.acceptTransferModes(TransferMode.ANY);
        } else {
          event.consume();
        }
      }
    });

    // Dropping over surface
    home.setOnDragDropped(new EventHandler<DragEvent>() {
      @Override
      public void handle(DragEvent event) {
        Dragboard dragboard = event.getDragboard();
        boolean success = false;
        if (dragboard.hasFiles()) {
          arrayFile = new ArrayList<Path>();
          success = true;
          noAllProperty.set(false);
          yesAllProperty.set(false);
          dragboard.getFiles().forEach(file -> {
            if(Files.isDirectory(file.toPath())){
              try(Stream<Path> paths = Files.walk(Paths.get(file.toURI()))) {
                paths.forEach(filePath -> {
                  if (Files.isRegularFile(filePath)) 
                  arrayFile.add(filePath);
                });
              } catch (IOException e) {
                e.printStackTrace();
              } 
            }else if (Files.isRegularFile(file.toPath()))
            arrayFile.add(file.toPath());

          });
        }
        event.setDropCompleted(success);
        event.consume();

        arrayFile.forEach(file -> {
          film = new RenameFiles(new File(file.toString()), settings);
          if(!noAllProperty.getValue()){
            if(film.getNameWithoutExt().length() > 0){
              if(!yesAllProperty.getValue()){
                    try {
                        firstFile = true;
                        contentPopup();

                        if(firstFile) {
                          dialog.show();
                          dialog.setOverlayClose(false);
                          firstFile = false;
                        }
                 // LOOK HERE
                        waitPopup(dialog);

                    } catch (FlowException | InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }           
              }else{
                film.applyRename(film.getCleanName());
              }
            }
          }
        });
      }
    });
  }
  public synchronized void waitPopup(JFXDialog dialog) {
        // This guard only loops once for each special event, which may not
        // be the event we're waiting for.
        while(!continuePopup.getValue()) {
            try {
                dialog.wait();
            } catch (InterruptedException e) {}
        }
    }

  public synchronized void notifyPopup(JFXDialog dialog) {
        continuePopup.setValue(true);
        dialog.notifyAll();
    }

  private void contentPopup() throws FlowException, InterruptedException{

    JFXButton buttonYesPopupRename = new JFXButton("Yes");
    buttonYesPopupRename.setPrefHeight(30);
    buttonYesPopupRename.setPrefWidth(70);
    buttonYesPopupRename.setId("buttonYesPopupRename");
    buttonYesPopupRename.setButtonType(ButtonType.RAISED);
    buttonYesPopupRename.setOnAction(e -> { film.applyRename(film.getCleanName());continuePopup.setValue(true);notifyPopup(dialog); });  // LOOK HERE
    buttonYesPopupRename.setStyle("-fx-text-fill:WHITE;-fx-background-color:#5264AE;-fx-font-size:14px;");

    JFXButton buttonNoPopupRename = new JFXButton("No");
    buttonNoPopupRename.setPrefHeight(30);
    buttonNoPopupRename.setPrefWidth(70);
    buttonNoPopupRename.setId("buttonNoPopupRename");
    buttonNoPopupRename.setButtonType(ButtonType.RAISED);
    buttonNoPopupRename.setOnAction(e -> { continuePopup.setValue(true); notifyPopup(dialog); });  // LOOK HERE
    buttonNoPopupRename.setStyle("-fx-text-fill:WHITE;-fx-background-color:#5264AE;-fx-font-size:14px;");

Solution

  • There is a better approach to the problem than your approach:

    Make the data about the elements that need to be asked about and the current element available to the event handlers and modify the those values and the UI when an event occurs.

    Simplified example that let's you choose Yes or No for some items:

    @Override
    public void start(Stage primaryStage) {
        Button btn = new Button("Dialog");
        List<String> list = Arrays.asList("A", "B", "C", "D");
        btn.setOnAction((ActionEvent event) -> {
            displayDialog(list.iterator());
        });
    
        StackPane root = new StackPane();
        root.getChildren().add(btn);
    
        Scene scene = new Scene(root);
    
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void displayDialog(Iterator<String> iterator) {
        if (iterator.hasNext()) {
            Button yes = new Button("Yes");
            Button no = new Button("No");
            Label text = new Label(iterator.next());
            Stage stage = new Stage();
            stage.setScene(new Scene(new VBox(text, yes, no)));
            EventHandler<ActionEvent> handler = evt -> {
                Labeled source = (Labeled) evt.getSource();
                System.out.println("You chose \"" + source.getText() + "\" for \"" + text.getText() + "\"");
    
                // procede to next state
                if (iterator.hasNext()) {
                    // display next item
                    text.setText(iterator.next());
                } else {
                    // close "dialog" when no more elements available
                    stage.close();
                }
            };
            yes.setOnAction(handler);
            no.setOnAction(handler);
            stage.show();
        }
    }