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

4D 文字列の最初の30バイトまでを切り出す方法は?

文字列を頭からバイト数で切り出す

4D v17

文字数ではなくてバイト数?

もともとコンピュータはバイトの世界、全角も半角も文字数を間違わずに計算してくれているので今は意識することが少なくなった。いまどきバイト数を気にするのはC言語使ってるプログラマが構造体のオフセットを考えている時だけかもしれない。

4D v17には文字列の長さを取得するコマンドはある。Lengthだ。文字数を返す。全角・半角混じりの場合はうまいこと文字数を返す。ただしその文字数が何バイトかわからない。文字列の長さをバイト数で取得したい、ということでコマンドを探してみたら、ない。

そこで文字列のバイト数を返すメソッド「zz_byte_Substring」を作ってみた。ので紹介する。全角半角が混じるので全角文字の途中で切ることをしないとすれば実際は30バイトちょうどにはならない、という仕様はご承知いただきたい。

実装のポイントはバイト数を返すだけのメソッド「zz_byte_GetLen」を別モジュールにしたこと。

//zz_byte_GetLen
//文字列のバイト数を返す
C_TEXT($1;$inStr)
$inStr:=$1
C_LONGINT($0;$sizeOfBlob)
$sizeOfBlob:=$0
C_BLOB($blob)

TEXT TO BLOB($inStr;$blob)
$sizeOfBlob:=BLOB size($blob)

$0:=$sizeOfBlob

このプログラムでバイト数を計算する。BLOB型の変数に代入してBLOB sizeを使った。

図2

zz_byte_GetLenは、(半角文字数 x 1) + (全角文字数 x 2) + 1を返すみたいだ。おそらく最後の1バイトは文字列の終端を示す区切り文字だと思われる。呼び元のメソッドzz_byte_Substringでは、この文字を含めて上限バイト数とする。上限バイト数は引数でもらうようにした。whileループで文字列を一文字ずつ少なくしていって、30byte以下になったところで止めれば良い。

次のようなコード。

//zz_byte_Substring
//20200317 wat
//指定されたバイト数で、文字化けしないように文字列を切る。

C_TEXT($1;$inStr)
$inStr:=$1
C_LONGINT($2;$inByteLen)
$inByteLen:=$2
C_TEXT($0;$outStr)
$outStr:=$inStr
C_LONGINT($byteLen)

//バイト数を取得
$byteLen:=zz_byte_GetLen ($outStr)
$outStr:=$inStr

While ($byteLen>$inByteLen)
//文字を一つ減らして、バイト数を取得
$outStr:=Substring($outStr;1;Length($outStr)-1)
$byteLen:=zz_byte_GetLen ($outStr)

End while
$0:=$outStr

これでバイト数で切り出すメソッド完成。

4D プロジェクトフォームとテーブルフォーム、イベントプロパティが違う

4D プロジェクトフォームとテーブルフォーム、イベントプロパティが違う

DBを集計した結果を画面に表示して、それをそのまま印刷したいと考えた。結構複雑な集計表なので印刷用と2つ作って両方をメンテするのは面倒。複数のテーブルから情報収集しているので、この印刷フォームは特定のテーブルに帰属させたくない、ということでプロジェクトフォームを作った。

4Dのフォームにはテーブルフォームとプロジェクトフォームがあって、前者はテーブルに帰属している、テーブルが削除されると同時に削除される。後者はプロジェクトに帰属していて呼び出し方が異なる。4Dv17

テーブルフォームを開くときはテーブル名とフォーム名を与える:Open form window([Table1];”P01_Print”)

プロジェクトフォームを開くときはフォーム名だけ与える:Open form window(“P01_Print”)

集計結果を画面に表示したところまでは順調。画面表示用なので[印刷]ボタンと[キャンセル]ボタンがある。当然だ。でも印刷時にはこれらのボタンを隠したい。いつものようにフォームメソッドの「On Printing Detail」でObject set visibleしようかな、と。やろうとしたら、

◆ プロジェクトフォームのプロパティに「On Printing Detail」イベントがない!

テーブルフォームじゃないとこれらのイベントは取得できない、らしい。Object set visibleなどのコマンドはフォームオブジェクトがロードされた後でないとコマンドが無効になるので、フォームがロードされた後、印刷が始まる前までに実行したい。選択肢は次の2つ?

1)プロジェクトフォームのままで、「On Load」イベントでボタンを制御

2)テーブルフォームに変更して、「On Printing Detail」を使う

今回のケースでは、複数のテーブルからデータを集計するのだが、カレントセレクションは使わないためどちらのテーブルに帰属させても動きに問題はない。しかしどちらのテーブルにも帰属させたくない。あとでメンテするたびにどちらのテーブルか思い出す必要があったりするので。

とりあえず1)を試す。

◆ Print formコマンドで「On Load」イベントが来ない!

そうかもしれない、今回のケースでは自身のフォームが画面表示用としてすでにロードされているから。これが別のフォームを開く場合だったら[印刷]ボタンクリックのあとで別のフォームがロードされてOn Loadイベントが発生するのに。

ほかに方法もありそうだけど、このあたりで方針変更して、2)に宗旨替え。

で、どのテーブルに帰属させるか。昔プロジェクトフォームがなかった頃はすべてのがテーブルフォームだった。[Z_Dialog]とか[Z_UserInterfae]とかフォームを作るためだけのレコードのないテーブルを作ってた。これをやりたくなかったけど仕方がない。あとで探しやすいように印刷用フォーム専用の[Z_PrintForm]という名前のテーブルを作ることにした。フォームの名前はP01_のようにPで始めたり、PF01_のようにPFで始める。

以上が印刷用フォーム専用テーブル[Z_PrintForm]ができた背景。これは4Dv17の場合であり、将来の4Dでプロジェクトフォームでもテーブルフォームと同様に「On Printing Detail」がサポートされたら[Z_PrintForm]を廃止するか、というとそうでもない。印刷用フォームを1箇所に集めておく意味はありそうだし、これはこれで悪くないと思う、今のところは。


VectorScriptのインクルード その2

VectorWorksプログラミング実行環境で使える言語「VectorScript」の話。インクルードしたいファイル名を絶対パスで指定するか、相対パスで指定するか、という話(VectorScriptのインクルード その1)の続きです。

普通C/C++などのコンパイル型言語では、コンパイル時にコンパイラーが理解できる相対パスでインクルードするのが一般的。ビルドしてしまえばあとは一体化しているのでプログラムを動かす時はインクルードパスを意識する必要はない。

VectorScriptでも、絶対パスでインクルードファイルを指定するのは無しだと思う。相対パスじゃないと別のマシンで読み込めない。ではインクルードファイルを参照可能な場所とはどこか。

ここではVectorWorksフォルダ(Vectorworksアプリのあるフォルダ)にしてみた。図面ファイルの横に置くと、図面を別の場所に保存するとき、VectorScriptのインクルードするソースファイルもコピーしなくてはならない。Vectorworksフォルダならすべての図面ファイルから参照可能。

メインとなるソースはたとえば次のようになる。

{$INCLUDE xxx}で4つのソースファイルをインクルード

{$INCLUDE xxx}で4つのソースファイルをインクルード。次の図のようにVectorWorksフォルダに「JCLVS」フォルダを置き、ソースファイルとテキストファイルを置いておく。

Vectorworks 2015フォルダに「JCLVS」フォルダ

たとえばダイアログを表示する「My_dlg_2Edit1Cbx.pas」の中身は次。

2つの入力フィールドと1つのチェックボックスを指定するためのダイアログボックスを表示するためのプログラムだ。メインのソースの、{$INCLUDE JCLVS\My_dlg_2Edit1Cbx.pas}のところに「My_dlg_2Edit1Cbx.pas」の中身(231行)が展開されて、構文チェックされる。メインプログラムは飛躍的に読みやすくなった。

メインプログラムの13行目、filenameに「JCLVS\default.txt」を代入してダイアログの関数に渡しいてる。外部ファイル「My_dlg_2Edit1Cbx.pas」のソースから引数でファイル名を渡された「JCLVS\default.txt」を読み込むことができていることにも注目してほしい。

このようにVectorWorksフォルダにソースファイルの一部を置いておくことでメインプログラムの可読性が高まる。メインプログラムを作っている時にうっかりサブプログラムに影響を与えることが減って生産性が高まる。変数のスコープ(有効範囲)を意識して、変数名・関数名にネーミングルールを設けた方がいい。これはソースを分割しない場合でも。

ソースが展開されるだけなので、サブプログラムからメインプログラムの変数は参照できるし、インクルードしているサブファイルが2つ以上あって、両方に同じ関数が定義されているとエラーになるので注意が必要だ。C言語の分割ソースとは異なる。

上記の場合で、たとえばMy_Lib.pasの中で共通ライブラリ「JCL_str.pas」の文字列操作関数を使いたいとする。My_Lib.pasに{$INCLUDE JCLVS\JCL_str.pas}を記述すれば、JCL_strを使えるようになる。が、メインプログラムでも{$INCLUDE JCLVS\JCL_str.pas}が記述されていて二重インクルードのエラーになる。この問題についてはまた次回。

4D リストボックスから開くフォームの次前ボタン

4Dのリストボックスから開いたフォームに、「次前(つぎまえ、と読む)」を実装する

リストボックスの選択行の詳細を表示した時、次のレコードに移動するには、一度フォームを閉じてリストボックスに戻り、次の行をダブルクリックなどでレコードを表示する。これではユーザは不便。それでフォームに[次][前]ボタンを配置して、リストボックスに戻らなくても隣のレコードを表示できるように、という要望にお応えするのだ。

実装方法

たとえばvKA01_lstKAというリストボックスがある。キー配列はvKA01_lstKA_IDとすると、次前はこのvKA01_lstKA_IDの配列中から隣のKA_IDを取得して、そのレコードをロードして表示する、ような実装をする。これでリストボックスに表示されているレコードを行ったり来たりする動きになる。

実装箇所

修正が必要なのは次の箇所。

1)フォームに◁▶︎のようなボタンと現在の位置を2/30のような形式で表示するテキストが必要

フォームにオブジェクトを配置、現在の位置はvKA02_varIndexに表示、メッセージはSetControlsValues_PrevNext(後述)で作成する。

2)呼び出し元、たぶんKA01_btnModのようなボタンメソッド

次前しなければ$ka_idは変更されない値だったが、次前を実装するとフォームが閉じた時に変化していることがある。選択されている行番号も同様だ。戻ってきた時、最後に選択されているはずの行の内容を保存したり、リストボックスでその行を選択したりするために、フォームで次前が行われた結果の$ka_idと行番号を返してもらう必要がある。よってこれらはポインタで渡す。引数は次の3つ。

  • $ka_idのポインタ
  • 配列vKA01_lstKA_IDのポインタ
  • 選択されている行番号のポインタ
KA01_btnModの例

3)呼び出される側、KA02_Input_Mod

KA02_Displayする前に、配列のポインタ、行番号、$ka_idをプロセス変数に保持、ゲッターセッターを用意。_DisplayでOKされたら、フォームを表示する前のKA_IDと閉じた時のKA_IDは次前で変化している可能性があるため、Acceptの際にゲッターでKA_IDを取得してからレコードを保存する。リストを再作成してから、変化した行番号をゲッターで取得してリストボックスの行を選択し直す。

KA02_Input_Modの例

4)一番前に来たら前ボタン、一番後ろに来たら次ボタンをディスエイブルにするために、SetControlsValuesにSetControlsValues_PrevNextを追加

SetControlsValuesの例

5)次前ボタン(KA02_brnNext)

次前の際に、レコードを保存するかどうかは設計が必要だ。次のようなケースがある。

  • A)問答無用で保存(いわゆる自動保存)
  • B)問答無用でキャンセル(保存しない、変更内容は失われる)
  • C)変更していたら自動保存、変更していなければ保存しない
  • D)変更していたらアラートでお知らせしてから保存、変更していなければアラートせずに保存しない

理想を言えばDだが、Aでも成立するケースは多い。ユーザからの強い要望でBというケースもあったので決めつけることのないように。次の実装はAの自動保存。

KA02_brnNextの例、KA02_brnPrevは24、26行目の「+1」を「-1」に変えるだけ。

ネーミングおよびJCL4Dについては「4Dアプリ開発ガイド」を参照。

VectorScriptのインクルード その1

知ってましたか?インクルード。VectorScriptの実行環境には、複数のソースファイルに分割して記述されたコードをまとめて実行する機能がある。あ、言い換える、ソースコードを複数のソースファイルに分けて書くことができる。

VectorScriptのスクリプトエディタには貼り付けることのできるコード量の上限があって。は、ひとつのプログラムがソースで32kbytesまで、字数にして32,000文字、全角なら16,000文字、A4に1行に50文字くらいだとして、1ページに60行入るとして、1ページに3,000文字、10ページちょっと。ソースだからステップ数(行数)でいうと600ステップ。えっ、そんなに少ないの?これだとすぐに限界がきちゃう。

VectorScriptでそんなに長いプログラムを書くことがあるのか?という疑問もおありでしょうが、いろいろなプログラムを作っているとすぐにぶつかりますね。特にダイアログ関係はアイテム数が多いとぐんぐんコードが増えていく。図面に色々な図形を自動描画させたりすれば、これもまたコードが増える。プログラマの欲望としてプログラムには1度に大量のことをやらせたいのだ。上限なんてとんでもねえ。

もう一つプログラムが長くなるのがライブラリ化。可読性を高めるために、たとえば文字列切り出しなどの小技なプログラムをfunction/procedureに書いておく。一度役に立ったものは、別のプログラムでも使えるかもしれない。どんどんたまっていく。使うところだけ切り離すのは手間。

そこでインクルード(INCLUDE)の登場。言語によってはimportって記述する。ソースコードを分割して記述できる機能で多くの言語に備わっている。VectorScriptでも実装されている。VectorScriptのスクリプトエディタに貼り付けたメインとなるソースの他に、どこかのフォルダにサブとなるソースファイル(テキスト形式)を置いておいて、コンパイル・実行時に読み込んでくれる。

ということは、メインとなるソースは図面ファイルの中にあって、プログラムの一部が外部のソースファイルにある。図面ファイルだけではプログラムが動かない。

外部にソースファイルを置く、ということはファイルのパス名(通り道の意味のpath)、ファイルパスとか呼ばれている。macOSでは「Macintosh HD:アプリケーション:…..」のようなもの、Windowsでは「C:\….」からはじまっている記述方法、が絶対パス、これに対して「../download」のような一つ上の「download」フォルダ、の意味を示す記述方法、相対パスがある。絶対パスで参照するか、相対パスで参照するか、いずれにしてもこのパス名をソースの中に記述することになる。自分のマシンだけでプログラムを動かすのなら絶対パスでもいいけど、別のマシンでもソースを修正することなく動いて欲しい。そう、マシンが変わったらパス名が違うし、フォルダが変わっても絶対パスは変わる。

VectorScriptのインクルード その2」に続く