TITLE:JFrameの終了をキャンセル

JFrameの終了をキャンセル

Posted by terai at 2004-08-09

概要

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

  • &jnlp;
  • &jar;
  • &zip;

#screenshot

サンプルコード

public static final String TITLE_CHANGE = "hogehoge";
private final JTextArea textarea = new JTextArea();
private final JButton saveButton = new JButton("Save");
private final JFrame frame;
public MainPanel(final JFrame frame) {
  super(new BorderLayout());
  this.frame = frame;
  final String title = frame.getTitle();
  addPropertyChangeListener(new PropertyChangeListener() {
    public void propertyChange(PropertyChangeEvent e) {
      if(TITLE_CHANGE.equals(e.getPropertyName())) {
        Boolean flg = (Boolean)e.getNewValue();
        frame.setTitle(title+(flg?"- [変更]":""));
      }
    }
  });
  frame.addWindowListener(new WindowAdapter() {
    @Override
    public void windowClosing(WindowEvent we) {
      System.out.println("windowClosing");
      //タイトルバー右上の×ボタンを押したり、システムメニュー
      //(タイトルバー左上クリックなどでポップアップするメニュー)で
      //閉じるを選ぶと発生する。
      //frame.dispose(); では発生しない。
      if(!title.equals(frame.getTitle())) {
        java.awt.Toolkit.getDefaultToolkit().beep();
        String[] obj = {"テストテキストは変更されています。",
                        "終了する前に保存しますか?"};
        int retValue = JOptionPane.showConfirmDialog(
          frame, obj, "テストアプリ", JOptionPane.YES_NO_CANCEL_OPTION);
        if(retValue==JOptionPane.YES_OPTION) {
          //保存が成功した場合と失敗した場合
          //boolean ret = save();
          //if(ret) {
          //保存が成功したので終了
          //  frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
          //}else{
          //保存に失敗したのでエラーを出してもとに戻る
          //  frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
          //  return;
          //}
          System.out.println("本当はちゃんと保存して終了");
          frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        }else if(retValue==JOptionPane.NO_OPTION) {
          System.out.println("保存しないで終了");
          frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        }else if(retValue==JOptionPane.CANCEL_OPTION) {
          System.out.println("終了をキャンセル");
          frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
        }
      }else{
        System.out.println("変更されていないのでそのまま終了");
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
      }
    }
    @Override
    public void windowClosed(WindowEvent e) {
      //frame.dispose(); で発生。
      //このサンプルではwebstart用にEXIT_ON_CLOSEを使っているので、
      //タイトルバー右上の×ボタンクリックでは呼ばれないはず。
      System.out.println("windowClosed");
      //When DISPOSE_ON_CLOSE met WebStart > www.pushing-pixels.org/?p=232
      //通常(webstartを気にしなくてもよい場合)なら、
      //setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
      //を使えば、タイトルバー右上の×ボタンクリックでも
      //windowClosingの後に、windowClosedが発生する。
    }
  });
  textarea.setText("Test Test Test");
  textarea.getDocument().addDocumentListener(new DocumentListener() {
    public void insertUpdate(DocumentEvent e) {
      fireTitleChangeEvent(true);
    }
    public void removeUpdate(DocumentEvent e) {
      fireTitleChangeEvent(true);
    }
    public void changedUpdate(DocumentEvent e) {}
  });
  saveButton.setEnabled(false);
  saveButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent ae) {
      System.out.println("ダミーなので保存はしない");
      fireTitleChangeEvent(false);
    }
  });
  add(new JScrollPane(textarea));
  add(saveButton, BorderLayout.SOUTH);
  setPreferredSize(new Dimension(512, 320));
}
private void fireTitleChangeEvent(boolean toggle) {
  if(toggle) {
    saveButton.setEnabled(true);
    firePropertyChange(TITLE_CHANGE, Boolean.FALSE, Boolean.TRUE);
  }else{
    saveButton.setEnabled(false);
    firePropertyChange(TITLE_CHANGE, Boolean.TRUE, Boolean.FALSE);
  }
}

解説

上記のサンプルでは、JFrameを閉じるようとしたとき*1に、内部のテキストが変更されている場合は、「終了する前に保存しますか?」と確認ダイアログを表示するようになっています。

  • はい
    • 実際に使用する場合は、ここで変更されたテキストを保存するような処理を実装します。
    • 上記のサンプルではなにもしないで、WindowConstants.EXIT_ON_CLOSE を JFrame#setDefaultCloseOperation(int)メソッドで設定して、JFrameを終了しています。
  • いいえ
    • WindowConstants.EXIT_ON_CLOSE を JFrame#setDefaultCloseOperation(int)メソッドで設定して、JFrameを終了しています。
  • 取り消し
    • WindowConstants.DO_NOTHING_ON_CLOSE を JFrame#setDefaultCloseOperation(int)メソッドで設定して、終了をキャンセルしています。

テキストが変更されているかどうかは、タイトルの文字列をフラグにして判断できるようになっています。

  • ドキュメントに文字列が追加されたとき、ソース側からfirePropertyChangeなどで、リスナーに変更をイベントで報告
  • リスナー側ではこのイベントを受け、JFrameのタイトルを変更

コメント

  • 私は以前 この終了をキャンセルするかどうかなどを確認するダイアログを作成したことがあります。あなたのソースコードは 参考のかいがあると思います。でも ひとつの問題があるんですけど、textareaに入力した文字列を削除する場合は JFrameのタイトルが変化されていません、どうですか? -- そうがい
    • こんばんは。「"123"->"12345"(45追加)->"123"(45削除)」と追加、削除をして元の状態に戻っても、タイトルが変化しないのは、仕様です。比較のコストが大きくなってしまいそうで嫌なので避けています。 -- terai