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

JFrameの終了をキャンセル

Posted by terai at 2004-08-09

概要

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

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

#screenshot

サンプルコード

public static final String ASTERISK_TITLEBAR = "unsaved";
private final JTextArea textarea = new JTextArea();
private final JButton saveButton = new JButton("save");
private final JFrame frame;
private final String title;
public MainPanel(final JFrame frame) {
  super(new BorderLayout());
  this.frame = frame;
  this.title = frame.getTitle();
  frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
  addPropertyChangeListener(new PropertyChangeListener() {
    public void propertyChange(PropertyChangeEvent e) {
      if(ASTERISK_TITLEBAR.equals(e.getPropertyName())) {
        Boolean flg = (Boolean)e.getNewValue();
        frame.setTitle((flg?"* ":"")+title);
      }
    }
  });
  frame.addWindowListener(new 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
    }
  });
  textarea.setText("Test Test Test");
  textarea.getDocument().addDocumentListener(new DocumentListener() {
    public void insertUpdate(DocumentEvent e) {
      fireUnsavedFlagChangeEvent(true);
    }
    public void removeUpdate(DocumentEvent e) {
      fireUnsavedFlagChangeEvent(true);
    }
    public void changedUpdate(DocumentEvent e) {}
  });
  saveButton.setEnabled(false);
  saveButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent ae) {
      System.out.println("Save(dummy)");
      fireUnsavedFlagChangeEvent(false);
    }
  });
  add(new JScrollPane(textarea));
  Box box = Box.createHorizontalBox();
  box.add(Box.createHorizontalGlue());
  box.add(new JButton(new AbstractAction("exit") {
    public void actionPerformed(ActionEvent e) {
      System.out.println("exit button");
      maybeExit();
    }
  }));
  box.add(Box.createHorizontalStrut(5));
  box.add(saveButton);
  add(box, BorderLayout.SOUTH);
  setPreferredSize(new Dimension(320, 240));
}

private void maybeExit() {
  if(title.equals(frame.getTitle())) {
    System.out.println("The document has already been saved,"+
                       " exit without doing anything.");
    frame.dispose();
    return;
  }
  java.awt.Toolkit.getDefaultToolkit().beep();
  String[] obj = {"unsaved documents", "Do you really want to exit?"};
  int retValue = JOptionPane.showConfirmDialog(frame, obj, "Select an Option",
                         JOptionPane.YES_NO_CANCEL_OPTION);
  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");
  }
}
private void fireUnsavedFlagChangeEvent(boolean unsaved) {
  if(unsaved) {
    saveButton.setEnabled(true);
    firePropertyChange(ASTERISK_TITLEBAR, Boolean.FALSE, Boolean.TRUE);
  }else{
    saveButton.setEnabled(false);
    firePropertyChange(ASTERISK_TITLEBAR, Boolean.TRUE, Boolean.FALSE);
  }
}

解説

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

  • public void windowClosing(WindowEvent e)
    • システムメニューでウィンドウを閉じようとしたときに呼び出されるリスナーのメソッド
      • タイトルバー左上クリックなどでポップアップするメニューで閉じるを選択
      • タイトルバー右上の×ボタンをクリック
      • Windowsなら、Alt+F4キーを押す
      • JButtonやJMenuなどから呼び出したい場合
        frame.getToolkit().getSystemEventQueue().postEvent(
          new WindowEvent(frame, WindowEvent.WINDOW_CLOSING));
        
    • frame.dispose();では、このメソッドは呼び出されない
    • このサンプルでは、frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);としているので、このメソッドが呼ばれても終了しない
      • ただし、システムメニューでウィンドウを閉じても、exitボタンと同じ処理になるように、終了処理を行うメソッド(maybeExit())を呼び出し、そこでドキュメントの保存状態によってframe.dispose();が呼ばれている
  • public void windowClosed(WindowEvent e)
    • frame.dispose() で、ウィンドウがクローズされたときに呼び出されるリスナーのメソッド
    • windowClosingの後に、windowClosedが発生呼び出される
      • ただし、frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);とし、システムメニューでウィンドウを閉じた(frame.dispose()ではなく)場合、呼び出される前にJVMがシャットダウン
    • このサンプルでは、frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);で、webstartから起動しても終了できるように、ここでSystem.exit(0);を呼んでJVMをシャットダウンしている

テキストが変更された場合、タイトル文字列の先頭にアスタリスクを付けています。

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

参考リンク

コメント

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