LAN内通信アプリを作ろう その4 JavaFXのコントロールに情報を設定する/スレッドからJavaFXの画面を呼び出す


仮眠(5時間)も取ったので作業を再開しましょう。

LAN内通信アプリは今日で終わりなのでササッと(出来ても出来なくても)

  • メッセージ部分の受信
  • 受信したメッセージの表示(受信ウィンドウの作成)

受信処理の改修からやっていきます。

受信処理の改修

  • 受信メッセージの表示ウィンドウを作成する。
  • メッセージを安定して受け取れるようにする。

受信メッセージの表示ウィンドウを作成する。

まずはこれ。
JavaFXを利用して受信したメッセージを表示する画面を作成。

送信ウィンドウを流用して作ったのがこちら。

受信時にウィンドウを呼び出す。

ここで詰まった。

今回のアプリケーションでは「メッセージを受信したときに受信メッセージを新規ウィンドウで表示する」というイメージで作成していて
「受信用の待ち受けスレッドを用意してポートを監視している」作りになっている。

最初は「受信用の待ち受けスレッドで画面を作成して表示すればいいか」と思っていたものの
javafx.stage.Stageのインスタンスを作ってshow()メソッドを呼ぶとIlegalStateExceptionを出してくる。

↓こんな感じ

RxThread.java
    (受信処理)

    Stage stage = new Stage();
    stage.show();

↓結果がこちら

このExceptionはなんぞや?と思って調べてみると、こんな事実に行き当たった。
曰く
「JavaFXの指定したスレッド(JavaFX application thread)以外からJavaFXの要素を操作しようとすると上記例外が投げられる」

で、回避方法を調べてみたところ、javaFX.platformなるクラスが存在することが判明。

Oracle - JavaFXドキュメンテーション クラスplatform
(日本語)

このクラスのrunLater()メソッドにrunnnableなスレッドを登録してやることでJavaFX applicationが代替実行してくれるそうな。

あ、formみたいにnewしてshow()!とは行かないのね。
さっそく調べた内容を元にStageを表示する処理を
書き換えて動作確認。

↓こんな感じに変更

RxThread.java
    (受信処理)

    Platform.runLater( () -> {
        Stage stage = new Stage();
        stage.show();
    });

↓結果がこちら(真っ白なウィンドウがnewしたStage。サイズ変更してるけど最初はかなり大きい)

…えー、この事実を受けて
画面描画の構成を変える必要が鎌首をもたげたわけですね。(ため息)
今の作りだとあまりにも汚いので・・・

直しました(1時間使って)。

ざっくりこんな感じの作りに修正


Stage関連処理

画面毎のFXMLファイル

画面毎のStage取得用クラス

画面毎のControllerクラス

送受信処理

ソケット通信を利用してデータを送信するスレッド継承クラス
受信ポートを監視し、データを受信するスレッド継承クラス

通常処理
Platform.runLator()にStageの描画処理を行わせるための汎用スレッド継承クラス
エントリポイントを含むクラス

上記作り変えを行ったことで、
[メッセージの受信を確認] => [受信メッセージStageを取得] => [Applicationスレッドに描画タスクを依頼] => [Stageの描画]
という処理が出来るようになりました。

次はこのStageに受信したメッセージを表示させたいと思います。

JavaFxを用いて表示したGUIのコントローラに値を設定する

ここもがっつり詰まりました。
しかもnull参照で。

時間が・・時間が・・・(焦燥)

設定の仕方はこんな手順で実現できます。

  1. GUIに紐付けているコントローラークラスに、コントロールを操作するための処理を用意する
  2. コントローラのインスタンスを取得して用意した操作メソッドを呼び出す

書いてしまえばなんと簡単に見えることか

GUIに紐付けているコントローラークラスに、コントロールを操作するための処理を用意する

これは簡単。普通のセッターを作ってあげればOK。
コントロールをオブジェクトとして扱う方法はLAN内通信アプリを作ろう その3の「IDを設定したコントロールと同一の型、名前を持つオブジェクトをクラス内で宣言する。」の項目を参照されたし。

コントローラのインスタンスを取得して用意した操作メソッドを呼び出す

これ。これでガッツリはまりました。
ポイントは以下の通り。

  • コントローラのインスタンスはFXMLLoaderクラスのgetController()メソッドを利用する。 (Object型で返却されるので、コントローラに指定してるクラスでキャストすること)
  • 上記インスタンスはFXMLLoader.load()メソッドを実行した段階で生成される。 (これより早いタイミングで触ろうとするとnull参照で怒られる。ガッ!)

で、この処理のためにfxmlLoaderの実態を持っているコントローラでgetControl()の値を返却するゲッターを作り
それをStage描画用スレッドで受け取ってそのまま返却するラッパーを作って・・・と細かいことを延々と・・・

その甲斐あって、取得したメッセージを表示するまでは成功

メッセージを安定して受け取れるようにする。

ここで大変悲しいお知らせです。
えー、昨日の自分はなんか意識が朦朧としてたので(言い訳)
無駄な処理を入れてメッセージの待ち受けを行ってました。

ので、無駄な処理を省いてこんな感じに修正

RxThread.java
            while (length >= 0) {
                byte[] buffer = new byte[1024];
                length = dis.read(buffer);
                sBuffer.append(new String(buffer));

            }

  1. バッファ領域とって
  2. 取得したデータ量を更新して
  3. 取得したデータは文字列バッファに格納していく

って処理ですね。
バッファサイズはlengthの値でもよかったかなー。

ソケット通信の場合、取得する情報が無くなると-1を返却してくるので
取る情報が無くなったら抜けてくれるわけですね。

今のところあんま問題ないのでこれ放置で。
本当は最初に送信するデータの量とかを相手方に伝えたりするんですが(伝送中の損失に備えるため)
まあ、今回はLAN内だしね。
暗号化もナシ。

端末間で通信してみる。

さて、とりあえず一通り実装した(ハズ)なので、LAN内の端末間で通信してみましょ。

ひとまず開発マシン(windows 7)と営業用のノートパソコン(windows 10)での結果がこんな感じ

(携帯のカメラで撮影したので非常に重い写真。開発した本体(ライブラリ込み)の70倍超)

で、悲しいかなLinuxで起動しようとしたら怒られました。
軽く調べてみたところ、なんかsun.*系のライブラリがどーたらこーたらなのでそこはもうちょっと調査が必要か。

せっかくJavaで組んだんだから多種のOSに対応したいものね。
とりあえず目的は達成?したので今回はこれまで。
直接IP打ってたりで少し使いづらいのはアレだけど。そこは改善点として・・・別機能だしね(開き直り)
取得したアドレスに/とか入ってるのが気になるところ。

追記:JavaFXのライブラリが入ってなかったっぽい・・・あれ?1.8だともう標準じゃなかったっけ・・・?
openjfxを導入したら普通に起動しました。

通信結果はこんな感じ。(VNCで表示してます。)


最終日の稼動は07:41:54で終了。
全体で23:03:51。

来週はどうなるかなー?

あ、挙げ忘れてたけど、GitHubはこちら

GitHub - PrivateMessenger
https://github.com/Shiratori1218/PrivateMessenger