6 JavaFXでのSwingアプリケーションの実装
この章では、Swingアプリケーションについて検討し、このアプリケーションをJavaFXで実装する方法について学習します。
この章の内容を理解するには、図6-1に示すConverterアプリケーションに関する知識が必要です。 このアプリケーションは、距離に関する測定値をメートル法と米国単位間で変換します。 
Swingで開発したConverterアプリケーションの分析
Javaプログラミング言語を使用したこの例の実装の詳細は、Swingチュートリアルのパネルの使用方法に関するトレールおよびモデルの使用に関するトレールコースを参照してください。 特に、グラフィカル・ユーザー・インタフェース(GUI)については、パネルに関するトレールを参照してください。
Converterアプリケーションのコードについて学習するには、例の索引から入手できる、このアプリケーションのNetBeansプロジェクトまたはソース・ファイルをダウンロードします。
Swingコンポーネントは、モデルを使用します。 プロジェクトの内容を確認すると、Converterアプリケーションのモデルを定義するConverterRangeModelおよびFollowerRangeModelクラスが含まれていることがわかります。 
Converterアプリケーションは、次のファイルで構成されています。
- 
ConversionPanel.java- コンポーネントを保持するためのカスタムJPanelサブクラスが含まれています。
- 
Converter.java- アプリケーションのメイン・クラスが含まれています。
- 
ConverterRangeModel.java- 上部のスライダのモデルを定義します。
- 
FollowerRangeModel.java- 下部のスライダのモデルを定義します。
- 
Units.java-Unitオブジェクトを作成します。
各テキスト・フィールドとそのスライダ間の同期は、値の変更をリスニングするイベント・ハンドラによって実装されます。
JavaFXでのConverterアプリケーションの計画
Converterアプリケーションには、テキスト・フィールド、スライダ、コンボ・ボックスなどのコンポーネントを保持する、類似した2つのパネルが含まれています。 パネルにはタイトルがあります。 javafx.scene.controlパッケージのTitlePaneクラスは、ConverterアプリケーションのGUIに最適です。 
ここでは、ConversionPanelクラスを実装し、このクラスの2つのインスタンスをConverterアプリケーションのグラフィカル・シーンに追加します。
まず、1つのConversionPanelオブジェクト内のコンポーネントを、次のように同期する必要があります。 スライダ上のノブを動かすたびにテキスト・フィールドの値を更新し、その逆も同様にする必要があります。つまり、テキスト・フィールドの値を変更するたびにスライダ上のノブの位置を調整する必要があります。 
コンボ・ボックスから別の値を選択するとすぐにテキスト・フィールドの値を更新し、それに伴いスライダ上のノブの位置も更新する必要があります。
2番目に、両方のConversionPanelオブジェクトを同期する必要があります。 1つのパネル上で変更が発生するとすぐに、別のパネル上の対応するコンポーネントを更新する必要があります。 
metersというDoublePropertyオブジェクトを使用してパネル間の同期を実装し、fromMetersとtoMetersという2つのInvalidationListenerオブジェクトを作成および登録することによって、テキスト・フィールドとコンボ・ボックスのプロパティ内の変更をリスニングすることをお薦めします。 1つのパネル上のテキスト・フィールドのプロパティが変更されるたびに、アタッチされたInvalidationListenerオブジェクトのinvalidatedメソッドがコールされ、metersプロパティが更新されます。 metersプロパティが変更されるため、metersプロパティにアタッチされたInvalidationListenerオブジェクトのinvalidatedメソッドがコールされ、別のパネル上の対応するテキストが更新されます。 
同様に、1つのパネル上のコンボ・ボックスのプロパティが変更されるたびに、アタッチされたInvalidationListenerオブジェクトのinvalidatedメソッドがコールされ、このパネル上のテキスト・フィールドが更新されます。
スライダの値とmetersオブジェクトの値が同期されるようにするには、双方向バインディングを使用します。
JavaFXプロパティとバインディングの詳細は、「JavaFXプロパティとバインディングの使用」を参照してください。
JavaFXでのConverterアプリケーションの作成
NetBeans IDEで新しいJavaFXプロジェクトを作成し、Converterという名前を付けます。 Unit.javaファイルをSwingアプリケーションからConverterプロジェクトにコピーします。 新しいjavaクラスをこのプロジェクトに追加し、ConversionPanel.javaという名前を付けます。 
GUIを作成するための標準的なJavaFXパターン
JavaFXでのConverterアプリケーションのGUIの作成を開始する前に、例6-1に示す、SwingアプリケーションでのGUIの標準的な作成パターンを確認します。
例6-1
public class Converter { 
    private void initAndShowGUI() {
        ...
    }
    public static void main(String[] args) { 
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                initAndShowGUI();
            }
        });
    }
}
このパターンをJavaFXにマップするには、例6-2に示すように、javafx.application.Applicationクラスを拡張し、startメソッドをオーバーライドし、mainメソッドをコールします。
例6-2
import javafx.application.Application;
import javafx.stage.Stage;
public class Converter extends Application {
    @Override
    public void start(Stage t) {
        ...
    }
    public static void main(String[] args) {
        launch(args);
    }
}
NetBeans IDEで新しいJavaFXプロジェクトを作成すると、このパターンが自動的に生成されます。 ただし、特にテキスト・エディタを使用する場合は、JavaFXでのGUI作成の基本的なアプローチを理解しておくことが重要です。
コンテナおよびレイアウト
Swingでは、コンテナおよびレイアウト・マネージャは異なるエンティティです。 JPanelオブジェクト、JComponentオブジェクトなどのコンテナを作成し、このコンテナのレイアウト・マネージャを設定します。 特定のレイアウト・マネージャを割り当て、コード内に.add()を記述することも、レイアウト・マネージャを割り当てないようにすることもできます。 
JavaFXでは、コンテナ自体がその子ノードのレイアウトを処理します。 Vboxオブジェクト、FlowPaneオブジェクト、TitledPaneオブジェクトなどの特定のレイアウト・ペインを作成した後、.getChildren().add()メソッドを使用して、コンテンツをその子ノードのリストに追加します。 
JavaFXには、ペインと呼ばれる様々なレイアウト・コンテナ・クラスがあり、その一部に対応するクラスがSwingでも提供されています。たとえば、JavaFXのFlowPaneクラスは、SwingのFlowLayoutクラスに対応しています。
詳細は、JavaFXでのレイアウトの操作を参照してください。
UIコントロール
JavaFX SDKでは、標準的なUIコントロールのセットが提供されます。 一部のUIコントロールは、Swingのコントロールと対応しています。たとえば、JavaFXのButtonクラスとSwingのJButton、JavaFXのSliderとSwingのJSlider、およびJavaFXのTextFieldとSwingのJTextFieldがあります。 
JavaFXでConverterアプリケーションを実装する場合、TextField、SliderおよびComboBoxクラスによって提供される標準的なUIコントロールを使用できます。
詳細は、「JavaFX UIコントロールの使用」を参照してください。
ユーザー・アクションとバインディングに基づいて通知を取得するメカニズム
Swingでは、任意のコンポーネントにリスナーを登録し、サイズ、位置、可視性などのコンポーネント・プロパティの変更をリスニングしたり、コンポーネントにキーボード・フォーカスがあるかないか、マウスをコンポーネント上でクリックする、押す、離すなどのイベントをリスニングできます。
JavaFXでは、リスナーを登録できるプロパティのセットがオブジェクトごとに提供されます。 このリスナーは、プロパティの値を変更するたびにコールされます。
オブジェクトは、別のオブジェクトのプロパティにおける変更のリスナーとして登録できることに注意してください。 このため、バインディング・メカニズムを使用すると、2つのオブジェクトの一部のプロパティを同期できます。
ConversionPanelクラスの作成
ConversionPanelクラスは、テキスト・フィールド、スライダ、コンボ・ボックスなどのコンポーネントを保持するために使用します。 Converterアプリケーションのグラフィカル・シーンを作成するときに、ConversionPanelクラスの2つのインスタンスをこのグラフィカル・シーンに追加します。 例6-3に示すように、TitledPaneクラスのimport文を追加し、ConversionPanelクラスを拡張します。 
UIコントロールのインスタンス変数の作成
例6-4に示すように、TextField、SliderおよびComboBoxコントロールのimport文を追加し、これらのコンポーネントのインスタンス変数を定義します。
DoublePropertyおよびNumberFormatオブジェクトの作成
例6-5に示すように、DoublePropertyおよびNumberFormatクラスのimport文を追加し、metersという名前のDoublePropertyオブジェクトを作成します。 metersオブジェクトは、2つのConversionPanelオブジェクト間の同期を確保するために使用します。 
コンポーネントのレイアウト
テキスト・フィールドとスライダのレイアウトを行うには、VBoxクラスを使用します。 これらのコンポーネントとコンボ・ボックスの両方のレイアウトを行うには、HBoxクラスを使用します。 例6-6に示すように、ObservableListクラスのimport文を追加し、ConversionPanelクラスのコンストラクタを実装します。 
例6-6
import javafx.collections.ObservableList;
public ConversionPanel(String title, ObservableList<Unit> units, 
DoubleProperty meters) {
    setText(title);
    setCollapsible(false);
    numberFormat = NumberFormat.getNumberInstance();
    numberFormat.setMaximumFractionDigits(2);
    textField = new TextField();
    slider = new Slider(0, MAX, 0);
    comboBox = new ComboBox(units);
    comboBox.setConverter(new StringConverter<Unit>() {
        
        @Override
        public String toString(Unit t) {
            return t.description;
        }
        
        @Override
        public Unit fromString(String string) {
            throw new UnsupportedOperationException("Not supported yet.");
        }
    })
    VBox vbox = new VBox(textField, slider);
    HBox hbox = new HBox(vbox, comboBox);
    setContent(hbox);
    this.meters = meters;
    
    comboBox.getSelectionModel().select(0);
}
コードの最終行は、ComboBoxオブジェクト内の値を選択しています。
InvalidationListenerオブジェクトの作成
テキスト・フィールドとコンボ・ボックスのプロパティにおける変更をリスニングするには、例6-7に示すように、fromMetersとtoMetersというInvalidationListenerオブジェクトを作成します。
例6-7
import javafx.beans.InvalidationListener;
private InvalidationListener fromMeters = t -> {
    if (!textField.isFocused()) {
        textField.setText(numberFormat.format(meters.get() / getMultiplier()));
    }
};
private InvalidationListener toMeters = t -> {
    if (!textField.isFocused()) {
        return;
    try {
        meters.set(numberFormat.parse(textField.getText()).doubleValue() *
getMultiplier());
    } catch (ParseException | Error | RuntimeException ignored) {
    }
};
コントロールへの変更リスナーの追加と同期の確保
テキスト・フィールドとコンボ・ボックス間が同期されるようにするには、例6-8に示すように、変更リスナーを追加します。
例6-8
meters.addListener(fromMeters); comboBox.valueProperty().addListener(fromMeters); textField.textProperty().addListener(toMeters); fromMeters.invalidated(null);
例6-9に示すように、スライダの値とmetersオブジェクトの値間の双方向バインディングを作成します。
新しい値をテキスト・フィールドに入力すると、toMetersリスナーのinvalidatedメソッドがコールされ、metersオブジェクトの値が更新されます。
Converterクラスの作成
NetBeans IDEによって自動的に生成されたConverter.javaファイルを開き、mainメソッド以外のすべてのコードを削除します。 その後、[Ctrl] (または[Cmd])キーと[Shift]キーを押しながら[I]キーを押して、import文を修正します。 
インスタンス変数の定義
例6-10に示すように、ObservableList、DoublePropertyおよびSimpleDoublePropertyクラスのimport文を追加し、適切な型のmetricDistances、usaDistancesおよびmeters変数を作成します。
Converterクラスのコンストラクタの作成
例6-11に示すように、Converterクラスのコンストラクタで、メートル法の距離と米国単位の距離に対応するUnitオブジェクトを作成します。 FXCollectionsクラスのimport文を追加します。 後から、これらの単位を使用して2つのConversionPanelオブジェクトをインスタンス化します。 
例6-11
import javafx.collections.FXCollections;
public Converter() {
metricDistances = FXCollections.observableArrayList(
        new Unit("Centimeters", 0.01),
        new Unit("Meters", 1.0),
        new Unit("Kilometers", 1000.0));
usaDistances = FXCollections.observableArrayList(
        new Unit("Inches", 0.0254),
        new Unit("Feet", 0.305),
        new Unit("Yards", 0.914),
        new Unit("Miles", 1613.0));
}
グラフィカル・シーンの作成
Converterアプリケーションのグラフィカル・シーンを作成するには、startメソッドをオーバーライドします。 2つのConversionPanelオブジェクトをグラフィカル・シーンに追加し、これらを垂直方向にレイアウトします。 2つのConversionPanelオブジェクトが、同じmetersオブジェクトを使用してインスタンス化されていることに注意してください。 グラフィカル・シーンのルート・コンテナには、VBoxクラスを使用します。 例6-12に示すように、2つのConversionPanelオブジェクトをインスタンス化します。 
例6-12
@Override
public void start(Stage stage) {
    VBox vbox = new VBox(
            new ConversionPanel(
                    "Metric System", metricDistances, meters),
            new ConversionPanel(
                    "U.S. System", usaDistances, meters));
    Scene scene = new Scene(vbox);
    
    stage.setTitle("Converter");
    stage.setScene(scene);
    stage.show();
}
このドキュメントの一番下にあるリンクを使用して、JavaFXでのConverterアプリケーションのソース・コードを参照したり、このアプリケーションのNetBeansプロジェクトをダウンロードできます。
図6-2に、JavaFXでのConverterアプリケーションを示します。
SwingライブラリとJavaFXを使用して同じ機能を実装した2つのアプリケーションを比較してみてください。
JavaFXアプリケーションに含まれるファイルが3つであるのに対して、Swingアプリケーションのファイルは5つであり、さらにJavaFXコードの方がすっきりしています。 アプリケーションのルック・アンド・フィールも異なります。



