JDialogはHIDE_ON_CLOSEで残る

めったにやらないことですが、端末からのCUIプログラムでダイアログを表示した後、Javaが終了しないという現象に出会いました。普段はGUIで親のフレームから呼び出すので、親とともに終了しますから問題にはなりません。仕組みとして意識しておくと役立つこともあるかもということですが、調べてみます。

showOptionDialog()を使う場合

JOptionPaneのstaticメソッドである、showOptionDialog()を使う場合。

previewPanelは表示するプレビューのJScrollPaneのインスタンスです。

String[] goOrNot = {"印刷","中止"};
int rev = JOptionPane.showOptionDialog(
        null, previewPanel,
        "印刷プレビュー",
        JOptionPane.OK_CANCEL_OPTION,
        JOptionPane.PLAIN_MESSAGE,
        null,goOrNot,goOrNot[0]
);
//rev:"印刷":0,"中止":1
if (rev!=0) System.exit(0);

rev==1の時は中止を選択したときですSystem.exit(0)するので終了します。

rev==0のときは印刷に進みます。印刷しても最終的にキャンセルしてもプログラムは終了します。

こうして作ったダイアログは大きさをマウスのドラッグで変更できません。そこで次のようにしました。

createDialog()で作る場合

JOptionPaneのインスタンスからcreateDialog()で作る場合

JOptionPane を直接生成して使用する方法がAPI仕様に紹介されています。

マウスのドラッグでダイアログの大きさを変更できます。setResizable(true)が鍵です。

String[] goOrNot = {"印刷","中止"};
JOptionPane pane = new JOptionPane(
                           previewPanel, 
                           JOptionPane.PLAIN_MESSAGE, 
                           JOptionPane.DEFAULT_OPTION,
                           null,goOrNot,goOrNot[0]
                       );
JDialog dialog = pane.createDialog("印刷プレビュー" );
dialog.pack();
//dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
dialog.setResizable(true);
dialog.setVisible(true);
dialog.dispose(); //needs for ends /set HIDE_ON_CLOSE by default
String svalue = (String)pane.getValue();
if (svalue==null || !svalue.equals("印刷")) System.exit(0);

dialog.dispose();を加えています。これがないと、このままプログラムが末尾まで実行されてもJDialogのインスタンスが生きているので、プログラムが終了しません。

もちろん親のフレームからダイアログが呼ばれていることが多いので、放置されても親が終了になればダイアログも残ることはありません。しかし、キャラクタ端末からダイアログが呼ばれると終了しないことが起こります。

若干の説明

pane.getValue() は、JOptionPane のインスタンスであるpaneにセットされたgoOrNotの中で選択されたオブジェクトを返します。今の場合はgoOrNotは文字列の配列ですから、"印刷"か"中止"の文字列が返ります。

もしもダイアログのクローズボタンで終了するとsvalue==nullになります。

操作 getValue()の値
goOrNot"印刷" 印刷
goOrNotの"中止" 中止
Dialogの[✕] null

ダイアログは、dialog.setVisible(true);で表示されてプログラムは入力待ちで停止します。ユーザーがボタンを選択すると、ダイアログは見えなくなり、次の文に進みます。

この時、dialogはHIDE_ON_CLOSEがデフォルトなので、見えないだけで生きたまま残ります。このプログラムでは中止とnullの時はSystem.exit(0)するので終了します。svalueが"印刷"のときは印刷に進みますが、その後、印刷しても最終的にキャンセルしてもSystem.exit(0)されないのでプログラムは終了しません。

dialog.dispose();しておけば、showOptionDialog()を使った時と同様の振る舞いに出来ます。disposeされるのはdialogでpaneではありませんから、dispose後もpane.getValue();は実行できますし、previewPanelにもアクセスできます。たとえば、JListとかJTableを含んでいる場合です。

setDefaultCloseOperation()だとDialogの[✕]を押した時だけ終了となります。正常にgoOrNotを選択した場合は終了できません。

確認のプログラム(DialogTest.java)

起動時の引数でshowOptionDialogと custum dialog を選択します。どちらを選択しても印刷はしません。

DialogTest.java

package test;
import java.util.*;
import javax.swing.*;
 
public class DialogTest {
 
  public static void main(String[] args) {
    boolean done = false;
    String msgpanel = "Some Panel";
    if(1>args.length){
        System.out.println("java test.DialogTest 1  ... for showOptionDialog");
        System.out.println("java test.DialogTest 2  ... for custum dialog");
    }else{
      if(args[0].equals("1")){
        String[] goOrNot = {"印刷","中止"};
        int rev = JOptionPane.showOptionDialog(
                null, msgpanel,
                "showOptionDialog",
                JOptionPane.OK_CANCEL_OPTION,
                JOptionPane.PLAIN_MESSAGE,
                null,goOrNot,goOrNot[0]
        );
        //rev:"印刷":0,"中止":1
        System.out.println(goOrNot[rev]);
        System.out.println("msg:"+msgpanel);
        System.out.println("java test.DialogTest 1  ... for showOptionDialog");
        done = true;

      }
      else if(args[0].equals("2")){
        String[] goOrNot = {"印刷","中止"};
        JOptionPane pane = new JOptionPane(
                                   msgpanel, 
                                   JOptionPane.PLAIN_MESSAGE, 
                                   JOptionPane.DEFAULT_OPTION,
                                   null,goOrNot,goOrNot[0]
                               );
        //JDialog dialog = pane.createDialog( null, "印刷プレビュー" );
        JDialog dialog = pane.createDialog("custum dialog" );
        dialog.pack();
        dialog.setResizable(true);
        dialog.setVisible(true);
        String svalue = (String)pane.getValue();
        dialog.dispose(); //needs for ends /set HIDE_ON_CLOSE by default
        System.out.println("1:"+svalue);
        String svalue2 = (String)pane.getValue();
        System.out.println("2:"+svalue2);
        System.out.println("msg:"+msgpanel);
        System.out.println("java test.DialogTest 2  ... for custum dialog");
        done = true;

      }
    }
    if(done)  System.out.println("**\ndone");
    else System.out.println("argment must 1 or 2");
  }
}

実行結果(showOptionDialog)

引数1でshowOptionDialogです。[印刷]を選択するとプログラムは終了します。dispose()は書いてありません。

$ java test.DialogTest 1
印刷
msg:Some Panel
java test.DialogTest 1  ... for showOptionDialog
**
done

やるまでもないですが、[中止]を選択するとプログラムは終了します。

$ java test.DialogTest 1
中止
msg:Some Panel
java test.DialogTest 1  ... for showOptionDialog
**
done

実行結果( custum dialog )

引数2で custum dialog です。dialog.dispose();がない場合は、■でカーソル点滅したままになります。Ctrl+Cで終了するしかありません。

$ java test.DialogTest 2
1:中止
2:中止
msg:Some Panel
java test.DialogTest 2  ... for custum dialog
**
done
■

dialog.dispose();があれば、終了します。dispose後もgetValue()が使えることを示しています。[印刷]を選択した場合。

$ java test.DialogTest 2
1:印刷
2:印刷
msg:Some Panel
java test.DialogTest 2  ... for custum dialog
**
done

やるまでもないですが、[中止]を選択した場合。

$ java test.DialogTest 2
1:中止
2:中止
msg:Some Panel
java test.DialogTest 2  ... for custum dialog
**
done

Dialogの[✕]の場合も終了します。getValue()はnullを返します。

$ java test.DialogTest 2
1:null
2:null
msg:Some Panel
java test.DialogTest 2  ... for custum dialog
**
done

再利用できるか(DialogReuse.java)

デフォルトが隠すだけなのは、再利用することを考えているのかもしれないと、やってみます。

DialogReuse.java

package test;
import java.util.*;
import javax.swing.*;
 
public class DialogReuse {
 
    public static void main(String[] args) {
        int ct = 0;
        String msgpanel = "第1問:正しいのはどっち";
        String[] goOrNot = {"Dimention","Dimension"};
        JOptionPane pane = new JOptionPane(
                                   msgpanel, 
                                   JOptionPane.PLAIN_MESSAGE, 
                                   JOptionPane.DEFAULT_OPTION,
                                   null,goOrNot,goOrNot[0]
                               );
        JDialog dialog = pane.createDialog("custum dialog" );
        dialog.pack();
        dialog.setResizable(true);
        dialog.setVisible(true);
        ct++;
        String svalue = (String)pane.getValue();
        System.out.println("1:"+svalue+"と答えました。("+ct+")");

        dialog.setVisible(true);
        ct++;
        svalue = (String)pane.getValue();
        System.out.println("1:"+svalue+"と答えました。("+ct+")");

        System.out.println("いまからmsgpanelの値を変えてダイアログが変化するでしょうか");
        msgpanel = "メッセージが第1問でなくなりましたか";
        goOrNot[0] = "はい";
        goOrNot[1] = "いいえ";
        dialog.setVisible(true); //見えるのは"Dimention","Dimension"
        ct++;
        svalue = (String)pane.getValue(); //返るのは"はい","いいえ"
        System.out.println("1:"+svalue+"と答えました。("+ct+")");

        pane.setMessage("第2問:正しいのはどれ?");
        pane.setOptions(new String[]{"String","Srting","Stwing"});
        dialog.setVisible(true);
        ct++;
        svalue = (String)pane.getValue();
        System.out.println("2:"+svalue+"と答えました。("+ct+")");

      }
}

再利用のテスト結果

$ java test.DialogReuse 
1:Dimensionと答えました。(1)
1:Dimensionと答えました。(2)
いまからmsgpanelの値を変えてダイアログが変化するでしょうか
1:いいえと答えました。(3)
2:Stringと答えました。(4)
^C

(2) .setVisible(true);で再利用可能

(3) これでメッセージなどを変更できないのは当然。ただ、参照型であることで影響が出るものもある。使うべきではない

(4) メッセージや選択オプションを変更するメソッドが用意されている。