programing」カテゴリーアーカイブ

4D v18からはストラクチャーではなく「プロジェクト」

これまで4Dでのアプリ開発はストラクチャーファイル(.4db)を作ることだった。ストラクチャーファイルはバイナリファイルだ。4Dで開けば中身を見ることができるが、外部からは何が書いてあるのかわからない。このためgitのようなバージョン管理システムでソースの差分を管理することができなかった。

4D v18から「プロジェクトモード」という形で保存できるようになった。プロジェクトモードではフォームもメソッドも「Sources」というフォルダにテキストファイルで保存されている。これらのテキストファイルをテキストエディタで修正すると、変更内容が4Dのフォームエディタやメソッドエディタに表示される。外部からテキストファイルを修正すると「ストラクチャー」に変更内容が適用されるのだ。

従来からあるストラクチャーもサポートされていて、従来通りの開発方法を選んでソースをバイナリ形式で保存することもできる。こちらは「バイナリモード」と呼ぶ。新規プロジェクトを始めるとき、開発者はバイナリモードかプロジェクトモードのどちらかを選ぶことになる。バイナリモードには「プロジェクトに書き出し」を実行してプロジェクトモードに書き出すことができる。v17以前のプロジェクトはこの方法でプロジェクトモードに移行する。プロジェクトモードからバイナリモードに変換することはできない。

筆者がプロジェクトを推奨する理由はいくつかある。

1)メソッドを生成したい

2)フォームを生成したい

3)gitを使って修正箇所(差分)を知りたい

これまで1)はプロジェクトメソッドであればやっていた。JCL4D(当社ライブラリ)を使えばテーブル定義のテキストファイルからプロジェクトメソッドを生成できる。ただしオブジェクトメソッドやフォームメソッドを生成することはできなかった。熱望していた2)はほぼほぼ実現は無理、3)もあきらめていた。

2014年にパリで開催された4D Developer Summitでに参加した際に、「フォームジェネレータを作りたい、使えるコマンドはないか」と質問した時は、そのようなコマンドの実装計画はない、と回答された。2020年のv18でフォームは生成できるようになった。対応が早いことに驚く。

以下、バイナリモードを使ってみたところ、いくつか注意事項があったのでメモしておく。

□ 標準入出力フォームを生成する機能がない

ウィザードを使ったフォーム生成機能がない。ユーザモードを使ってデータを確認するときは不便だろう。もともとフォームを自動生成する仕組みを作るつもりだったので、まあいいかという感想。半年くらいかけてJCL4DにFormGeneratorを作成した。これを使えばフィールド定義ファイルからテーブルを生成、続けてメソッドとフォームを自動生成することができる。

□ スタイルシートが開発モードでは確認しづらい

実行モードでは適用される。設定がうまくできているか確認しづらいので注意。

□ 外部テキストファイルを修正した場合、アプリに反映するのに少し時間がかかる

Finder/Desktop上でファイルの入っているフォルダを表示すれば変更が適用されたりする。

□ リモートの4D Server上のソースやフォームを4D Clientで接続して編集することができない

ソースはテキストファイルなので、開発者が各自手元で修正してアップしてcommitすることになる。

これ以外にも注意点や制約がありそうだが、気付いた時点でまたブログを追加する。

4D Server v18をmacOS Big Surで運用中に発生した問題 2021年3月以降

4D Server v18.4をmacOS Bigu Surにインストールした後、運用中にトラブルに遭遇した。

4D v18.4のインストーラ「4D_v18.4_Mac_Japanese.dmg」をダブルクリックして起動。次のような画面が表示される。いつものようにマウントされたボリュームの4DのフォルダをApplicationsフォルダにドラッグ&ドロップしてインストール。あとは4D Serverをドックに入れて起動するだけ。

dmgファイルを開いたところ。このままフォルダをドラッグ&ドロップしてはだめ

この状態で運用していたらファイルアップロードに絡んで実装している機能でエラーが表示された。サーバ側で受信したファイルを所定のフォルダに移動しようとした時、MOVE DOCUMENTが失敗しているようだ。「ドキュメントが移動できません。」と表示されている。

これまで動いていた機能なので不審に思って、サポートに連絡。すると「インストールに失敗している」との回答が得られた。なんでも2021年3月にAppleがGate Keeperの仕様を変更したらしい。「dmgをマウントしたボリュームの「4D v18.4」フォルダごとドラッグ&ドロップしないで、.appファイルを一つずつドラッグ&ドロップしてコピーしてほしい」とのこと。

インストールが失敗しているか確認するには、アクティビティモニタで4D Serverをダブルクリックして、「開いているファイルとポート」をクリック、そこに表示される4行目が「/Applications…」で始まっているかを見る。インストールが失敗していると「/Private」で始まる。

インストールに失敗している場合
正しくインストールされた場合

「4D v18.5」でも同じ注意が必要。

【注意】

この問題が厄介なのは、MOVE DOCUMENTのようにサーバ側でファイルを動かすような機能を実行しないとエラーにならないこと。/Private状態でもDBへの読み書きなど、ほとんどのコマンドは動いてしまう。また、dmgを開いた時、インストーラの画面には上記の注意を促すメッセージは表示されない。


4D パスワードを入力中、タイプインされた文字列を誰かに見られないように「・」で表示したい

パスワードを入力中、タイプインされた文字列を誰かに見られないように「・」で表示したいばあいにどうするか。備忘録。

4Dでは、入力フィールドに特殊なフォントを設定することで実現。

次のようなメソッドを記述する。

 OBJECT SET FONT(vD61_fldPassword;”%password”)

  • オブジェクトに入力された文字は全て「・」で表示される。
  • オブジェクト内での”カット”と”コピー”が無効になる。

• %password オプションは、フィールド、変数、そしてコンボボックス型のオブジェクトに対して使用可能。

フィールドにタイプインされる文字を半角英数文字に限定したいなら、入力フィルターに「&@」と指定する。フィールドに入る前に全角モードになっていても自動的に半角モードになってくれる。「&a」や「&9」でもそうだが、英数字に制限するようなフィルタを設定すると、全角半角の自動切り替えのような動きになる。

4Dアプリ開発ガイドをv18対応に改訂、キーフィールドが必須に

4Dアプリ開発ガイドを改訂。v15対応(以下、旧ガイドと呼ぶ)をv18対応(以下、新ガイドと呼ぶ)にした。

4Dの仕様変更に従って一部内容を変えた。当時の4DはSQLと同様に「レコードのキーフィールドはなくてもいい」という仕様だった。Display selectionとModify recordでレコードを操作する場合はそれでよかった。しかし旧ガイドは、ハンズオンの目標としてリストボックスをダブルクリックするアプリを作る都合上「キーフィールドは必須」だ。そのためキーフィールドに格納するユニークなレコードIDを生成するためにシリアル番号テーブルを実装していた。SQLではよくやる手だ。

v18ではキーとなるIDフィールドの実装が必須になった。SQLとは異なる仕様だ。新ガイドではSQL的な実装をやめて4Dの機能を使うようにした。IDフィールドには、重複不可の属性をつけて自動入力属性でユニークIDを入れるという実装だ。これによりシリアル番号テーブルは不要になった。旧ガイドで実装していたのはシリアル番号テーブルとログインユーザテーブルの2つ。シリアル番号テーブルがなくなると、実装するのは「ログインユーザ」テーブル1つだけになる。これではガイドとして内容が薄いということで、新ガイドではログイン履歴を記録することにした。履歴の保存先としてアクションテーブルを実装。ログインユーザを表示すると、ユーザ名やパスワードとログイン履歴一覧を表示するアプリとした。

この結果として、新ガイドでは「ログインユーザ1件についてログイン履歴がN件あるリレーショナルデータベース」を実装することになり、1対Nのリレーショナル構造を実装するためのテーブル定義と画面表示について説明することになった。

ハンズオンのステップの多くは旧ガイドから流用した。誤字脱字は訂正し、デスマス調をデアル調に変えた。よく使うキーボードショートカットキーを示したり、コラム欄を追加して、なぜそのような実装にしているかの説明を増やした。附録のデバッグの章には、4Dのデバッガでよく使う機能についての説明を追加した。

4D 自力distinct

4D 自力distinctを作ってみたので紹介する。このコマンドはv17、v18で動作確認済み。

4DのDistinctコマンド

4DにはDistinctというコマンドがある。テーブルをクエリーしてセレクションができて、そのテーブルのフィールドの重複を排除したユニークな値の配列を取得できる便利なコマンドだ。

Distinctにはフィールドを一つしか与えることができない。たとえばユニークなコードの配列をください、そのコードに対応した名前の配列(ユニークでないかもしれない)を同時にください、というときは不便。Distinctを実行してもセレクションができるわけではないので、別のフィールドの値を取得するには、得られた配列のユニーク値をもとにしてクエリーし直さなくてはならない。

自力ディスティンクト

そこで「自力ディスティンクト」を作ってみた。ディスティンクトで得られるユニークなフィールドの配列を、データベース側のセットに実現することでセレクションを作る。このモジュールを呼び出すとIDがユニークな配列が返されると同時に、その配列の元になるカレントセレクションが用意される。呼び出し元ではユニークフィールド以外のフィールドの値はSelection to arrayで簡単に持ってこれる。セットを使っているので高速に動く。

このモジュールの特徴は、テーブル名を決め打ちしていることと、ユニークフィールドを整数に限定していること。別のテーブル用とかユニークフィールドが文字列以外の場合はこのモジュールを複製・修正して使う。MVCのモデルメソッドという位置付けである。

//JO_Distinct_byAC_ID
//20200821 wat
//自力Distinct //ジャーナルの勘定科目とカテゴリー
//セレクションは上位で作成しておく
//distinctキーとなるフィールドのポインタは整数、整数の結果配列を返す。

C_POINTER($1;$keyFldPtr)
$keyFldPtr:=$1
C_POINTER($2;$outIDAryPtr)
$outIDAryPtr:=$2 //結果の、IDの配列

C_LONGINT($ac_id)
C_LONGINT($i;$numOfRecs)
C_TEXT($setOrg;$setName)
$setOrg:=”set_org”
CREATE SET([JOURNAL];$setOrg) //最初は全件、評価したら除いていく
$setName:=”result_set”
CREATE EMPTY SET([JOURNAL];$setName) //最初は0件、まだないIDを追加していく

//元セットにレコードがなくなるまで繰り返す
USE SET($setOrg)
$numOfRecs:=Records in selection([JOURNAL])
FIRST RECORD([JOURNAL])
$ac_id:=$keyFldPtr->

While ($numOfRecs>0)
//結果セットを日付でクエリして、なければ追加、
USE SET($setName)
QUERY SELECTION([JOURNAL];$keyFldPtr->=$ac_id)
If (Records in selection([JOURNAL])=0)
//DB全体をクエリして最初のレコードを取得して、セットに追加
QUERY([JOURNAL];$keyFldPtr->=$ac_id)
FIRST RECORD([JOURNAL])
ADD TO SET([JOURNAL];$setName)

End if

//元のセットを日付でクエリして、元のセットからのぞく
USE SET($setOrg)
QUERY SELECTION([JOURNAL];$keyFldPtr->=$ac_id)
For ($i;1;Records in selection([JOURNAL]))
GOTO SELECTED RECORD([JOURNAL];$i)
REMOVE FROM SET([JOURNAL];$setOrg)

End for

//リデュースされた元セットの最初のレコードを取得
USE SET($setOrg)
$numOfRecs:=Records in selection([JOURNAL])
FIRST RECORD([JOURNAL])
$ac_id:=$keyFldPtr->

End while
CLEAR SET($setOrg)

//転送
USE SET($setName)
CLEAR SET($setName)


SELECTION TO ARRAY($keyFldPtr->;$outIDAryPtr->) //カレントセレクションがあるので、呼び出し元ではお好みのフィールドの値をSELECTION TO ARRAYで取得できる

使い方

呼び出し元では次のようにコーディング。この例では元となるセレクションは全件、このように元となるセレクションを作っておいて、そのセレクションの中でユニーク評価をして新たなセレクションを作る。呼び出し後に必要なフィールドの値を転送。

READ ONLY([JOURNAL])
ALL RECORDS([JOURNAL])<;br> JO_Distinct_byAC_ID (->[JOURNAL]JO_D_AC_ID;->$aryAC_ID)
SELECTION TO ARRAY([JOURNAL]JO_D_AC_CODE;$aryAC_CODE)
// SELECTION TO ARRAY(…