• title: JFrameの終了をキャンセル tags: [JFrame, WindowListener] author: aterai pubdate: 2004-08-09 description: JFrameを閉じる前に、本当に終了してよいか、終了をキャンセルするかなどを確認するダイアログを表示します。

概要

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 = dummyDocumentSaveMethod();
      //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

解説

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

  • WindowAdapter#windowClosing(WindowEvent e)
    • システムメニューでウィンドウを閉じようとしたときに呼び出されるリスナーのメソッド
      • OSWindowsなら、Alt+F4キーを押す
      • タイトルバー左上にあるアイコンをクリックし、ポップアップメニューで閉じるを選択
      • タイトルバー右上の×ボタンをクリック
      • JButtonJMenuなどをクリックした時に、対象となるframewindowClosingを呼び出したい場合は、frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING));
    • frame.dispose();では、このメソッドは呼び出されない
  • WindowAdapter#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のタイトルを変更

参考リンク

コメント