New process でウインドウを手前に持ってくる 〜 プロセス間通信の実際

Webエリアを配置したフォームはそうでないフォームと振る舞いが微妙に異なる、という印象を受けている。たとえばダイアログを表示すると親ウインドウに隠れてしまって操作できなくなる問題が起きたりする。4Dのマニュアルを調べると、ウインドウを手前に持ってくるコマンドはBring to front、引数はプロセスID。つまりプロセスを一番手前に持ってくるのであって、同じプロセス内で動いているウインドウの前後関係を操作するコマンドは存在しない。4Dは同じプロセス内で表示されたウインドウの前後関係を保証しない、のが仕様であるらしい。確かに今回の問題はmacOSでは起こらずWindowsだけで発生していた。ならばNew processでウインドウを制御するしかない。

New processは、以前(v2011の頃)使っていたが起動が遅くて評判が良くないため使わなくなっていたのだが。どうやらv13あたりで速度が改善されたようだ。それならばと使ってみたところ、実装した結果が良かったので報告する。

当社がコンポーネントで配布している「Calendar」というプロジェクトがある。呼び出し側で、Webエリアが配置されたフォームから呼び出すと裏に回ってしまう。次のようなコードで使う。

  //カレンダー表示 vB02_btnCADateのボタンメソッド


C_DATE($date)

$date:=vB02_fldDate

  // クリックされたボタンの近くに表示

C_LONGINT($left;$top)

zz_obj_LeftTop (->vB02_btnCADate;->$left;->$top;398;441)


$dlg_ok:=D20_Calendar (->$date;$left;$top;"入力")
If ($dlg_ok=1)
	
	vB02_fldDate:=$date


End if 

これが裏に回って困っていた。そこでNew processを導入。次のような、D20_Calendarをラップした「D20_Calendar_np」を作成。コンポーネント側は修正しないとしてみた。

  //D20_Calendar_np
  //20210905 wat
  //ダイアログが後ろに隠れる問題がWinだけで発生。おそらくVPエリア(Webエリア)がらみ
  //別プロセスでフロントに持ってくることで解決しようとしている

C_LONGINT($1;$left)
$left:=$1
C_LONGINT($2;$top)
$top:=$2
C_LONGINT($3;$parent_process)
$parent_process:=$3
C_OBJECT($4;$sobCA)
$sobCA:=$4
C_DATE($date;$retDate)
C_LONGINT($dlgOk)

  //カレンダーの初期値を取得、共有オブジェクトを読む
$date:=$sobCA.date
$retDate:=$date

$dlgOk:=D20_Calendar (->$date;$left;$top;"入力")
If ($dlgOk=1)
	  //OKされた時だけ日付を取得
	$retDate:=$date
	
End if 

Use ($sobCA)
	  //共有オブジェクトに代入するときはUseブロックで。
	$sobCA.date:=$retDate
	$sobCA.dlgOk:=$dlgOk
	
End use 

RESUME PROCESS($parent_process)

呼び出し側は次のようになる。

  //カレンダー表示

C_DATE($date)
$date:=vB02_fldDate

  // クリックされたボタンの近くに表示するための座標値計算
C_LONGINT($left;$top)
zz_obj_LeftTop (->vB02_btnCADate;->$left;->$top;398;441)
$left:=$left-398

  //カレンダー表示を別プロセスで実行、確実にフロントに持ってくる
C_LONGINT($myProc;$proc)
$myProc:=Current process
C_OBJECT($sobCA)
$sobCA:=New shared object
Use ($sobCA)
	  //共有オブジェクトに代入するときはUseブロックで。
	$sobCA.date:=vB02_fldDate
	
End use 

  //ニュープロセスで表示
$proc:=New process("D20_Calendar_np";0;"D20_Calendar_np";$left;$top;$myProc;$sobCA)
If ($proc#0)
	BRING TO FRONT($proc)
	
	  //子プロセスが終了するまで待つ
	PAUSE PROCESS($myProc)
	
	  //終了した子プロセスから、共有オブジェクトで、結果を取得
	$dlgOk:=$sobCA.dlgOk
	If ($dlgOk=1)
		  //共有オブジェクト
		vB02_fldDate:=$sobCA.date
		
		  //20210902 wat view proを更新
		C_TEXT($frmObjName)
		$frmObjName:=B02_varVpObjName_get 
		C_LONGINT($col;$row)
		C_TEXT($str)
		
		  //作成日
		$str:=JCL_str_Date (vB02_fldDate)
		$col:=8
		$row:=0
		JCL_vp_SetTextValue ($frmObjName;$col;$row;$str)
		
	End if 
End if 
BRING TO FRONT($myProc)

親プロセス(呼び出し)側ではNew processでウインドウを表示するメソッドを実行して、子プロセスをBring to frontしてPAUSE PROCESSで待つ。子プロセスはRESUME PROCESSして終わる。すると親プロセスのPAUSEが解除されて次に進み、共有オブジェクトに値が返ってきている。$sobCA.dlgOkでダイアログがOKされかたを判定して、$sobCA.dateをフォームオブジェクトに代入する、という仕組み。

子プロセスに渡すオブジェクトが「共有オブジェクト(Shared object)」というところがミソ。共有オブジェクトを参照するときは普通のオブジェクトだが、代入するときはUse – End useブロックで排他制御をする必要がある。

コンポーネント側を修正しないように実装してみたが、一方的にカレンダー側がNew processになると、呼び出し側も共有オブジェクトを使うなどの対応に迫られるので、今回の実装はありだなと結論。コンポーネント側に両方のAPIを備えるのが正解だろう。