こんにちは。SI部の吉原です。

Java8の導入に伴い今までJavaの標準GUIとして使われてきた Swing に代わり、JavaFXが標準GUIに取って代わる事となりました。
そこで、今回は長らくJavaの標準GUIとして使われてきたSwingと、JavaFX 2.0の違いについて書こうと思います。

JavaFXとは

まず始めにJavaFXについて簡単に説明します。
JavaFXは、Javaの名前が入っておりますが、Javaとは別個のリッチクライアント向けの新しいプラットフォームです。
とは言ってもJavaの技術をベースに構築されているため、Javaの多様なAPIを違和感なく使用できます。
また、Swingアプリケーション内でJavaFXを使用した拡張を行う事も可能です。

JavaFXの一番の目玉は、FXMLとCSSであると私は思います。
このFXMLによって、画面をXML形式で定義できるようになるからです!
ただ、今回はSwingとの比較を行うため、Swingに対する拡張性を意識してFXMLは使用せずに比較を行おうと思います。

SwingとJavaFXの違い

比較のためにまず同じウィンドウと簡単なイベントをSwingとJavaFXで作ってみて、コーディングがどのようなものか比較してみます。
作成する画面は以下のような簡易的な親画面と子画面(ダイアログ)の2つとなります。

親画面

FX_Window01

子画面(モーダルダイアログ)

FX_dialog01

SwingとJavaFXの画面表示とイベント処理のプログラム全文が以下のようなものとなります。

Swing プログラム

public class SwingWindow extends JFrame implements ActionListener {
    /** ActionEvent名 */
    private static final String ACT_MENUBAR01 = "Action_MenuBar1";
    private static final String ACT_MENUBAR02 = "Action_MenuBar2";
    private static final String ACT_BTN01     = "Action_Btn01";
    private static final String ACT_BTN02     = "Action_Btn02";
    /** 画面コンポーネント */
    private SwingWindow mainFrame;
    private JTextField textField;
    private JDialog dialog;

    /** メイン画面 */
    public void start() throws Exception{
        /** フレーム */
        mainFrame = new SwingWindow();
        mainFrame.setTitle("JavaSwing Window");
        mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        mainFrame.setSize(300, 200);
        mainFrame.setLocationRelativeTo(null);

        /** メニューバー */
        JMenu menu1 = new JMenu("ファイル(F)");
        JMenuItem menuItem1 = new JMenuItem("終了");
        menu1.add(menuItem1);
        JMenu menu2 = new JMenu("ヘルプ(H)");
        JMenuItem menuItem2 = new JMenuItem("作成情報");
        menu2.add(menuItem2);

        JMenuBar menuBar = new JMenuBar();
        menuBar.add(menu1);
        menuBar.add(menu2);

        /** ボディ部 */
        JPanel bodyPane = new JPanel();
        bodyPane.setPreferredSize(new Dimension(300, 100));
        bodyPane.setLayout(new BoxLayout(bodyPane, BoxLayout.Y_AXIS));
        bodyPane.setAlignmentX(JComponent.LEFT_ALIGNMENT);

        // メッセージ
        JPanel msgPane = new JPanel();
        msgPane.setMaximumSize(new Dimension(300, 30));
        msgPane.setLayout(new FlowLayout(FlowLayout.LEFT));
        msgPane.add(new JLabel("好きな文字を入力してください"));
        bodyPane.add(msgPane);
        bodyPane.add(Box.createRigidArea(new Dimension(0,3)));

        // inputテキスト
        JPanel inputPane = new JPanel();
        inputPane.setMaximumSize(new Dimension(300, 30));
        inputPane.setLayout(new FlowLayout(FlowLayout.LEFT));
        textField = new JTextField();
        textField.setPreferredSize(new Dimension(100, 20));
        inputPane.add(new JLabel("Input"));
        inputPane.add(textField);
        bodyPane.add(inputPane);
        bodyPane.add(Box.createRigidArea(new Dimension(0,3)));

        // 表示ボタン
        JPanel btnPane = new JPanel();
        btnPane.setMaximumSize(new Dimension(300, 40));
        btnPane.setLayout(new FlowLayout(FlowLayout.LEFT));
        JButton btnDisp = new JButton("表示");
        btnDisp.addActionListener(this);
        btnPane.add(btnDisp);
        bodyPane.add(btnPane);

        /** アクションイベント設定 */
        menuItem1.setActionCommand(ACT_MENUBAR01);
        menuItem1.addActionListener(mainFrame);
        menuItem2.setActionCommand(ACT_MENUBAR02);
        menuItem2.addActionListener(mainFrame);
        btnDisp.setActionCommand(ACT_BTN01);

        /** 画面表示設定 */
        mainFrame.setJMenuBar(menuBar);
        mainFrame.getContentPane().add(bodyPane);
        SwingUtilities.updateComponentTreeUI(mainFrame);
        mainFrame.setVisible(true);
    }

    /** アクションイベント */
    public void actionPerformed(ActionEvent ae) {
        if(ae.getActionCommand().equals(ACT_MENUBAR01)) {
            dispose();
        } else if (ae.getActionCommand().equals(ACT_MENUBAR02)) {
            dispModalDialog(this, "作成情報", "Version:1.0.0", "Date:2015-07-01");
        } else if (ae.getActionCommand().equals(ACT_BTN01)) {
            dispModalDialog(mainFrame, "Text", textField.getText());
        } else if (ae.getActionCommand().equals(ACT_BTN02)) {
            this.dialog.setVisible(false);;
        }
    }

    /** ダイアログ表示 */
    private void dispModalDialog (JFrame parentWindow, String title, String... msg) {
        /** ダイアログ生成 */
        int x = parentWindow.getX()+(parentWindow.getWidth()-WIDTH)/4; // 表示位置X
        int y = parentWindow.getY()+(parentWindow.getHeight()-HEIGHT)/4; // 表示位置Y
        dialog = new JDialog(parentWindow, title, true);
        dialog.getContentPane().setLayout(new FlowLayout(FlowLayout.CENTER));
        dialog.setBounds(x, y, 200, 160);

        // ダイアログ内容
        JPanel panel = new JPanel();
        panel.setMaximumSize(new Dimension(200, 150));
        panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
        panel.add(new JLabel(" ")); // 空行
        for (String s : msg) {
            JLabel label = new JLabel(s);
            label.setMaximumSize(new Dimension(200,25));
            label.setHorizontalAlignment(JLabel.CENTER);
            panel.add(label);
        }
        panel.add(new JLabel(" ")); // 空行

        JButton button = new JButton("close");
        button.setActionCommand(ACT_BTN02);
        button.addActionListener(this);
        panel.add(button);

        /** ダイアログ表示設定 */
        dialog.getContentPane().add(panel);
        dialog.setVisible(true);
    }
}

JavaFX プログラム

public class JavaFxWindow extends Application {
    /** メイン画面 */
    @Override
    public void start(Stage primaryStage) {
        /** フレーム生成 */
        primaryStage.setTitle("JavaFX Window");
        BorderPane bordPane = new BorderPane();
        bordPane.setPrefSize(300, 100);

        /** メニューバー */
        MenuBar menuBar = new MenuBar();
        menuBar.setUseSystemMenuBar(true);
        menuBar.setPrefSize(300, 25);Menu menu1 = new Menu("ファイル(F)");
        MenuItem menuItem1 = new MenuItem("終了");
        menu1.getItems().add(menuItem1);
        Menu menu2 = new Menu("ヘルプ(H)");
        MenuItem menuItem2 = new MenuItem("作成情報");
        menu2.getItems().add(menuItem2);menuBar.getMenus().add(menu1);
        menuBar.getMenus().add(menu2);

        /** ボディ部 */
        FlowPane flowPane = new FlowPane();
        flowPane.setPrefSize(300, 100);
        flowPane.setOrientation(Orientation.VERTICAL);

        // メッセージ
        flowPane.getChildren().add(new Label("好きな文字を入力してください"));
        flowPane.getChildren().add(new Label(""));

        // inputテキスト
        HBox hbox = new HBox(20d);
        hbox.setAlignment(Pos.CENTER_LEFT);
        hbox.getChildren().add(new Label("Input"));
        TextField textField = new TextField();
        hbox.getChildren().add(textField);
        flowPane.getChildren().add(hbox);
        flowPane.getChildren().add(new Label(""));

        // 表示ボタン
        Button btnDisp = new Button("表示");
        flowPane.getChildren().add(btnDisp);

        /** アクションイベント設定 */
        menuItem1.setOnAction( e -> System.exit(0) );
        menuItem2.setOnAction( e -> dispModalDialog(primaryStage, "作成情報", "Version:1.0.0", "Date:2015-07-01"));
        btnDisp.setOnAction( e -> dispModalDialog(primaryStage, "Text", textField.getText()));

        /** 画面表示設定 */
        bordPane.setTop(menuBar);
        bordPane.setCenter(flowPane);
        Scene scene = new Scene(bordPane, 300, 200);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /** ダイアログ表示 */
    private void dispModalDialog (Window parentWindow, String title, String... msg) {
        /** ダイアログ生成 */
        Stage dialog = new Stage();
        dialog.setTitle(title);
        dialog.initModality(Modality.WINDOW_MODAL);
        if (parentWindow != null) dialog.initOwner(parentWindow);

        // ダイアログ内容
        final StackPane pane = new StackPane();
        {
            final VBox vbox = new VBox();
            pane.getChildren().add(vbox);

            vbox.setAlignment(Pos.CENTER);
            vbox.setMinWidth(150);
            vbox.getChildren().add(new Label(""));// 空行
            for (String s: msg) {
                vbox.getChildren().add(new Label(s));
            }
            vbox.getChildren().add(new Label(""));// 空行

            Button btnOk = new Button();
            btnOk.setText("close");
            btnOk.setOnAction((ActionEvent) -> { dialog.close(); });

            vbox.getChildren().add(btnOk);
            vbox.getChildren().add(new Label(""));// 空行
        }

        /** ダイアログ表示設定 */
        dialog.setScene(new Scene(pane));
        dialog.showAndWait();
    }
}

長くなってしまいましたが、全体的に見ると画面を構成する各種部品に違いはあれど、実装の仕方にそこまでの大きな差はないように思えます。

それは、Swingが[JFrame]に部品(コンポーネント)を貼り付けていくのに対し、JavaFXも同様に[Stage]をベースに部品(コントロール)を貼り付けてくからではないかと思います。
また、ボタンやラベルなどの単純な部品は、プロパティも含めほぼ一緒となっています。
そのため、画面描画プログラムに大きな違いは感じないのかと思います。

レイアウト

しかし、レイアウトを設定する部分はちょっと違ってきます。
Swingでは以下のように コンテナ + レイアウトマネージャ の組み合わせでレイアウトを設定します。

    JPanel bodyPane = new JPanel();
    bodyPane.setPreferredSize(new Dimension(300, 100));
    bodyPane.setLayout(new BoxLayout(bodyPane, BoxLayout.Y_AXIS));
    bodyPane.setAlignmentX(JComponent.LEFT_ALIGNMENT);</pre>

これに対して、JavaFXではコンテナがレイアウト機能を含んでますのでswingでいうレイアウトマネージャー機能を持っているコンテナを使用だけでレイアウト設定ができます。
ですので、JavaFXでは以下のように、使いたいレイアウトに対応した何たら Pane を定義するだけで良いのです。

    FlowPane flowPane = new FlowPane();
    flowPane.setPrefSize(300, 100);
    flowPane.setOrientation(Orientation.VERTICAL);</pre>

ダイアログ

また、ダイアログについても少し変わっています。
ダイアログを作成する場合、Swingではダイアログ用のユーティリティクラスを使用します。

private void dispModalDialog (JFrame parentWindow, String title, String... msg) {
    dialog = new JDialog(parentWindow, title, true);
    dialog.getContentPane().setLayout(new FlowLayout(FlowLayout.CENTER));
    dialog.setBounds(x, y, 200, 160);
    ~略~
    dialog.getContentPane().add(panel);
    dialog.setVisible(true);
}

これに対し、JavaFXにはダイアログ用の部品は存在しません。
親画面と同じ Stage を使いダイアログの作成を行います。
そのため少し手間はかかりますが、自由度が高くなり色々な事ができるようになります。

private void dispModalDialog (Window parentWindow, String title, String... msg) {
    Stage dialog = new Stage();
    dialog.setTitle(title);
    dialog.initModality(Modality.WINDOW_MODAL);
    if (parentWindow != null) dialog.initOwner(parentWindow);
    ~略~
    dialog.setScene(new Scene(pane));
    dialog.showAndWait();
}

アクションイベント

次にアクションイベントの設定方法ですが、これについては大きな違いがあります。
Swingでは、イベントリスナーを使用してイベント処理を行います。
ですので、コンポーネント毎にアクションイベントのIDの設定と、イベント処理内容を別々のメソッドに記述します。

/** メイン画面 */
public void start() throws Exception{
~略~
    /** アクションイベント設定 */
    menuItem1.setActionCommand(ACT_MENUBAR01);
    menuItem1.addActionListener(mainFrame);
    menuItem2.setActionCommand(ACT_MENUBAR02);
    menuItem2.addActionListener(mainFrame);
    btnDisp.setActionCommand(ACT_BTN01);
~略~
}

/** アクションイベント */
public void actionPerformed(ActionEvent ae) {
    if(ae.getActionCommand().equals(ACT_MENUBAR01)) {
        dispose();
    } else if (ae.getActionCommand().equals(ACT_MENUBAR02)) {
        dispModalDialog(this, "作成情報", "Version:1.0.0", "Date:2015-07-01");
    } else if (ae.getActionCommand().equals(ACT_BTN01)) {
        dispModalDialog(mainFrame, "Text", textField.getText());
    } else if (ae.getActionCommand().equals(ACT_BTN02)) {
        this.dialog.setVisible(false);;
    }
}

しかし、JavaFXではイベントリスナーがなく、各コントロールに直接アクションイベントの設定を行えます。
アクションイベントをキャッチするメソッドを作る必要がないので、クラス変数を多用せずに実装できます。
また、ラムダ式を使う事で非常にすっきりしたコードとなります。

/** メイン画面 */
public void start(Stage primaryStage) {
~略~
    /** アクションイベント設定 */
    menuItem1.setOnAction( e -> System.exit(0) );
    menuItem2.setOnAction( e -> dispModalDialog(primaryStage, "作成情報", "Version:1.0.0", "Date:2015-07-01"));
    btnDisp.setOnAction( e -> dispModalDialog(primaryStage, "Text", textField.getText()));
~略~
}

総括

全体的に比べてみると、JavaFXもSwingと同様にGUI部品を配置して、イベント処理を行うという基本的な部分は同じです。
そのため、画面描画の実装方法に細やかな違いはあれど、大きな違いはありません。

しかし、JavaFXではGUIを組み込むためのウィンドウ本体がJavaFX側で用意されるなど、根本的な部分は大分変わっております。
また、GUI部品自体にアクションイベントの設定ができるため、Swingと比較して大分使い勝手が良くなったと思えます。

まとめ

今回は、Swingとの比較を行うため、GUIプログラミングに焦点をあてて比較を行いました。
Swingアプリケーション内で、JavaFXを使用しての拡張が可能であるため、
コードは簡潔にはなりましたが、実装内容が大きくは変わるといった事はないと感じます。

しかし、JavaFXの特徴はFXMLにあると思います。
FXMLを利用すれば、画面構築部分は、XML形式で記述されたFXMLファイルを読み込むだけで完成します。
機会があればFXMLを使った画面作成など、JavaFXについてご紹介させていただければと思います。