4D」カテゴリーアーカイブ

4D コマンドの定義に移動 オンラインヘルプ

4D コマンドの定義に移動 オンラインヘルプ

4Dのメソッドエディタには、オプションキーを押しながらマウスカーソルをコマンド名の上に置くと、コマンド名がリンク文字列のように変化して、クリックするとリファレンスマニュアルがブラウザで表示される、という機能がある。

最近、コマンドの定義に移動しない。定義が表示されるコマンドもあるけど、いつもv13のリファレンスマニュアルが表示される、なぜかなと。

コマンドの定義に移動する先は、4Dメニューの【環境設定】の「一般」の「Webサイト」という項目に記述されている。次のようになってた。

http://doc.4d.com/4dv13/help/command/ja/

v16の場合、正しくは次。

http://doc.4d.com/4dv16/help/command/ja/

http://doc.4d.com/4Dv16R5/4D/16-R5/General-Page.300-3509135.ja.html#102865

http://doc.4d.com/4Dv15/4D/15.4/Writing-a-method.300-3285560.ja.html

※ 注意 ※

コマンドを選択したり、キャレットをコマンド名のどこかにおいて右クリックしても同様の動きになる。この場合、コマンド名をすべて選択しているか、または何も選択していないでキャレットが挿入されているか、のどちらかでないとコマンドを正しく認識しない。中途半端にコマンドの一部の文字が選択されているとブラウザが表示されても、目的のコマンドのページに移動しないので注意。

※ 注意 ※

4D のマニュアルは毎週更新されているそうです。このブログの情報も最新ではないかもしれません。4Dドキュメントセンターで確認することをお勧めします。

4D v16: フォームの2ページ目にWebエリアを配置しない

4D v16: フォームの2ページ目にWebエリアを配置しない

フォームにWebエリアを配置して、WA SET PAGE CONTENTでhtmlコンテンツをレンダリングしています。

このフォームで表示したいWebエリアが4つあって、1ページのフォームでは入りきらない。そこでページを増やして2ページ目にWebエリアを配置したのでした。するとその2ページのWebエリアにhtmlコンテンツが表示されない。

不思議なことに3ページに移動させて戻ったりすると2ページのWebエリアは表示される。2ページから1ページに戻って、2ページに行くと表示される。しかしウインドウを開いた直後に2ページに移動させたときだけはどうしても表示されないのでした。メソッドが悪いわけではなさそう。

(4D サポートに電話して確認、解決しました)

これは4D、というかWebエリアの仕様でした。Webエリアは表示方法や画面制御について外部のレンダリングを使っていて、そのため4Dが制御しきれない部分があって、ウインドウを開いた直後に表示されるページ(1ページと0ページ)には、ウインドウを開いたタイミングで表示されるが、別ページのWebエリアにはオブジェクトがロードされる前にコマンドを実行しても作用しない、ということでした。どうやらFORM GOTO PAGE(2)の直後にWA SET PAGE CONTENTを実行しても、まだWebエリアはロードしきれていないため内容が表示されない。

で、回避策を考えました。
1)ページをやめて別のウインドウを開く
2)0ページに配置して、1ページを表示するときは四角形で隠しておく
3)0ページに配置して、2ページ目を表示するときだけ所定の位置に移動させ、ほかのページを表示するときは遠くに移動させておく
4)0ページに配置して、2ページ目を表示するときだけVisible、ほかのページを表示するときはInvisible

1)はできることはわかっているけど仕様変更なのでやりたくない。2)から試す。すると、1ページ目に置いた四角形は、0ページのボタンとか四角形を隠すことはできるが、0ページのWebエリアを隠せない。Webエリアは常に一番前に表示されてしまう。順番も4Dが制御しきれない部分のようです。左の図の2つあるWebエリアのうち、後ろの方を0ページに、前の方を1ページに配置したら、右の図のように1ページ目の四角形やボタンよりも前に表示されてしまう。これも驚きの結果だ。

 

3)はOK。ちょっとコードがトリッキーに見えるけど、これでも良しか。
4)もOK。こちらは仕様に納得感があり、コードはシンプル。これを採用。

マニュアルを見ると、次のように書いてあります。VISIBLEやMOVEで制御しろと言っているような...

Webエリアのプログラムによる管理
他の動的なフォームオブジェクトのように、Webエリアはオブジェクト名とテキスト型の変数名を持ちます。これらはWebエリアをプログラミングに よって処理する際に使用されます。 特にWebエリアに対して OBJECT SET VISIBLE コマンドと OBJECT MOVE コマンドを使用することができます。

4D: TEXT型のフィールドにインデックスを張るときの注意

TEXT型のフィールドにインデックスを張るときの注意

フィールドに保存した文字列を検索しても該当しない、という現象にはまりました。(はまりレベル3)

テーブルHBにテキスト型のフィールドを追加しました。そのフィールドにHBのすべてのレコードについて、メソッドで求めた文字列を保存しました。その保存した文字列で検索しても該当しない、のです。

1対N構造のテーブルがあって(テーブルHBとテーブルKJ)、N側(テーブルKJ)のラインナップが同じであるテーブルHBのレコードを探す、という機能を実装していたときのことです。ロジック的には、テーブル HBの各レコードについてテーブル KJをクエリして判定すればいいのですが、テーブルHBにはレコードが1万件以上あって、遅くてとても実用になりません。テーブルKJをキャッシュして配列に持たせても同様に遅い、という状況でした。

処理の高速化を図るため、テーブルHBに「HB_KJLINE」というフィールドを用意して、KJ_IDをコンマ区切りの文字列で連結して保存します。こうしておくと上記のクエリはテーブルHBだけをクエリすればよいことになります。検索時に遅いので保存時に一つ余計なフィールドを用意して検索のための値を保存しておく、というデータベース高速化の常套手段です。

高速化のためのコーディングですから、追加したHB_KJLINEにはインデックスを作成しました。HB_KJLINEの文字列は255文字を超えるでしょうからテキスト型です。そして実行、値はHB_KJLINEに入っていますが、あるはずの文字列で検索しても該当なし、です。

カレントテーブル表示(コマンド+Uキー)でクエリ(コマンド+Y)して、そのフィールドに入っている文字列で検索してみても、該当しません。一つレコードを保存してみると、保存したレコードだけ正常に該当します。

(4D Japanのサポートに電話して解決しました。)

原因はインデックスにありました。テキスト型のフィールドにインデックスを作成した場合、最初の1,024文字までしか結果が保証されない、という4Dの仕様です。HBには380個以上のKJを持つものがあり、この場合KJ_IDがコンマを入れて一つ4〜5文字とするとKJLINEの文字数は1,024文字を超えます。インデックスのチェックを外したら検索できるようになりました。

教訓1:

テキスト型のフィールドにはインデックスを作成しない。高速化を図る場合は別の方法を考えること。

ここで次の教訓も覚えておきましょう。

教訓2:

4Dでは、レコード数が32000件以上の場合、FInd in Arrayの該当レコード番号が保証されなくなる。(はまりレベル5)

 

CALL WORKERの使用例

何個のスレッド数が適正かを判断するために、スレッド数は可変、外からわたすようにしておきます。

最初のメソッドです。ここからワーカーコマンドで別スレッドで実行するメソッドを呼び出します。スレッド数と担当番号を渡します。

//CW_RIM_pick_MD_Init
 //20171228 yabe

C_LONGINT($1;$numOfThread)
$numOfThread:=$1
C_LONGINT($winRef;$i;$myNum)
$winRef:=Current form window //途中経過表示用のフォーム

For ($i;1;$numOfThread)
 $cw_name:="cw_"+String($i)
 $myNum:=$i-1
 CALL WORKER($cw_name;"CW_MD_Add";$cw_name;$winRef;$numOfThread;$myNum)
End for 

呼び出された側では、OPテーブルの全レコードについて、担当番号のレコードだけ処理します。全レコード数をスレッド数で割った余りが自分の担当番号と一致した場合にだけ処理する、というロジックにしました。テーブルとフィールドはコンポーネント化を考慮してポインタで参照しています。第4引数のフォームに、CALL FORMを呼び出して進捗状況を表示します。

最後にすべての処理が終わったところで自分自身をKILL WORKERしています。このため親メソッドからワーカー名(第1引数)をもらっています。

//CW_MD_Add
//20171228 wat
//コンポーネントメソッド、見出しのリンクを更新

C_TEXT($1;$cw_name)
$cw_name:=$1
C_LONGINT($2;$winRef)
$winRef:=$2
C_LONGINT($3;$numOfThread)
$numOfThread:=$3
C_LONGINT($4;$myNum)
$myNum:=$4

MD_DefInit 
SN_DefInit 
HB_DefInit 
HB_cache_make 

C_POINTER($OPptr)
C_POINTER($OP_HTMLptr)
C_POINTER($OP_ORG_FNAMEptr)
C_LONGINT($i;$numOfRecs)
C_LONGINT($hb_id)
C_BOOLEAN($done)
C_TEXT($html)

//見出しテーブルをクリアして、シリアル番号をリセット
JCL_tbl_DelAll (vMDptr)
JCL_tbl_ResetSN ("MD_ID";"MIDASI")

//オーピーの全レコードについて見出しを抽出
$OPptr:=JCL_tbl_GetPtr_byName ("V_ORG_PAGE")
$OP_HTMLptr:=JCL_tbl_GetFldPtr ($OPptr;"OP_HTML")
$OP_ORG_FNAMEptr:=JCL_tbl_GetFldPtr ($OPptr;"OP_ORG_FNAME")

READ ONLY($OPptr->)
ALL RECORDS($OPptr->)
FIRST RECORD($OPptr->)

READ WRITE(vMDptr->)
$numOfRecs:=Records in selection($OPptr->)
$i:=1
While (($i<=$numOfRecs) & ($done=False))
 CALL FORM($winRef;"CW_RIM_pick_MD_Pgs";$myNum;$i;$numOfRecs;$start;$numOfThread)

If (($i%$numOfThread)=$myNum)
// OPのレコードに対して実行
GOTO SELECTED RECORD($OPptr->;$i)
$html:=$OP_HTMLptr->
hb_id:=HB_cache_find($OP_ORG_FNAMEptr->)

   // 見出しテーブルにレコードを追加
MD_Add ($html;$hb_id)
End if 

// ユーザによるキャンセルがあれば$doneをfalseにセットする
$i:=$i+1

End while 
UNLOAD RECORD(vMDptr->)
READ ONLY(vMDptr->)

KILL WORKER($cw_name)

以下は、CALL FORMで現在の進捗を表示するメソッドです。ホストプロジェクトで画面に表示されているフォームの進捗バーと文字列に、処理数を渡しています。

//CW_RIM_pick_MD_Pgs
//20171228 yabe
C_LONGINT($1;$myNum)
$myNum:=$1+1
C_LONGINT($2;$count)
$count:=$2
C_LONGINT($3;$all)
$all:=$3
C_LONGINT($4;$start)
$start:=$4
C_LONGINT($5;$numOfThread)
$numOfThread:=$5
C_POINTER($ptr)

$ptr:=OBJECT Get pointer(Object named;"vC01_lstCW_Count")
$ptr->{$myNum}:=$count

C_LONGINT($sum)
$sum:=Sum($ptr->)
$ptr:=OBJECT Get pointer(Object named;"vC01_Thermo")
$ptr->:=$sum/$numOfThread/$all*100

C_TEXT($msg)
$msg:="No."+String($myNum)+"  "
$msg:=$msg+String($count)+" / "+String($all)
$msg:=$msg+"  ("+String((Milliseconds-$start)/1000)+"sec)"
$ptr:=OBJECT Get pointer(Object named;"vC01_lstCW_CountStr")
$ptr->{$myNum}:=$msg

【注意】

この例では、ユーザによる途中キャンセルが実装されていません。

4Dの親コンポーネントのテーブルとフィールドをポインタで参照する

親コンポーネントのテーブルとフィールドをポインタで参照する

テーブルに値を保存するような処理をコンポーネント化する場合は、ホストデータベースのテーブルをコンポーネント側で参照するのにポインタを使います。自分のテーブルではないためテーブル名を記述することができないからです。4Dのデザインリファレンスに「ホストデータベースとコンポーネントはポインターを使用して通信を行うことができます。」とあります。

http://doc.4d.com/4Dv16/4D/16.3/Interaction-between-components-and-host-databases.300-3670869.ja.html

ここではホストデータベースのテーブルを「親のテーブル」と呼びます。親のテーブルはポインターで参照します。次のように記述します。これはHBデータベースのIDと名前を配列に取得する例です。まずポインタ変数をプロセス変数として宣言、テーブル名とフィールド名のポインタを取得します。

  //HB_DefInit
  //20171228 wat
  //テーブルとフィールドのポインタをプロセス変数に取得
  //本文 テーブル

  //テーブル
C_POINTER(vHBptr)
vHBptr:=JCL_tbl_GetPtr_byName ("HONBUN")

  //フィールド
C_POINTER(vHB_IDptr)
C_POINTER(vHB_NAMEptr)
vHB_IDptr:=JCL_tbl_GetFldPtr (vHBptr;"HB_ID")
vHB_NAMEptr:=JCL_tbl_GetFldPtr (vHBptr;"HB_NAME")

次にそれぞれのテーブルから値を取得、プロセス変数の配列に格納しておきます。

  //HB_cache_make
  //20171228 wat
  //本文 テーブルをキャシュ配列に取得

ARRAY LONGINT(vAryHB_ID;0)
ARRAY TEXT(vAryHB_NAME;0)

READ ONLY(vHBptr->)
ALL RECORDS(vHBptr->)
SELECTION TO ARRAY(vHB_IDptr->;varyHB_ID)
SELECTION TO ARRAY(vHB_NAMEptr->;vAryHB_NAME)

このようにして配列にキャッシュしておき、あとでHB_cache_findを使って参照します。

$hb_id:=HB_cache_find ($name)

HB_cache_findは次のように記述しておきます。

  //HB_cache_find
  //20171228 wat
  // hbのキャッシュからファイル名を探して、あればIDを返す。なければゼロ

C_TEXT($1;$filename)
$filename:=$1
C_LONGINT($0;$hb_id)
$hb_id:=0
C_LONGINT($index)

$index:=Find in array(vAryHB_NAME;$filename)
If ($index>0)
 $hb_id:=vAryHB_ID{$index}
End if 

$0:=$hb_id

このようにして親テーブルの値を参照します。保存するときも同じ要領でいけます。たとえば次のように記述します。

CREATE RECORD(vMDptr->)
vMD_NAMAEptr->:=""
vMD_IDptr->:=$md_id
SAVE RECORD(vMDptr->)

コンポーネントから親テーブルを参照する場合は、上記のような記述をしておけば内部テーブルと同様にデータを保存することができます。