マルチウィンドウを表示するには

◆PROCESSING 逆引きリファレンス

 カテゴリー:制御系

マルチウィンドウを表示するには

【解説】

PROCESSINGは手軽にビジュアルなプログラムが作成できる事が利点ですが、シングルウィンドウが基本となります。

PROCESSINGで複数のウィンドウ(マルチウィンドウ)を実現する方法については、これまで多くの方が実験をし、ブログなどに掲載されてきました。その経緯や内容については、下記サイト様などにまとめられており大変参考となります。

しかしちょっと内容が古いですので、本記事では上記サイト様とは別の方法を紹介したいと思います。

なお、PROCESSING 2.0 系と 3.0 系ではマルチウィンドウの実現方法が大きく異なります。この記事ではPROCESSING 3.0 系での実現方法を紹介しています。以下の記述は 2.0 系には当てはまらないので注意してください。

 

【詳細】

準備

まずはサブウィンドウとして動作する部分を、別クラスとして作成します。クラスの名前は任意ですが、必ず PApplet クラスを継承させておいてください(じゃないと、サブウィンドウ側がPROCESSINGのプログラムとして動作しませんからね:笑)。

またメインウィンドウとサブウィンドウを実行するクラスには、必ず settings() メソッドを実装し、画面サイズはこの中で変更するようにしてください。

標準エディタだけでコーディングをしている方は、普段  settings() メソッドの存在は意識することが少ないと思いますので、注意が必要です。

たとえば上記のような感じです。

以降はこの雛形を元に説明をさせていただきますが、サブウィンドウ側が終了した時にどうしたいのかにより、プログラムの作りが若干異なってきます。

本記事では、サブウィンドウをSUB、メインウィンドウをMAINとして、以下の4パターンを考えます。

  • SUB終了時、MAINも(つまりAPを)終了する
  • SUBだけ終了。MAINではSUBが終了した事は気にしない
  • SUBだけ終了。MAINではSUBが終了した事だけ分かればOK
  • SUBだけ終了。終了時にSUBからMAINに処理結果を通知する

上から下にいくほど、難易度が上がります(汗)。

あとマルチウィンドウを行わせる前提として、PROCESSINGの内部構造に関する知識が必要となりますが、詳しくない方でも心配はいりません。簡単な機能しか利用しませんので(笑)。

簡単にいうと、PROCESSING(PApplet)は内部にPSurface と呼ばれるクラスを持っており、このPSurface から AnimationThread と呼ばれる別スレッドが起動されて、setupやdrawが呼び出されているという事です。

スレッド?と思われた方は、「マルチスレッドを実現するには」記事を参照してください。


上記に簡単な図にしてみましたが、ざっくり書くとこんな感じになっています。

PROCESSINGでコーディングしたプログラムを実行すると、私達の知らない所で main() => runSketch()=>settings()=>PSurfaceクラスの生成という順番で処理が流れているんですね。

覚えておいて欲しい事は、メインスレッドにあるPSurfaceクラスから AnimationThread と呼ばれる別スレッドが起動されていて、その中から setup() や draw() が呼び出されているという事です。

つまりPROCESSINGで作成したアプリケーションは、1つのウィンドウしか作らない場合でも、必ずメインスレッドとサブスレッドの2つが協力しながら動作しているのです。

そして私達が良くお世話になる setup() や draw() 関数は、サブスレッド側で動作しています。

今回はこのサブスレッド(下図のA)から、さらに別のウィンドウ(下図のBサブスレッド)を開きます。

(画像URL:illust-AC 様 wksさん)

サブウィンドウを作成するためには、サブウィンドウ用のクラスの実態(インスタンス)を作成した後に、サブスレッドの初期化を行って、ウィンドウを開かせる必要があります。

そしてその初期化処理は、PApplet内部にある runSketch() と呼ばれるメソッドにまとめられています。

では順番に説明しましょう。

 

ケース1:SUB終了時、MAINも終了する

一番簡単なケースですね。単純にマルチウィンドウが実現できれば良いという場合です。

まずメインウィンドウ側からサブウィンドウクラス(SecondApplet)を生成します。続いて、生成したサブウィンドウクラスを初期化します。

サブウィンドウが持つPAppletの初期化には、PAppletの runSketch メソッドを利用します。これが一番簡単で確実な方法です。

実はrunSketch メソッドは、メインウィンドウにおいては、アプリケーションが開始すると自動的に呼び出されているメソッドなのです。

ただしサブウィンドウは私達が独自に生成するものですので、自分達で runSketch() により初期化する必要があります。

このメソッドを呼び出せば、メソッド内部でPAppletに必要な初期化処理を全てやってくれます。おまけに static メソッドなので、PApplet . runSketch( … ); で簡単に呼び出せます。便利です。
.

PAppletを初期化するvoid PApplet . runSketch(final String[] args, PApplet constructedSketch);

args : 動作パラメータ(下記表を参照)
constructedSketch : サブウィンドウのPAppletインスタンス

argsにはPROCESSINGの動作に必要なパラメータを与えます。いくつかある中から使えそうなものを紹介します。

形式 説明
1 –location=x,y Windowの初期位置を指定します
2 –present 引数はありません。指定すると全画面表示になります
3 –window-color=#rrggbb Windowの背景色がrgbで指定した色になります

注意としては上記パラメータの最後に、必ずサブウィンドウにつける名前を指定する事です。

名前は何でも構いませんが、なるべく該当サブウィンドウを一意に特定するものが望ましいです。

こんな感じになります。コーディングの全体像は、下記サンプルを参照してください。

メインウィンドウ側の setup() メソッドの中から サブウィンドウクラスを生成し、runSketch() するだけです。とても簡単ですね。


下記サンプルを実行すると、上記のようにウィンドウが2つ表示されます。

注意点は、サブウィンドウを✕ボタンで閉じると、メインウィンドウも閉じられてしまう事です。つまりアプリケーションが終了してしまいます。

これはPROCESSINGに割り当てられているウィンドウの閉じるボタンの動作が、デフォルトではアプリケーションの終了になっている為です。

 

ケース2:SUBだけ終了。MAINではSUBが終了した事は気にしない

サブウィンドウの✕ボタンが押下された時に、アプリケーションが終了するのではなく、サブウィンドウだけを閉じるようにしたくはありませんか?

ケース1ではサブウィンドウ側の✕ボタンを押しただけなのに、メインウィンドウも閉じてしまいます。

これはPROCESSINGが、ウィンドウが閉じる際に自動的に実行されるメソッド(これをイベント関数と言います)に、アプリケーションの終了を割り当てているからです。

サブウィンドウの✕ボタンが押下された時にサブウィンドウだけを閉じるためには、ウィンドウが閉じる際に走るイベント関数を破棄して、私達が期待する動作を行う関数に書き換えてやる必要があります。

具体的には、以下の手順でプログラミングします。

  • PSurface がもつ Windowフレームを取得
  • 該当フレームから、getWindowListeners()でWindow操作用のイベントを取得
  • 取得したイベントをremoveWindowListener()で削除
  • 該当フレームに、addWindowListener()で新しいイベントをセット

PSurfaceは内部でウィンドウ(Windowフレーム)も管理しています。PSurfaceがもつWindowフレームは、以下のようにして取得します。

新しいWindow クローズ動作は DISPOSE_ON_CLOSE に設定します。DISPOSE_ON_CLOSE は、登録されたClose用のイベントを実行した後で、Windowを非表示にして破棄するものです。

呼び出される新しいイベントは、WindowAdapterを継承した独自クラスとして作成します。

一連の流れは、上記のようになります。

ウィンドウ操作イベントについては、JavaのAWTやSwingの知識が必要になりますが、この流れをむりやり図にすると以下のような感じになります。

(画像URL:illust-AC 様 ヤマモリポテコさん、acworksさん)

ちょっと強引な図ですが、JavaのWindows操作がよくわからない人でも、なんとなく何がやりたいのかイメージしていただけるのではないでしょうか?(笑)

上記サンプルではWindowAdapterを継承したクラス(WindowManage)にwindowClosed()をあてがっていますので、サブウィンドウが閉じられた直後のイベントを処理しています。

閉じられる直前のイベントを捕まえたい(例えば、本当に閉じるかどうか確認するダイアログボックスを開きたいなど)の場合は、windowClosed() ではなく windowClosing() を利用してください。

続いてwindowClosed()の中に何を記述するかですが、本ケースでは自分自身のスレッドを終了させる処理を記述する事になります。

メインウィンドウにサブウィンドウが終了した事を教えなくても良いわけですから、素直に自分自身を終了すれば良いのですね。

PROCESSINGは、PSurface から AnimationThread と呼ばれる別スレッドが起動される事で実行されています。つまりサブウィンドウはメインウィンドウとは別のスレッドになっているのです。

DISPOSE_ON_CLOSEはWindowを破棄してくれますが、Windowを表示しているスレッドを終了するわけではありません。今回はサブウィンドウを閉じるだけでなく、サブウィンドウを実行しているスレッドも停止したいので、自分自身のスレッドを終了させる必要があります。

AnimationThread を停止するには、サブウィンドウが持つ PSurfaceの stopThread() メソッドを利用します。
.

AnimationThread を停止するpublic boolean ret = sur . stopThread();

sur : PSurfaceインスタンス
ret : 戻り値。成功なら True

使い方は簡単です。終了させたい PApplet クラスから PSurface を取り出して、stopThread() メソッドを発行するだけです。

本例では、Window操作イベントクラスのコンストラクタに終了対象となるPAppletクラスを受け取るようにし、WindowがCloseされたら自分自身を終了しています。

コーディングの全体像は、下記サンプルを参照してください。

 

ケース3:SUBだけ終了。MAINではSUBが終了した事だけ検知する

それではメインウィンドウ側でサブウィンドウ側の死活を知りたい場合は、どうすれば良いでしょうか?。

PROCESSINGは、PSurface から AnimationThread と呼ばれる別スレッドが起動されていると説明しましたが、PSurface の isStopped()メソッドを利用すると、この AnimationThread  の死活状態を取得する事が可能です。
.

AnimationThread の死活状態を取得するpublic boolean ret = sur . isStopped();

sur : PSurfaceインスタンス
ret : 戻り値。生きているなら True

この isStopped() メソッドを、メインウィンドウから定期的に呼び出せば、サブウィンドウ側の死活が検査可能です。

コーディングの全体像は、下記サンプルを参照してください。

 

ケース4:SUBだけ終了。終了時にSUBからMAINに処理結果を通知する

最も難易度の高いケースです。とはいえ、これが最も良くあるケースなのではないでしょうか? (汗)。

メインウィンドウからサブウィンドウを開き、サブウィンドウ側で何かの処理や計算を行わせて、その結果をメインウィンドウ側で受け取るケースです。

サブウィンドウからメインウィンドウに処理結果を通知する方法は幾つか考えられますが、もっとも理想的なのはサブウィンドウ側からメインウィンドウにあるメソッドを呼び出して、結果を通知する事です。

Javaにはリフレクション (reflection) という仕組みがありますので、今回はこれを利用します。

Javaのクラスが実装している getMethod() というメソッドがあります。このgetMethod() を実行すると、指定したメソッドを呼び出すために必要な Methodクラスの情報が得られます。

関連する部分を抜き出すと、こんな感じです。mainAppletはメインウィンドウのPAppletです。

サブウィンドウ側からメインウィンドウ側にある GetSubStatus という名前のメソッドを取得しています。

このMethodクラスの情報を元に、サブウィンドウ側からメインウィンドウ側のメソッドを呼び出します。

呼び出しには Method クラスが持っている invoke() メソッドを利用します。

method は先程取得したメインウィンドウの GetSubStatus メソッド情報です。これを invoke() メソッドを利用して呼び出しています。

上記例では、GetSubStatusに100という値を渡します。

(画像URL:iilust-AC 様 うどんさん)

この流れを絵にすると、こんなイメージになります。

 

【関連記事】

 


サンプルプログラム

ケース1:SUB終了時、MAINも終了する例:

実行すると2つのウィンドウが開きます。メインウィンドウを閉じても、サブウィンドウを閉じても、必ずアプリケーションが終了します。

<出力サンプル>

 

ケース2:SUBだけ終了。MAINではSUBが終了した事は気にしない例:

サブウィンドウの✕ボタン押下時、サブウィンドウ側だけ終了するようにしています。

<出力サンプル>
jconsole でスレッドの数を監視しました。サブウィンドウ終了時、ちゃんとスレッド数が減少しているのがわかります。

 

ケース3:SUBだけ終了。MAINではSUBが終了した事だけ検知する例:

ケース2に比べて、マウスクリックでサブウィンドウを生成するように変更しています。

メインウィンドウの draw() で定期的にサブウィンドウの死活を監視し、状態を画面に表示します。

<出力サンプル>

 

ケース4:SUBだけ終了。終了時にSUBからMAINに処理結果を通知する例:

サブウィンドウをマウスクリックすると、サブウィンドウが終了します。この時、サブウィンドウのフレームカウントをメインウィンドウに渡します。

サブウィンドウを✕ボタンで終了するとフレームカウントが渡されないため、メインウィンドウでは終了コードが -1 のままとなります。

<出力サンプル>

(画像URL:iilust-AC 様 うどんさん)

 


PROCESSING逆引きリファレンス一覧 へ戻る

本ページで利用しているアイコン画像は、下記サイト様より拝借しております。各画像の著作権は、それぞれのサイト様および作者にあります。