Java Platform, Standard Editionデプロイメント・ガイド
目次      

13 JavaFXアプリケーションのプリローダー

JDK 8u451では、JavaFXはJava SE 8の一部として含まれなくなりました。 詳細は、https://www.oracle.com/javase/javafxを参照してください。

この項では、JavaFXアプリケーションで使用されるプリローダーについて、およびカスタム・プリローダーの実装方法について説明します。 カスタム・プリローダーはJavaアプリケーションには使用できませんが、デフォルトのプリローダーをカスタマイズすることはできます。

起動の2番目のフェーズ中に、プリローダー・アプリケーションは、JavaFXランタイムのデフォルト・アプリケーションまたは指定したカスタム・アプリケーションのいずれかを実行します。 プリローダーが起動フローにどのように適合するかの詳細は、4.2項「アプリケーションの起動プロセス、操作およびカスタマイズ」を参照してください。

カスタム・プリローダー・アプリケーションはオプションで、アプリケーションのロードおよび起動操作を調整するために使用できます。 たとえば、プリローダーを使用すると、進捗インジケータやログイン・プロンプトなど、一部のコンテンツをユーザーに先に表示することにより、認識されたアプリケーション起動時間を削減するのに役立ちます。

プリローダー・アプリケーションは、カスタム・メッセージをユーザーに示すためにも使用できます。 たとえば、現在何が起こっているか、およびユーザーは次に何をするよう依頼されるか(アプリケーションに権限を付与するなど)を説明したり、カスタム・エラー・メッセージを示すためにプリローダーを作成したりできます。

すべてのアプリケーションがカスタム・プリローダーを必要とするわけではありません。 たとえば、アプリケーションが小規模で権限などの特別な要件がない場合、おそらくアプリケーションは迅速に起動し、デフォルトのプリローダーで十分です。 大規模なアプリケーションでも、JavaFXランタイムに付属のデフォルトのプリローダーは、ネットワークではなくクライアント・マシンからロードされるため、適切であることがあります。

この項には次のトピックが含まれます:

デフォルトのプリローダーのカスタマイズ方法の詳細は、4.2項「アプリケーションの起動プロセス、操作およびカスタマイズ」を参照してください。

Javaアプリケーションのプリローダーのカスタマイズの詳細は、第14章「ロード動作のカスタマイズ」を参照してください。

13.1 カスタム・プリローダーの実装

カスタム・プリローダーは、javafx.application.Preloaderクラスを拡張する特殊なJavaFXアプリケーションです。 Preloaderクラスはjavafx.application.Applicationの拡張であるため、カスタム・プリローダーは同じライフ・サイクルを持ち、JavaFXランタイムのすべての機能を使用できます。

図13-1に、アプリケーションの起動に関連するプリローダーの起動順序を示します。 プリローダー・アプリケーションは、メイン・アプリケーションの前に起動し、アプリケーション・リソースのロード、アプリケーションの初期化と起動の進捗状況、およびエラーの通知を取得します。

図13-1 アプリケーションの起動に関連するプリローダーの起動

図13-1の説明が続きます
「図13-1 アプリケーションの起動に関連するプリローダーの起動」の説明

例13-1は、ロードの進捗状況を示すProgressBarコントロールを使用する単純なプリローダーを示しています。

例13-1 ProgressBarコントロールを使用した単純なプリローダー

public class FirstPreloader extends Preloader {
    ProgressBar bar;
    Stage stage;
 
    private Scene createPreloaderScene() {
        bar = new ProgressBar();
        BorderPane p = new BorderPane();
        p.setCenter(bar);
        return new Scene(p, 300, 150);        
    }
    
    public void start(Stage stage) throws Exception {
        this.stage = stage;
        stage.setScene(createPreloaderScene());        
        stage.show();
    }
    
    @Override
    public void handleProgressNotification(ProgressNotification pn) {
        bar.setProgress(pn.getProgress());
    }
 
    @Override
    public void handleStateChangeNotification(StateChangeNotification evt) {
        if (evt.getType() == StateChangeNotification.Type.BEFORE_START) {
            stage.hide();
        }
    }    
}

通常のJavaFXアプリケーションとして、FirstPreloaderクラスは、start()メソッドを使用して、ロードの進捗状況を表示するシーンを作成します。 進捗における更新はhandleProgressNotification()メソッドを使用してプリローダーに配信され、FirstPreloader実装はその更新を使用してUIを更新します。

プリローダーとメイン・アプリケーションは異なるStageオブジェクトを持ち、プリローダーは、必要なときに自身のステージの表示および非表示を処理する必要があります。 例13-1では、メイン・アプリケーションのstart()メソッドを呼び出そうとするという通知を受信した後で、プリローダー・ステージが非表示になります。

FirstPreloaderクラスの実装は、主な概念を示し、多くのシナリオで動作しますが、すべての使用事例で最適なユーザー操作性を提供しているわけではありません。 FirstPreloaderクラスの改善方法の例は、13.3項「プリローダーのコード例」を参照してください。

13.2 プリローダーを使用したアプリケーションのパッケージ化

プリローダーを使用したアプリケーションのパッケージ化には特別な要件があります。

最初に、ほとんどの場合、残りのアプリケーションとは別の1つ以上のJARファイルに、プリローダーのコードをパッケージ化する必要があります。 これにより、アプリケーションがWebでデプロイされたときに高速にロードできます。 アプリケーションとプリローダー・コードの両方に単一のJARファイルを使用することは、特殊な事例(たとえばアプリケーションがスタンドアロン・モードでのみ実行される場合など)では適切であることもあります。 NetBeans IDEでは、2つのプロジェクト(メイン・アプリケーション用のプロジェクトと、プリローダー用の特別なJavaFXプリローダー・プロジェクト)を作成することによって、JARファイルは別々にパッケージ化されます。 13.2.1項「NetBeans IDEでのプリローダー・アプリケーションのパッケージ化」を参照してください。

2番目に、アプリケーションのデプロイメント記述子には、どのクラスがプリローダーに属するか、およびプリローダー・コードがどこにあるかの情報を含める必要があります。 これを指定する方法は、パッケージ化に使用するツールに応じて異なります。 ツールの詳細は、5.3.1項「Javaパッケージ化ツール」を参照してください。

例13-2に示すように、すべてのパッケージ化ツールによって、プリローダーを含むデプロイメント記述子が作成されます。 この例では、メイン・アプリケーションはAnimatedCirclesと呼ばれ、プリローダー・アプリケーションはFirstPreloaderと呼ばれます。

例13-2 プリローダーを使用したアプリケーションのデプロイメント記述子のサンプル

<?xml version="1.0" encoding="utf-8"?>
<jnlp spec="1.0" xmlns:jfx="http://javafx.com" href="AnimatedCircles.jnlp">
  <information>
    <title>AnimatedCircles</title>
    <vendor>Oracle</vendor>
    <description>Animated Circles</description>
    <offline-allowed/>
  </information>
  <resources>
    <j2se version="1.7_06+" href="http://java.sun.com/products/autodl/j2se"/>
    <jar href="lib/FirstPreloader.jar" size="2801" download="progress" />
    <jar href="AnimatedCircles.jar" size="13729" download="always" />
  </resources>
  <applet-desc  width="800" height="600"
      main-class="com.javafx.main.NoJavaFXFallback"  name="AnimatedCircles" />
  <jfx:javafx-desc  width="800" height="600"
    main-class="animatedcircles.AnimatedCircles"  name="AnimatedCircles"  
    preloader-class="firstpreloader.FirstPreloader"/>
  <update check="background"/>
</jnlp>

例13-3に示すように、マニフェストにはプリローダーへのクラス・パスも含まれている必要があります。

例13-3 プリローダーを使用したアプリケーションのマニフェストのサンプル

Manifest-Version: 1.0
JavaFX-Version: 2.2
implementation-vendor: nhildebr
implementation-title: AnimatedCircles
implementation-version: 1.0
JavaFX-Preloader-Class: firstpreloader.FirstPreloader
JavaFX-Application-Class: animatedcircles.AnimatedCircles
JavaFX-Class-Path: lib/FirstPreloader.jar
Created-By: JavaFX Packager
Main-Class: com/javafx/main/Main
Permissions: sandbox

13.2.1 NetBeans IDEでのプリローダー・アプリケーションのパッケージ化

NetBeans IDEを使用している場合、メイン・アプリケーションでは、メイン・プリローダー・クラスを含む別のNetBeansプロジェクト、またはプリローダーがパッケージ化されるJARファイルのいずれかを指定できます。

次の手順では、プロジェクト構成に応じた、NetBeans IDEのプリローダーをパッケージ化する2つの方法を示します。 新しいNetBeansプロジェクトを作成してプリローダー・オプションを選択することも、既存のNetBeansプロジェクトにプリローダーを追加することもできます。 両方の手順では、例13-1のプリローダー・クラスを使用します。

NetBeans IDEでプリローダーを使用した新規アプリケーションを作成するには:

  1. 「ファイル」メニューで、「新規プロジェクト」を選択します。

  2. 「JavaFX」カテゴリを選択し、プロジェクト・タイプとして「JavaFXアプリケーション」を選択します。 「次」をクリックします。

  3. プロジェクト名としてFirstAppを入力し、カスタム・プリローダーの作成を選択します。 「終了」をクリックします。

    Netbeans IDEは2つの新規プロジェクトを作成します。カスタム・プリローダーの基本的な実装を使用したFirstApp-Preloaderプロジェクトと、カスタム・プリローダーを使用したサンプルJavaFXアプリケーションを使用したFirstAppプロジェクトです。

  4. FirstApp-Preloaderプロジェクトのソース・パッケージでSimplePreloaderクラスを開きます。

  5. SimplePreloaderクラスの実装を、FirstPreloaderクラスの実装またはこのページの他のサンプルで置き換えます。

    必要な場合は、「ソース」メニューに移動し、「インポートを修正」を選択して、必ずインポートを修正します。

  6. FirstAppプロジェクトを選択し、「消去してビルド」を実行して、サンプル・アプリケーションとプリローダーの両方をビルドします。

    アーティファクトは、FirstAppプロジェクトのdistフォルダに配置されます。

  7. Netbeansでアーティファクトを実行して、アーティファクトをテストします。


    ヒント:

    スタンドアロンとして、または「プロジェクト・プロパティ」の「実行」カテゴリを選択してブラウザでアプリケーションを起動することも、ビルド・アーティファクトを直接開くこともできます。

    スタンドアロンの起動では、ロードするものがないため、プリローダーがロードの進捗状況のみを表示する場合、プリローダーが表示されない可能性があります。 ローカル・ハード・ドライブからのWeb起動のテストの場合でも、プリローダーは非常に短い間のみ表示される可能性があります。

既存のNetBeansプロジェクトにプリローダーを追加するには:

  1. プリローダー・クラスのタイプJavaFXプリローダーの別のNetBeansプロジェクトを作成します。 例では、プロジェクト名がFirstPreloaderで、ここにはfirstpreloaderパッケージおよびFirstPreloaderクラスのコードが含まれています。

  2. メイン・アプリケーションの「プロジェクト・プロパティ」で、「実行」カテゴリをクリックします。

  3. チェック・ボックスプリローダーの使用を選択し、「参照」をクリックしてから、プリローダーのNetBeansプロジェクトを選択します。 図13-2に示すように、デフォルトでプリローダー・クラス・フィールドに移入されます。

    図13-2 NetBeansの「プロジェクト・プロパティ」の「実行」カテゴリのプリローダー・オプション

    NetBeansのプリローダー・オプションのスクリーンショット

    ノート:

    プリローダーのNetBeansプロジェクトを選択するかわりに、「参照」をクリックしたときにプリローダーJARファイルを選択するオプションがあります。

  4. 「OK」をクリックして、「プロジェクト・プロパティ」ダイアログ・ボックスを閉じます。

  5. メイン・アプリケーションを右クリックし、「消去してビルド」を選択します。

    デプロイメント用のメイン・アプリケーション・ファイルはdistディレクトリに作成され、プリローダーJARファイルはlibサブディレクトリに配置されます。 必要なすべてのJNLPおよびマニフェスト・エントリは、IDEによって処理されます。

13.2.2 Antタスクでのプリローダー・アプリケーションのパッケージ化

Antユーザーは、<fx:jar>タスクと<fx:deploy>タスクの両方で、プリローダー・クラスおよびJARファイルに関する情報を指定する必要があります。 <fx:jar>タスクに適切なパラメータを設定すると、スタンドアロン・アプリケーションにプリローダーが確実に登録されます。 <fx:deploy>タスクに適切なパラメータを設定すると、Webデプロイメント用の構成が作成されます。

Antスクリプトの他の部分にいくつかの設定が必要です。 例13-4に示すように、<fx:deploy>要素の一部として、プリローダー・メイン・クラスが指定されます。

例13-4 <fx:application>でのプリローダー・クラスの指定

<fx:application id="app-desc" 
        mainClass="sample.AppSample"
        preloaderClass="preloaders.SamplePreloader"/>

例13-5に示すように、<fx:application>の下でネストされたアプリケーション・リソースの記述内に、プリローダー・リソースがrequiredFor="preloader"属性でマークされます。

例13-5 <fx:fileset>のrequiredFor属性の使用

<fx:application ... >
    <fx:resources>
        <fx:fileset id="preloader-files" 
                requiredFor="preloader"
                dir="dist" 
                includes="preloader.jar"/>
        <fx:fileset dir="dist" includes="myapp.jar"/>
    </fx:resources> 
</fx:application>

id属性への参照を作成するrefid属性を利用して、コードの重複を減らすために要素を再利用できます。 <fx:jar>および<fx:deploy>タスクのプリローダー設定を例13-6に示します。

例13-6 <fx:jar>および<fx:deploy>タスクのプリローダー設定

<fx:jar destfile="dist/application.jar">
    <fx:application refid="app-desc"/>
        <fx:resources>
            <fx:fileset refid="preloader-files"/>
        </fx:resources>
        <fileset dir="build/classes/" include="**"/>
</fx:jar>
        
<fx:deploy width="600" height="400"
        outdir="app-dist" outfile="SampleApp">
    <fx:info title="Sample application"/>
    <fx:application refid="app-desc"/>
    <fx:resources>
        <fx:fileset requiredFor="startup" dir="dist" include="application.jar"/>
        <fx:fileset refid="preloader-files"/>
    </fx:resources>
</fx:deploy>

完全なAntタスクの別のプリローダー構成は、例10-3を参照してください。 その例では、プリローダーとメイン・アプリケーションJARファイルの両方が、<fx:signjar>タスクで署名されます。 プリローダーJARが署名されておらず、メイン・アプリケーションJARファイルが署名されている場合、マルチパート・デプロイメント記述子が必要です。 パッケージ化は、署名および無署名のコードを混在させたものを使用した他のJavaFXアプリケーションと似ています。 詳細は、5.7.2項「アプリケーション・リソース」を参照してください。

次のプリローダー固有の詳細に注意してください。

  • 例13-4に示すように、プリローダー・クラスの名前は常にメイン・アプリケーション記述子で指定されます。

  • ほとんどの場合、プリローダーJARファイルをメイン・アプリケーション記述子に保持することをお薦めします。そのため、プリローダーJARファイルはすぐにロードを開始します。

最後のポイントの理由は次のとおりです。 このアプリケーションをパッケージするために2つの<fx:deploy>タスクがあり、2つの異なるJNLPファイル(1つはメイン・アプリケーション用でもう1つは拡張用)を生成します。 アプリケーションはメインJNLPへのリンクから起動するため、メインJNLPファイルから参照されるものは何でもすぐにロードを開始することができ、迅速に準備が整います。

13.3 プリローダーのコード例

次のコード例では、プリローダーの様々な使用を示します。

13.3.1 必要な場合のみのプリローダーの表示

アプリケーションがスタンドアロンで実行するか、Webキャッシュからロードされる場合、ロードするものがないため、プリローダーは進捗通知を取得せず、アプリケーションはすばやく起動すると考えられます。

例13-1で実装されるようなFirstPreloaderの例を使用すると、0パーセントの進捗状況のプリローダー・ステージのみが短時間ユーザーに表示されます。 アプリケーションがブラウザに埋め込まれないかぎり、短時間表示されるウィンドウもポップアップします。 この場合、より適切なユーザー操作は、最初の進捗通知まで何も表示されないことです。

アプリケーションがWebページに埋め込まれる場合、アプリケーションが表示される場所にグレーのボックスが表示されるのを回避するために、何かを表示する必要があります。 可能なアプローチの1つは、プリローダーに表示するものが発生するまで、またはアプリケーションの準備が整うまで(プリローダーが何もイベントを取得しない場合)、HTMLスプラッシュ画面を表示することです。 もう1つのオプションは、プリローダーの簡略化されたバージョンを表示し、最初の進捗通知を受信した後で進捗インジケータを追加することです。

例13-7は、FirstPreloader実装の該当部分を改善する方法を示しています。

  • 最初の進捗通知まで進捗インジケータを表示しません。

  • プリローダー・ステージが埋め込まれていない場合、最初の進捗通知まで進捗バーを表示しません。

例13-7 プリローダーが表示される際の調整の例

boolean isEmbedded = false;
public void start(Stage stage) throws Exception {
    //embedded stage has preset size
    isEmbedded = (stage.getWidth() > 0);

    this.stage = stage;
    stage.setScene(createPreloaderScene());        
}
    
@Override
public void handleProgressNotification(ProgressNotification pn) {
    if (pn.getProgress() != 1 && !stage.isShowing()) {
        stage.show();
    }
    bar.setProgress(pn.getProgress());
}

スプラッシュ画面の非表示を延期する方法の例は、13.3.3項「プリローダーを使用したJavaScriptの使用」を参照してください。

13.3.2 視覚的遷移の強化

アプリケーションの起動前にプリローダーによって受信される最新の状態変更通知は、StateChangeNotification.Type.BEFORE_STARTです。 通知の処理後、アプリケーションのstart()メソッドが呼び出されます。 ただし、start()メソッドが呼び出された後でアプリケーションがステージを表示する準備ができるまで時間がかかることがあります。 プリローダー・ステージがすでに非表示になっている場合、アプリケーションが画面に何も表示しない期間が発生することがあります。 アプリケーションがWebページに埋め込まれる場合、これがWebページの効果の欠点となることがあります。

この理由およびその他の理由で、プリローダーをすぐに非表示にすることが、プリローダーからアプリケーションへの最適な視覚的遷移にならない可能性があります。 プリローダーとアプリケーション間の視覚的遷移を改善する1つのアプローチを、例13-8に示します。 このFirstPreloaderの例がWebページに埋め込まれたアプリケーションに使用される場合、すぐに非表示にならずに1秒間でフェードアウトします。

例13-8 プリローダーのフェードアウト

@Override
public void handleStateChangeNotification(StateChangeNotification evt) {
    if (evt.getType() == StateChangeNotification.Type.BEFORE_START) {
        if (isEmbedded && stage.isShowing()) {
            //fade out, hide stage at the end of animation
            FadeTransition ft = new FadeTransition(
                Duration.millis(1000), stage.getScene().getRoot());
                ft.setFromValue(1.0);
                ft.setToValue(0.0);
                final Stage s = stage;
                EventHandler<ActionEvent> eh = new EventHandler<ActionEvent>() {
                    public void handle(ActionEvent t) {
                        s.hide();
                    }
                };
                ft.setOnFinished(eh);
                ft.play();
        } else {
            stage.hide();
        }
    }
}

プリローダーとアプリケーションが連携すると、遷移がさらに円滑になります。 アプリケーションにフェードインするプリローダーの例は、13.3.6項「プリローダーとアプリケーションの連携: ステージの共有」を参照してください。

アプリケーションの初期化に時間がかかる場合、アプリケーションの準備ができたときにプリローダーからアプリケーションへの遷移を開始するカスタム通知を使用することが有用な場合があります。 詳細は、13.3.4項「アプリケーションの初期化の進捗状況を表示するためのプリローダーの使用」を参照してください。

13.3.3 プリローダーを使用したJavaScriptの使用

JavaFXアプリケーションのプリローダーにはパラメータやホスト・サービスなどのアプリケーションの機能にアクセスできるため、プリローダーはJavaScriptを使用してアプリケーションが埋め込まれたWebページと通信できます。

例13-9では、JavaScriptのアクセスは、HTMLスプラッシュ画面にロードの進捗状況を表示したり、アプリケーションの準備ができた場合のみスプラッシュ画面を非表示にしたりするプリローダーを作成するために使用されます。 コードでは、Webページで指定する必要がある次の2つのJavaScriptメソッドを使用します。

  • HTMLスプラッシュ画面を非表示にするhide()

  • 進捗状況を更新するprogress(p)

デフォルトで非表示にならないカスタムHTMLスプラッシュ画面があることが前提となっています。

例13-9 プリローダーからのJavaScriptの使用

import javafx.application.Preloader;
import javafx.stage.Stage;
import netscape.javascript.JSObject;
 
public class JSPreloader extends Preloader {
    public void start(Stage stage) throws Exception {}
    
    public void handleStateChangeNotification(StateChangeNotification evt) {
        if (evt.getType() == StateChangeNotification.Type.BEFORE_START) {
            JSObject js = getHostServices().getWebContext();
            if (js != null) {
                try {
                    js.eval("hide();");
                } catch (Throwable e) {
                    System.err.println("Ouch "+e);
                    e.printStackTrace();
                }
            }
        }
    }
 
    public void handleProgressNotification(ProgressNotification pn) {
        JSObject js = getHostServices().getWebContext();
        if (js != null) {
            try {
                js.eval("progress("+ ((int) (100*pn.getProgress()))+");");
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }
    }   
}

例13-10は、例13-9のプリローダーを使用するサンプルWebページ・テンプレートを示しています。 パッケージ化中にこのテンプレート・ページが処理されると、#DT.SCRIPT.URL#および#DT.EMBED.CODE.ONLOAD#は、JavaFXアプリケーションをWebページに埋め込むためのコードに置き換えられます。 テンプレートの詳細は、JavaFX Antリファレンスの「<fx:template>」を参照してください。

例13-10 プリローダー用のJavaScriptを含むWebページ・テンプレート

<html>
    <head>
        <style>
            div.label {
                position:absolute;
                bottom:100px;
                left:200px;
                font-family: 'tahoma';
                font-size:150px;
                color:silver;
            }
        </style>
 
        <SCRIPT src="#DT.SCRIPT.URL#"></SCRIPT>
        <script>
            //Postpone the moment the splash screen is hidden 
            // so it can show loading progress
            // save reference to hide function and replace it with no op for now
            var realHide = dtjava.hideSplash;
            dtjava.hideSplash = function(id) {}
 
            //hide splash
            function hide() {
                realHide('sampleApp');
            }
 
            //update progress data
            function progress(p) {
                var e = document.getElementById("splash");
                e.removeChild(e.firstChild);
                e.appendChild(document.createTextNode(""+p));
            }
   
            //create custom splash to be used
            function getSplash(app) {
                var l = document.createElement('div');
                l.className="label";
                l.id="splash";
                l.appendChild(document.createTextNode("..."));
                return l;
            }
        </script>
    <!-- #DT.EMBED.CODE.ONLOAD# -->
 
    </head>
    <body>
        <h2>Test page for <b>JS preloader sample</b></h2>
        <!-- Application will be inserted here -->
        <div id='javafx-app-placeholder'></div>
    </body>
</html>

13.3.4 アプリケーションの初期化の進捗状況を表示するためのプリローダーの使用

JavaFXアプリケーションは、カスタム通知を使用して、イベントに関する情報をプリローダーに渡すことができます。 たとえば、アプリケーションの初期化の進捗状況を表示するためにプリローダーを使用できます。

技術的には、Preloader.PreloaderNotificationインタフェースを実装するクラスは、カスタム通知として機能することができ、アプリケーションは、Application.notifyPreloader()メソッドを使用してプリローダーに送信できます。 プリローダー側では、アプリケーション通知はhandleApplicationNotification()メソッドに配信されます。

例13-11は、FirstPreloaderの例を少し変更したものです。 これは、アプリケーション起動の通知を受信した後にプリローダーが非表示になりません。 これは、アプリケーション固有の通知を待機し、進捗通知を表示し、アプリケーションが状態変更通知を送信した後でスプラッシュ画面を非表示にします。

例13-11 アプリケーションの初期化およびロードの進捗状況を表示するためのプリローダー

public class LongAppInitPreloader extends Preloader {
    ProgressBar bar;
    Stage stage;
    boolean noLoadingProgress = true;
 
    private Scene createPreloaderScene() {
        bar = new ProgressBar(0);
        BorderPane p = new BorderPane();
        p.setCenter(bar);
        return new Scene(p, 300, 150);
    }
 
    public void start(Stage stage) throws Exception {
        this.stage = stage;
        stage.setScene(createPreloaderScene());
        stage.show();
    }
 
    @Override
    public void handleProgressNotification(ProgressNotification pn) {
        //application loading progress is rescaled to be first 50%
        //Even if there is nothing to load 0% and 100% events can be
        // delivered
        if (pn.getProgress() != 1.0 || !noLoadingProgress) {
          bar.setProgress(pn.getProgress()/2);
          if (pn.getProgress() > 0) {
              noLoadingProgress = false;
          }
        }
    }
 
    @Override
    public void handleStateChangeNotification(StateChangeNotification evt) {
        //ignore, hide after application signals it is ready
    }
 
    @Override
    public void handleApplicationNotification(PreloaderNotification pn) {
        if (pn instanceof ProgressNotification) {
           //expect application to send us progress notifications 
           //with progress ranging from 0 to 1.0
           double v = ((ProgressNotification) pn).getProgress();
           if (!noLoadingProgress) {
               //if we were receiving loading progress notifications 
               //then progress is already at 50%. 
               //Rescale application progress to start from 50%               
               v = 0.5 + v/2;
           }
           bar.setProgress(v);            
        } else if (pn instanceof StateChangeNotification) {
            //hide after get any state update from application
            stage.hide();
        }
    }  
 }

例13-11では、アプリケーションの初期化とロードの両方の進捗状況を表示するために、同じ進捗バーが使用されます。 簡単にするため、各フェーズに50パーセントが予約されます。 ただし、たとえばアプリケーションがスタンドアロンとして起動する場合など、ロードのフェーズがスキップされた場合、進捗バー全体が、アプリケーションの初期化の進捗状況の表示に割り当てられます。

例13-12は、アプリケーション側のコードを示しています。 longStart()メソッドは、バックグラウンド・スレッドで発生する長期間の初期化プロセスをシミュレートするために使用されます。 初期化が完了すると、readyプロパティが更新され、これによりアプリケーション・ステージが表示されるようになります。 初期化中に、中間進捗通知が生成されます。 初期化の終わりに、StateChangeNotificationが送信され、プリローダーが自身で非表示になります。

例13-12 進捗状況の表示を有効にするアプリケーション・コード

public class LongInitApp extends Application {
    Stage stage;
    BooleanProperty ready = new SimpleBooleanProperty(false);
    
    private void longStart() {
        //simulate long init in background
        Task task = new Task<Void>() {
            @Override
            protected Void call() throws Exception {
                int max = 10;
                for (int i = 1; i <= max; i++) {
                    Thread.sleep(200);
                    // Send progress to preloader
                    notifyPreloader(new ProgressNotification(((double) i)/max));
                }
                // After init is ready, the app is ready to be shown
                // Do this before hiding the preloader stage to prevent the 
                // app from exiting prematurely
                ready.setValue(Boolean.TRUE);
 
                notifyPreloader(new StateChangeNotification(
                    StateChangeNotification.Type.BEFORE_START));
                
                return null;
            }
        };
        new Thread(task).start();
    }
 
    @Override
    public void start(final Stage stage) throws Exception {
        // Initiate simulated long startup sequence
        longStart();
        
        stage.setScene(new Scene(new Label("Application started"), 
            400, 400));
        
        // After the app is ready, show the stage
        ready.addListener(new ChangeListener<Boolean>(){
            public void changed(
                ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) {
                    if (Boolean.TRUE.equals(t1)) {
                        Platform.runLater(new Runnable() {
                            public void run() {
                                stage.show();
                            }
                        });
                    }
                }
        });;                
    }
}

この例では、標準的なイベントが再利用されますが、一般に、アプリケーションは任意のデータをプリローダーに送信できます。 たとえば、アプリケーションのロードでは、イメージ・コレクションの通知にサンプル・プレビュー・イメージなどを含めることができます。

13.3.5 プリローダーとアプリケーションの連携: ログイン・プリローダー

StateChangeNotificationの一部として、プリローダーはアプリケーションへの参照を受信し、これによりプリローダーはアプリケーションと密接に連携できます。

この項の例では、アプリケーションのロード中にユーザー資格証明を要求し、それをアプリケーションに渡す、ログイン・プリローダーにおけるこの連携の使用方法を示しています。

連携するために、このプリローダーとアプリケーションは、資格証明をアプリケーションに渡すためにプリローダーが使用するCredentialsConsumerインタフェースを共有します。 共有インタフェースの実装に加えて、このサンプルで他に特殊な点は、アプリケーションは、ユーザー資格証明とStageオブジェクトへの参照の両方が含まれるまで、自身を表示しないことのみです。

例13-13は、ログイン・プリローダーのアプリケーション・コードを示しています。

例13-13 ログイン・プリローダーの有効化

public class AppToLogInto extends Application implements CredentialsConsumer {
    String user = null;
    Label l = new Label("");
    Stage stage = null;
    
    private void mayBeShow() {
        // Show the application if it has credentials and 
        // the application stage is ready
        if (user != null && stage != null) {
            Platform.runLater(new Runnable() {
                public void run() {
                    stage.show();
                }                
            });
        }
    }
    
    @Override
    public void start(Stage stage) throws Exception {
        this.stage = stage;
        stage.setScene(new Scene(l, 400, 400));
        mayBeShow();
    }
 
    public void setCredential(String user, String password) {
        this.user = user;
        l.setText("Hello "+user+"!");
        mayBeShow();
    }
}

ユーザーは資格証明を指定する必要があるため、プリローダー・ステージは無条件に表示されます。 ただし、アプリケーションに渡す資格証明がないかぎり、アプリケーションが起動の準備ができてもプリローダーは非表示になりません。

資格証明を渡すには、アプリケーションが実装していることを前提として、StateChangeNotificationからCredentialsConsumerにアプリケーションへの参照をキャストできます。

例13-14では、前の例のログイン・ペインUIは単純ですが、実行モードに適応させる方法を示しています。 表示する進捗がない場合、UIに進捗バーを追加しても意味がありません。 また、アプリケーションがロードを終了しても、ユーザーの入力を待機している場合、不必要な進捗を非表示にすることによってUIを簡略化できます。

例13-14 ログイン・プリローダー・コード

public class LoginPreloader extends Preloader {
    public static interface CredentialsConsumer {
        public void setCredential(String user, String password);
    }
    
    Stage stage = null;
    ProgressBar bar = null;
    CredentialsConsumer consumer = null;
    String username = null;
    String password = null;
    
    private Scene createLoginScene() {
        VBox vbox = new VBox();
 
        final TextField userNameBox = new TextField();
        userNameBox.setPromptText("name");
        vbox.getChildren().add(userNameBox);
        
        final PasswordField passwordBox = new PasswordField();
        passwordBox.setPromptText("password");
        vbox.getChildren().add(passwordBox);
        
        final Button button = new Button("Log in");
        button.setOnAction(new EventHandler<ActionEvent>(){
            public void handle(ActionEvent t) {
                // Save credentials
                username = userNameBox.getText();
                password = passwordBox.getText();
                
                // Do not allow any further edits
                userNameBox.setEditable(false);
                passwordBox.setEditable(false);
                button.setDisable(true);
                
                // Hide if app is ready
                mayBeHide();
            }
        });
        vbox.getChildren().add(button);
        
        bar = new ProgressBar(0);
        vbox.getChildren().add(bar);
        bar.setVisible(false);
        
        Scene sc = new Scene(vbox, 200, 200);
        return sc;
    }
    
    @Override
    public void start(Stage stage) throws Exception {
        this.stage = stage;
        stage.setScene(createLoginScene());
        stage.show();
    }
    
    @Override
    public void handleProgressNotification(ProgressNotification pn) {
        bar.setProgress(pn.getProgress());
        if (pn.getProgress() > 0 && pn.getProgress() < 1.0) {
            bar.setVisible(true);
        }
    }
 
    private void mayBeHide() {
        if (stage.isShowing() && username != null && consumer != null) {
            consumer.setCredential(username, password);
            Platform.runLater(new Runnable() {
                public void run() {
                   stage.hide();
                }
            });
        }
    }
    
    @Override
    public void handleStateChangeNotification(StateChangeNotification evt) {
        if (evt.getType() == StateChangeNotification.Type.BEFORE_START) {
            //application is loaded => hide progress bar
            bar.setVisible(false);
            
            consumer = (CredentialsConsumer) evt.getApplication();
            //hide preloader if credentials are entered
            mayBeHide();
        }
    }    
}

プリローダーとアプリケーション間で密接に連携すると、プリローダーとアプリケーションの両方が同じ信頼ドメインにある(つまり両方が権限を付与されているか両方がサンドボックスである)場合を除いては、コードの混在による制限を受けます。 詳細は、第27章「特権付きコードとサンドボックス・コードの混合」を参照してください。

13.3.6 プリローダーとアプリケーションの連携: ステージの共有

この項では、プリローダーからアプリケーションへの遷移を改善するためにプリローダーとアプリケーション間の連携を使用する方法を示します。

例13-15は、プリローダーとアプリケーションが同じステージを共有し、アプリケーションの準備ができたときにプリローダーがアプリケーションにフェードインする方法を示します。 例13-14のように、プリローダーとアプリケーションはSharedSceneインタフェースを共有する必要があります。

例13-15 SharedSceneインタフェース

/* Contact interface between application and preloader */
public interface SharedScene {
    /* Parent node of the application */
    Parent getParentNode();
}

Applicationクラスは、アプリケーション・シーンへのアクセス権を持つプリローダーを指定するために実装します。 プリローダーは遷移を設定するために後でこれを使用します。

ここでは、インタフェースを実装する必要があります。 例13-16のコードは、アプリケーションが遷移中にアクティブであることを示します。

例13-16 SharedSceneインタフェースの実装

public class SharedStageApp extends Application 
        implements FadeInPreloader.SharedScene {
    private Parent parentNode;
    private Rectangle rect;
            
    public Parent getParentNode() {
       return parentNode;
    }    
 
    public void init() {
        //prepare application scene
        rect = new Rectangle(0, 0, 40, 40);
        rect.setArcHeight(10);
        rect.setArcWidth(10);
        rect.setFill(Color.ORANGE);
        parentNode = new Group(rect);
    }
    
    public void start(Stage primaryStage) {
        //setup some simple animation to 
        // show that application is live when preloader is fading out
        Path path = new Path();
        path.getElements().add(new MoveTo(20, 20));
        path.getElements().add(new CubicCurveTo(380, 0, 380, 120, 200, 120));
 
        PathTransition pathTransition = new PathTransition();
        pathTransition.setDuration(Duration.millis(4000));
        pathTransition.setPath(path);
        pathTransition.setNode(rect);
        pathTransition.setCycleCount(Timeline.INDEFINITE);
        pathTransition.setAutoReverse(true);
        
        pathTransition.play();
    }
}

プリローダー側では、プリローダー・ステージを非表示にするのではなく、プリローダー・シーンの後ろにアプリケーション・シーンを挿入し、プリローダー・シーンを一定期間フェードアウトすることによって、コードはフェードイン遷移を開始します。 フェードアウトが終了すると、アプリケーションがステージとシーンを所有できるように、プリローダーがシーンから削除されます。

例13-17 円滑な遷移のためのプリローダーのフェードアウトの使用

public class FadeInPreloader extends Preloader{
    Group topGroup;
    Parent preloaderParent;
 
    private Scene createPreloaderScene() {
        //our preloader is simple static green rectangle
        Rectangle r = new Rectangle(300, 150);
        r.setFill(Color.GREEN);
        preloaderParent = new Group(r);
        topGroup = new Group(preloaderParent);
        return new Scene(topGroup, 300, 150);        
    }
    
    public void start(Stage stage) throws Exception {
        stage.setScene(createPreloaderScene());        
        stage.show();
    }
    
    @Override
    public void handleStateChangeNotification(StateChangeNotification evt) {
        if (evt.getType() == StateChangeNotification.Type.BEFORE_START) {
            //its time to start fading into application ...
            SharedScene appScene = (SharedScene) evt.getApplication();
            fadeInTo(appScene.getParentNode());            
        }
    }    
    
    private void fadeInTo(Parent p) {
        //add application scene to the preloader group
        // (visualized "behind" preloader at this point)
        //Note: list is back to front
        topGroup.getChildren().add(0, p);
        
        //setup fade transition for preloader part of scene
        // fade out over 5s
        FadeTransition ft = new FadeTransition(
                Duration.millis(5000), 
                preloaderParent);
        ft.setFromValue(1.0);
        ft.setToValue(0.5);
        ft.setOnFinished(new EventHandler<ActionEvent>() {
            public void handle(ActionEvent t) {
               //After fade is done, remove preloader content
               topGroup.getChildren().remove(preloaderParent);
            }            
        });
        ft.play();
    }    
}

13.3.7 エラー・メッセージのカスタマイズ

プリローダーは、エンド・ユーザーへのメッセージのカスタマイズにも使用できます。 たとえば、例13-18に示すように、ユーザーが権限の付与を拒否したためアプリケーションを起動できない場合、より適したフィードバックを提供するためにプリローダーを使用できます。

例13-18 エラー・メッセージを使用したプリローダー

@Override
    public boolean handleErrorNotification(ErrorNotification en) {
        // Display error
        Label l = new Label(
                "This application needs elevated permissions to launch. " +
                "Please reload the page and accept the security dialog.");        
        stage.getScene().setRoot(l);
        
        // Return true to prevent default error handler to take care of this error
        return true;
    }  

エラーがプリローダー自体に影響を及ぼす場合、プリローダーはエラー・メッセージを提供できません。 たとえば、Javaプロキシ設定が正しくないのでWebページに埋め込まれたアプリケーションをユーザーが実行できない場合、プリローダー・コードをロードできないため、エラー・メッセージを表示できません。

13.4 パフォーマンスに関するヒント

プリローダーはメイン・アプリケーションのロード中に表示されるため、プリローダーは迅速にロードし、円滑に実行することが重要です。

次のガイドラインを使用して、プリローダーが確実にうまく実行されるようにします。

次のガイドラインは、メイン・アプリケーションとプリローダーの両方に適用可能です。 一般的なヒントは、第18章「コーディングのヒント」も参照してください。

  • JavaFXアプリケーション・スレッドでの長期間の操作を回避します。

    UI更新とイベント処理を一時停止する、JavaFXスレッドのブロックを回避します。 アプリケーションUIのフリーズを回避するには、JavaFX Worker APIを使用し、他のスレッドへの長期間の操作の負荷を軽減します。

  • start()メソッドの軽量な実装を維持します。

    init()メソッドでより多くの操作を行い、JavaFXアプリケーション・スレッドの負荷を取り除きます。

  • Webデプロイメントのためのアプリケーションのパッケージ化の際に埋込みを有効化します。

    デプロイメント記述子(JNLP)およびセキュリティ証明書(必要な場合)を埋め込むと、アプリケーションに関するすべての情報の収集に必要な時間を削減し、プリローダーとアプリケーションを高速に起動する際に役立ちます。

目次      

Copyright © 1993, 2025, Oracle and/or its affiliates. All rights reserved.