Locked History Actions

JavaFX/Dialogs

ダイアログの使い方

ここでは特にButtonTypeボタンの有無による動作の違いについて述べる。

Converting ButtonType to Return Type

Dialogの型パラメータはDialogの返り値を示すはずだが、ボタンをつけると全く意味がなくなる。以下のresultはString型のはずだが、そのままではButtonTypeが返されてしまうため、ClassCastExceptionが発生する。この場合ResultConverterの設定が必要。

Type Parameter of Dialog class should designate the return type of it. But if you use some buttons, it has no meaning. Actually the Dialog returns ButtonType. So the following code will occur ClassCastException. You need ResultConverter.

import java.util.*;

import javafx.application.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.stage.*;

public class DialogTest1 extends Dialog<String> {

  public DialogTest1() {
    Label label = new Label("test dialog");
    this.getDialogPane().setContent(label);
    this.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
    // setResultConverter(type-> type == ButtonType.OK? "OK":"CANCEL");
  }
    
  public static class Kicker extends Application {
    DialogTest1 dialogTest = new DialogTest1();
    public void start(Stage stage) {
      Button open = new Button("open");
      open.setOnAction(a-> {        
        Optional<String> result = dialogTest.showAndWait();
        System.out.println("result " + result.orElse(null)); <-- Without ResultConverter, ClassCastException raised
      });
      stage.setScene(new Scene(open));
      stage.show();
    }
  }
  
  public static void main(String[] args) throws Exception {
    Kicker.launch(Kicker.class);
  }
}

ちなみに、ダイアログ表示中にEscapeキー押下すると、CANCELとみなされる。また、ResultConverterにてOKの時にnull値を返すと、それもCANCELとみなされる。

Hitting Escape key on the Dialog is regarded as CANCEL. Returning (String)null value when clicking OK button is also regarded as CANCEL.

Prevent closing when OK

OKボタンが押された時に条件が満たされない場合にクローズを拒否するには以下。

Prevent closing without condition fulfilled.

  public DialogTest1() {
    Label label = new Label("test dialog");
    this.getDialogPane().setContent(label);
    this.getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
    setResultConverter(type-> type == ButtonType.OK? null:"CANCEL");
    ((Button)getDialogPane().lookupButton(ButtonType.OK)).addEventFilter(ActionEvent.ACTION, e -> {
      if (true) e.consume(); 
    });
  }

Dialog without Buttons

ボタンの無いダイアログの場合、GUIからクローズする方法がなくなる。ダイアログウインドウのXボタンを押しても閉じない。プログラム的に閉じる必要があるが、これはresultPropertyを変更することで行うらしい。ただし、同じ値を設定しても何も起こらないので、表示前に初期化しておく。

There's no way to close a Dialog without buttons from GUI operation. Even if clicking dialog window's X button, it won' close. In this case, you should set resultProperty when it should be closed. Notice that setting the same value to resultProperty doesn't occur closing. So initialize it beforehand.

import java.util.*;

import javafx.application.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.stage.*;

public class DialogTest2 extends Dialog<String> {

  public DialogTest2() {
    Label label = new Label("test dialog");
    this.getDialogPane().setContent(label);
  }

  public static class Kicker extends Application {
    DialogTest2 dialogTest = new DialogTest2();
    public void start(Stage stage) {
      Button open = new Button("open");
      open.setOnAction(a-> {        
        new Thread() { public void run() {
          try { Thread.sleep(1000); } catch (InterruptedException ex) {}
          Platform.runLater(()-> dialogTest.setResult("CLOSED"));
        }}.start();
        dialogTest.setResult(null);
        Optional<String> result = dialogTest.showAndWait();
        System.out.println("result " + result.orElse(null));
      });
      stage.setScene(new Scene(open));
      stage.show();
    }
  }
  
  public static void main(String[] args) throws Exception {
    Kicker.launch(Kicker.class);
  }
}

別の方法としては以下。

Another way is the following.

import java.util.*;

import javafx.application.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.stage.*;

public class DialogTest2 extends Dialog<String> {

  public DialogTest2() {
    Label label = new Label("test dialog");
    this.getDialogPane().setContent(label);
  }

  public static class Kicker extends Application {
    DialogTest2 dialogTest = new DialogTest2();
    public void start(Stage stage) {
      Window window = dialogTest.getDialogPane().getScene().getWindow();
      Button open = new Button("open");
      open.setOnAction(a-> {        
        new Thread() { public void run() {
          try { Thread.sleep(1000); } catch (InterruptedException ex) {}
          Platform.runLater(()-> window.hide());
        }}.start();
        Optional<String> result = dialogTest.showAndWait();
        System.out.println("result " + result.orElse(null));
      });
      stage.setScene(new Scene(open));
      stage.show();
    }
  }
  
  public static void main(String[] args) throws Exception {
    Kicker.launch(Kicker.class);
  }
}

Closing by Dialog window's X button

上とは逆にダイアログウインドウのXボタンで閉じたい場合には以下。

Contrary to the above, the following code closes the Dialog when Dialog window's X button clicked.

import java.util.*;

import javafx.application.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.stage.*;

public class DialogTest3 extends Dialog<String> {

  public DialogTest3() {
    Label label = new Label("test dialog");
    this.getDialogPane().setContent(label);
    Window window = getDialogPane().getScene().getWindow();
    window.setOnCloseRequest(event -> window.hide());
  }

  public static class Kicker extends Application {
    DialogTest3 dialogTest = new DialogTest3();
    public void start(Stage stage) {
      Button open = new Button("open");
      open.setOnAction(a-> {        
        Optional<String> result = dialogTest.showAndWait();
        System.out.println("result " + result.orElse(null));
      });
      stage.setScene(new Scene(open));
      stage.show();
    }
  }
  
  public static void main(String[] args) throws Exception {
    Kicker.launch(Kicker.class);
  }
}

Setting icon to Dialog

ダイアログのウインドウを取得すると、それはStageなのでStageと同様にiconを設定できる。

If you get the Window of a Dialog, it should be a Stage. So you can set icon to it.

  Stage stage = (Stage)dialog.getDialogPane().getScene().getWindow();
  stage.getIcons().add(iconImage);

stylesheetsはDialogPaneにある。 Stylesheets is in DialogPane.

   ObservableList<String> stylesheets = dialog.getDialogPane().getStylesheets()

Binding a modeless Dialog with a ToggleButton bidirectonallly

モードレスなダイアログの開閉をトグルボタンを双方向的に結びつける。DialogのshowingProperty()はリードオンリーのため、新たにプロパティを作る。

showingProperty() of Dialog is read only. So we need another property.

import javafx.application.*;
import javafx.beans.property.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.stage.*;

public class DialogTest4 extends Dialog<String> {

  SimpleBooleanProperty showing = new SimpleBooleanProperty();
  
  public DialogTest4() {
    initModality(Modality.NONE);
    Label label = new Label("test dialog");
    DialogPane pane = getDialogPane();
    pane.setContent(label);
    Window window = pane.getScene().getWindow();
    window.setOnCloseRequest(event -> showing.set(false));
    window.setOnShowing(e-> System.out.println("showing"));
    window.setOnHiding(e->  System.out.println("hiding"));
    showing.addListener((ob,o,n)-> {
      if (n) show();
      else window.hide();
    });
  }

  public static class Kicker extends Application {
    DialogTest4 dialogTest = new DialogTest4();
    public void start(Stage stage) {
      ToggleButton openClose = new ToggleButton("open/close");      
      openClose.selectedProperty().bindBidirectional(dialogTest.showing);
      stage.setScene(new Scene(openClose));
      stage.setX(600);      
      stage.show();
    }
  }
  
  public static void main(String[] args) throws Exception {
    Kicker.launch(Kicker.class);
  }
}

Recovering Dialog Window position and size exactly same as previous run

例えば、いったんアプリを終了し、再起動した後にダイアログの位置とサイズを前回と全く同じにしたいとする。この場合、ダイアログのウインドウ位置は復旧することができるが、サイズを復旧することはできない。なぜなら、Dialogに対してもWindowに対してもsetWidth/Heightを行ったところでそれは無視されてしまう。ダイアログの初回表示時に自動リサイズされてしまうからである。

完全に前回と同じにしたい場合、DialogPaneのサイズを指定する必要がある。つまり、Window位置とDialogPaneのサイズを指定することになる。以下の例はDialog1と全く同じ位置、サイズでDialog2を表示させる例である。

Suppose you want a Dialog being exactly same position and size as previous Application run. In general you can do it about position but not size. If you set Dialog or window size, they will be ignored because of automatic packing.

In this case you should specify size of DialogPane. The following code does this. Dialog2 will be exactly same position and size as Dialog1.

import javafx.application.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.*;

public class DialogTest5 extends Dialog<String> {

  DialogPane pane;
  public DialogTest5(String title) {
    initModality(Modality.NONE);
    setTitle(title);    
    setResizable(true);    
    Label label = new Label("test " + title);
    pane = getDialogPane();
    pane.setContent(label);
  }

  public static class Kicker extends Application {
    
    DialogTest5 oneDlg = new DialogTest5("one");
    DialogTest5 twoDlg = new DialogTest5("two");
    public void start(Stage stage) {
      Button oneButton = new Button("one");
      oneButton.setOnAction(e-> oneDlg.show());
      Button twoButton = new Button("two");
      twoButton.setOnAction(e-> { copyOneToTwo(); twoDlg.show(); });
      stage.setScene(new Scene(new HBox(oneButton, twoButton)));
      stage.show();
    }
    
    private void copyOneToTwo() {
      twoDlg.setX(oneDlg.getX());
      twoDlg.setY(oneDlg.getY());
      double width = oneDlg.pane.getWidth();
      double height = oneDlg.pane.getHeight();
      twoDlg.pane.setPrefWidth(width);
      twoDlg.pane.setPrefHeight(height);
    }    
  }
  
  public static void main(String[] args) throws Exception {
    Kicker.launch(Kicker.class);
  }
}