概要

JFrameを閉じる前に、本当に終了してよいか、終了をキャンセルするかなどを確認するダイアログを表示します。

サンプルコード

class SaveHandler extends WindowAdapter implements DocumentListener, ActionListener {
  // public static final String ASTERISK_TITLEBAR = "unsaved";
  public static final String CMD_SAVE = "save";
  public static final String CMD_EXIT = "exit";
  private final JFrame frame;
  private final String title;
  private final List<JComponent> list = new ArrayList<>();

  public SaveHandler(JFrame frame) {
    super();
    this.frame = frame;
    this.title = frame.getTitle();
  }

  // WindowAdapter
  @Override public void windowClosing(WindowEvent e) {
    System.out.println("windowClosing");
    maybeExit();
  }

  @Override public void windowClosed(WindowEvent e) {
    System.out.println("windowClosed");
    System.exit(0); // webstart
  }

  // ActionListener
  @Override public void actionPerformed(ActionEvent e) {
    String cmd = e.getActionCommand();
    if (CMD_EXIT.equals(cmd)) {
      maybeExit();
    } else if (CMD_SAVE.equals(cmd)) {
      fireUnsavedFlagChangeEvent(false);
    }
  }

  // DocumentListener
  @Override public void insertUpdate(DocumentEvent e) {
    fireUnsavedFlagChangeEvent(true);
  }

  @Override public void removeUpdate(DocumentEvent e) {
    fireUnsavedFlagChangeEvent(true);
  }

  @Override public void changedUpdate(DocumentEvent e) {
    /* not needed */
  }

  private void maybeExit() {
    if (title.equals(frame.getTitle())) {
      System.out.println(
          "The document has already been saved, exit without doing anything.");
      frame.dispose();
      return;
    }
    Toolkit.getDefaultToolkit().beep();
    Object[] options = {"Save", "Discard", "Cancel"};
    int retValue = JOptionPane.showOptionDialog(
        frame, "<html>Save: Exit & Save Changes<br>"
        + "Discard: Exit & Discard Changes<br>Cancel: Continue</html>",
        "Exit Options", JOptionPane.YES_NO_CANCEL_OPTION,
        JOptionPane.INFORMATION_MESSAGE, null, options, options[0]);
    if (retValue == JOptionPane.YES_OPTION) {
      System.out.println("exit");
      // boolean ret = sampleDocumentSaveMethod();
      // if (ret) { // saved and exit
      //   frame.dispose();
      // } else { // error and cancel exit
      //   return;
      // }
      frame.dispose();
    } else if (retValue == JOptionPane.NO_OPTION) {
      System.out.println("Exit without save");
      frame.dispose();
    } else if (retValue == JOptionPane.CANCEL_OPTION) {
      System.out.println("Cancel exit");
    }
  }

  public void addEnabledFlagComponent(JComponent c) {
    list.add(c);
  }

  public void removeEnabledFlagComponent(JComponent c) {
    list.remove(c);
  }

  private void fireUnsavedFlagChangeEvent(boolean unsaved) {
    frame.setTitle(String.format("%s%s", unsaved ? "* " : "", title));
    for (JComponent c : list) {
      c.setEnabled(unsaved);
    }
  }
}
View in GitHub: Java, Kotlin

解説

上記のサンプルでは、アプリケーションの終了時に、ドキュメントが保存されているかどうかで処理を変更するために、ウィンドウイベントを受け取るためのリスナーを設定しています。

  • WindowListener#windowClosing(WindowEvent e)
    • システムメニューでウィンドウを閉じようとしたときに呼び出されるリスナーのメソッド
      • OSWindowsならAlt+F4キーを押す
      • タイトルバー左上にあるアイコンをクリックしポップアップメニューで閉じるを選択
      • タイトルバー右上の×ボタンをクリック
      • JButtonJMenuなどをクリックした時に対象となるframewindowClosingを呼び出したい場合は、frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING));
    • frame.dispose();ではこのメソッドは呼び出されない
  • WindowListener#windowClosed(WindowEvent e)
    • frame.dispose()でウィンドウがクローズされたときに呼び出されるリスナーのメソッド
    • windowClosingの後自動的にwindowClosedが呼び出されるのはWindowConstants.DISPOSE_ON_CLOSEの場合のみ
    • このサンプルではWeb Startから起動しても終了できるようにframe.dispose()すれば必ず呼び出されるこのメソッド中でSystem.exit(0);を使いJVMごとシャットダウンしている

  • JFrame#setDefaultCloseOperationメソッドでタイトルバー右上の×ボタンをクリック(デフォルトの終了処理)し、windowClosingが呼ばれた後(このためwindowClosing中で変更しても有効)の動作を設定可能
    • これらの動作についてはJFrame#processWindowEvent(WindowEvent)のソースを参照
  • WindowConstants.DO_NOTHING_ON_CLOSE
    • windowClosingが呼ばれた後になにもしない(終了しない)
    • return;と同じ
    • このサンプルではWindowConstants.DO_NOTHING_ON_CLOSEを設定しているが、システムメニューでウィンドウを閉じても、下のexitボタンと同じ処理になるようにwindowClosingの中で終了処理を行うメソッド(maybeExit())を呼び出し、そこでドキュメントの保存状態によってframe.dispose();を呼んでいる
  • WindowConstants.HIDE_ON_CLOSE
    • windowClosingが呼ばれた後でウィンドウは非表示になる
    • setVisible(false);と同じ
    • 初期値
  • WindowConstants.DISPOSE_ON_CLOSE
    • windowClosingが呼ばれた後でウィンドウは破棄される
    • dispose();と同じ
    • dispose()されるのでこの後windowClosedが呼び出される
  • WindowConstants.EXIT_ON_CLOSE
    • windowClosingが呼ばれた後でJVMがシャットダウンされる
    • System.exit(0);と同じ
    • dispose()されないのでwindowClosedは呼び出されない

  • テキストが変更された場合、タイトル文字列の先頭にアスタリスクを付けることで保存状態の可視化と保持を行っている
  • ドキュメントに文字列が追加されたとき、ソース側からfirePropertyChangeなどでリスナーに変更をイベントで報告
  • リスナー側ではこのイベントを受けJFrameのタイトルを変更

参考リンク

コメント