0

I have a JavaFX application that opens multiple windows. The data within a window is recorded to a database whenever the focus is lost from that window. When a user wants to close a window, an event handler should be fired that requests the tuple be deleted from the database. I only want this to occur when the user actively clicks the x in the top right of the window and not when the application is quit outright (e.g. if the program is quit from an OS start bar or equivalent) or halted in some other fashion.

the close event handler stub looks similar to the below:

foo.setOnCloseRequest(new EventHandler<WindowEvent>() 
  {      
    @Override
    public void handle(WindowEvent event)
    {          
      try
      {
        barController.exec(Action.DELETE, item);     
      } catch (Exception e)
      {
        e.printStackTrace();
      }
    }
  });

The trouble is, when I halt the program from the bar in Ubuntu (for example), this event is still being called for each window; and each time the tested event is WindowEvent.WINDOW_CLOSE_REQUEST whether the user or the application closed the window.

Simply put: is there some kind of way to delineate "onUserCloseWindow" from "onCloseApplication"?

Jay Edwards
  • 950
  • 1
  • 12
  • 21
  • You could do the database work in the background, a short time after the window has closed. (Personally I’d use a 30 second delay.) – VGR Jun 28 '16 at 16:35
  • @VGR, aside from the inherent problems with having a GUI-driven application perform potentially risky background tasks after the visual component of the program has stopped running, you'd still have the same problem: the application has no way of knowing which items have been nominated for deletion and which have not. The only simple and reasonably viable workaround I can see is to have the close event add to a queue of timestamps when a window is closed. then you could have Application.stop() test for any timestamps that are older than the last second and delete accordingly the associated. – Jay Edwards Jun 28 '16 at 18:00

3 Answers3

1

When using the native window decoration, I do not believe there is a way to distinguish these types of events. They are both, from the point of view of your app, an

external request to close this Window

.

I think your best option would be implementing your own window decoration as mentioned here.

Oliver Jan Krylow
  • 1,758
  • 15
  • 22
1

It looks like, when you close from the Taskbar (at least on a Mac), the owner window, gets a close request event before the event is delivered to other child windows. So you could detect the close request event on the owner, set a flag and when the other windows receive their close requests they can check if the flag has been set before performing their processing. If the flag was set, then it was a task bar or user initiated shutdown of the complete application, otherwise it was a user initiated close of just a single secondary window.

import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Eventacity extends Application {
    private boolean isPrimaryClosing = false;

    @Override
    public void start(Stage primary) {
        primary.setScene(new Scene(new Group(), 200, 200));
        primary.setTitle("Main");
        addEventHandlers(primary);
        primary.show();

        for (int i = 0; i < 3; i++) {
            Stage secondary = new Stage();
            secondary.setTitle("Secondary " + i);
            secondary.setScene(new Scene(new Group(), 100, 100));
            secondary.setX(primary.getX() + (i+1) * 25);
            secondary.setY(primary.getY() + (i+1) * 25);

            secondary.initOwner(primary);
            addEventHandlers(secondary);

            secondary.show();
        }
    }

    private void addEventHandlers(Stage stage) {
        stage.setOnCloseRequest(event -> {
            if (stage.getOwner() == null) {
                isPrimaryClosing = true;
            }

            System.out.println(
                    "Close Request:   " + stage.getTitle()
            );
            System.out.println(
                    "Primary Closing: " + isPrimaryClosing
            );

            if (!isPrimaryClosing) {
                // delete data from database.
            }
        });
        stage.setOnHiding(event -> System.out.println(
                "Hiding:          " + stage.getTitle())
        );
        stage.setOnHidden(event -> System.out.println(
                "Hid:             " + stage.getTitle())
        );
        stage.setOnShowing(event -> System.out.println(
                "Showing:         " + stage.getTitle())
        );
        stage.setOnShown(event -> System.out.println(
                "Showed:          " + stage.getTitle())
        );
    }

    public static void main(String[] args) {
        launch(args);
    }
}
jewelsea
  • 150,031
  • 14
  • 366
  • 406
  • A reasonable workaround for some situations. Sadly, the number of windows to be created is only known at run time and Ubuntu is closing the "primary window" last. That brings me back to the same problem really! Timestamping and a 50 millisecond delay is working on my main PC. My only worry is that It might be slightly less reliable on an older machine leading to some artifacts in the DB. Not the end of the world, but not ideal! – Jay Edwards Jun 29 '16 at 12:33
0

The best approach seemed to be to test if the stage had focus. pressing the X should always occur in focus. Therefore, if a close request is called on a focused window then a delete request is sent. This doesn't seem to consider actions like "Alt-F4" but that can be justified as a "feature". It isn't a commercial application, just a useful tool I wanted to write for graphical Linux environments.

if (foo.focusedProperty().getValue()) {
            try
            {
              barController.exec(Action.DELETE, fooId);
            } catch (Exception e)
            {
              e.printStackTrace();
            }
          }
Jay Edwards
  • 950
  • 1
  • 12
  • 21