クラスはオブジェクトの性質や動作を定義しています。画面を構成する部品(ボタン、ウィンドウなど)、印刷される文字及び、処理されるデータの各要素は、すべてクラスにより表現されています。クラスは、プロパティ、メソッド、イベント、定数が定義されています。
オブジェクトはクラスのインスタンスです。クラスにより定義されるメソッドやプロパティをCRSスクリプトで操作することで、オブジェクトの挙動を指示することができます。
プロパティとは、プロパティ名とプロパティ値の組で表現されるオブジェクトの属性で、プロパティの値によりオブジェクトの性質を決定する要素です。プロパティの値を変更する事で、オブジェクトの見えかたや、ユーザの操作に対する反応が変わります。変化の状態はどのプロパティを変えたかによって異なります。
例
Activeプロパティ=$TRUE
↓
Activeプロパティ=$FALSE
プロパティはオブジェクトのクラスに応じた多くの種類があります。またプロパティごとにアクセス制御が行われており、動的に変更ができるもの、読取専用のもの、作成時にしか指定ができないものなどがあります。
メソッド(Method)は一種の関数です。オブジェクトは実行されるメソッドに応じた動作を行います。
Methodの呼び出し例
Form1.DeleteChild(); /* Form1の内容を消去します。 */ Form1.Get("/mainmenu.crs"); /* Form1にmainmenu.crsをロードします */
オブジェクトが何かに反応する場合、イベントが発生します。イベントの種類や発生する契機はクラスにより定義されます。例えば、Buttonクラスの場合、ボタンを押下するとTouchイベントが発生します。イベントは、「"On" + イベント名」の名前をもつFunction(イベントハンドラと呼びます)により捕捉することができ、パラメータにEventオブジェクトが渡されます。イベントハンドラでは、イベントに応じた処理を記述することができます。
: Button btn { X = 10; Y = 10; Width = 120; Height = 30; Function OnTouch(e) { Title = "Clicked!"; } }
クラスに関する操作を行うオペレータとして、instanceofとsuperが用意されています。instanceofは、あるオブジェクトがあるクラスのインスタンスかどうかを確認するために利用します。
: if (object1 instanceof TextBox) { MessageBox("object1はTextBoxです"); } :
この例では、object1がTextBoxオブジェクトか、またはTextBoxを基にして定義したクラスのオブジェクトの場合にメッセージボックスが表示されます。instanceofのクラス判定は派生関係を考慮して行われる点に注意してください。CRSの全てのクラスは、Objectクラスからの派生ですので、object1 instanceof Objectはobject1が何らかのオブジェクトを指している場合、常に真となります。
superオペレータは、Function内だけで利用できるオペレータです。superにより派生元クラスのメソッドを呼び出すことが可能となります。
class ClassA extends String { Function ToString() { return "ClassA : " + super ToString(); } } class ClassB extends ClassA { Function ToString() { return "ClassB : " + super ToString(); } } var A = new ClassA; var B = new ClassB; A.Value = "test"; B.Value = "test"; print(A.ToString(), "\n"); print(B.ToString(), "\n");
実行結果
ClassA : test ClassB : ClassA : test
この例では、B.toString()の呼び出しにより、ClassBで定義されているtoString()が実行され、superによりClassAのtoString()が呼び出されます。ClassAのtoString()ではさらにsuper toString()を実行しているため、StringのtoString()が呼び出されています。
また、superは、具体的なFunction名を指定せずに関数形式で呼び出すことができます。コンストラクタFunctionの中から関数のように呼び出すと、派生元クラスのコンストラクタを呼び出されます。通常のFunction内から呼び出すと、派生元クラスの同名のFunctionを呼び出します。
class MyClassA extends String { Function MyClassA(arg) { ... (A) : } } class MyClassB extends MyClassA { Function MyClassB(arg) { super(arg); ... (B) : } }
この例では、MyClassBのコンストラクタからMyClassAのコンストラクタを呼び出しています。
Biz/Browserに内蔵されているクラスを基にして、別のクラスをCRSプログラムで新しく定義(派生と言います)することができます。新しく定義したクラスは、基のクラスの特性を受け継ぎますが(継承と言います)、新しい特性を定義したり元の特性を変更することができます。
: class クラス名 extends 基底クラス名 { : }
ユーザークラスは、classステートメントを使用して定義します。継承元のクラス(基底クラス)はextendsキーワードで指定します。
: class CustCode extends TextBox { Width = 120; Height = 24; MaxLength = 12; FontSize = 12; FontKind = $BOLD; Function OnGetFocus(e) { BgColor = $YELLOW; } Function OnLostFocus(e) { BgColor = $STD; } }
この例では、TextBoxを基にしてCustCodeクラスを定義しています。CustCodeクラス固有の動作として、フォーカスを受けると黄色くなり、フォーカスを失うと元に戻るTextBoxを実現することができます。
アプリケーション画面を作成する場合、画面ごとに入力欄を個別に定義するよりも、このように入力項目に対応したクラスを定義して、画面にはそのオブジェクトを配置したほうが開発効率とメンテナンス性が向上します。
CustCodeクラスの利用例
: Form form1 { : CustCode cd1 { X = 10; Y = 10; } : } :
クラス定義内に記述されたファンクションは、メソッドのように動作します。
クラスに定義されるファンクションは、定義するクラス単位に管理されます。例えば、クラスAから派生したクラスBの両方にファンクションFが定義されている場合、クラスAのファンクションFとクラスBのファンクションFは別のものとして共存します。オブジェクトから同じ名前で定義されている、こうしたファンクションを呼び出すと、クラス階層上でもっとも上位に定義されたクラスのファンクションが実行されます。
class ClassA extends String { Function FunctionF() { return "ClassA : " + Value; } } class ClassB extends ClassA { Function FunctionF() { return "ClassB : " + Value; } } var A = new ClassA; var B = new ClassB; A.Value = "test"; B.Value = "test"; print(A.FunctionF(), "\n"); print(B.FunctionF(), "\n");
実行結果
ClassA : test ClassB : test
このように最上位のファンクションが呼び出される動作は、呼び出し元のスクリプトの位置には関係なく、オブジェクトのクラス階層のみにより決定されます。
class ClassA extends String { Function PrintFunc(text) { ... (A) print("ClassA.PrintFunc : ", text, "\n"); } Function FunctionA() { ... (B) PrintFunc("ClassA : " + value); } } class ClassB extends ClassA { Function PrintFunc(text) { ... (C) print("ClassB.PrintFunc : ", text, "\n"); } Function FunctionB() { ... (D) PrintFunc("ClassB : " + value); } } var C = new ClassB("test"); C.FunctionA(); ... (X) C.FunctionB(); ... (Y)
実行結果
ClassB.PrintFunc : ClassA : test ClassB.PrintFunc : ClassB : test
この例では、(X)のFunctionAの呼び出しにより、(B)と(C)のファンクションが実行されます。(Y)のFunctionBの呼び出しでは、(D)と(C)のファンクションが実行されます。これは、ClassAを派生させたClassBによりClassAのPrintFuncを差し替えるように動作します。また、次のようにClassBを派生させてClassCを定義してPrintFuncを再定義すると、さらに別の動作を与えることができます。
class ClassC extends ClassB { Function PrintFunc(text) { //.MessageBox("ClassC.PrintFunc : " + text); } } var C = new ClassC("test"); C.FunctionA();
実行結果
全てのクラスは、コンストラクタと呼ばれるクラス名と同じ名前を持った初期化専用のメソッドを持っています。コンストラクタは、クラスのインスタンス生成時に自動的に呼び出されます。
コンストラクタがどのような動作を行うか、また、どのような引数を採るかはクラスにより異なります。例えば、Stringクラスのコンストラクタに文字列を渡すと、その文字列でStringオブジェクトが初期化されます。
var v = new String("test"); print(v, "\n");
実行結果
test
オブジェクトの初期化以外のコンテキストでコンストラクタを呼び出すことはできません。例えば以下のようなスクリプトはエラーとなります。
var v = new String(); v.String("test"); print(v, "\n");
クラスを定義する際、コンストラクタは通常のファンクションと同じように定義しますが、ファンクション名をクラス名と同じ名前にします。
コンストラクタ定義の例
class MyString extends String { Function MyString(text) { super(text); value = ">>" + Value + "<<"; } }
コンストラクタを定義しない場合、派生元クラスのコンストラクタと同じコンストラクタが定義されたように振舞います。
class MyString extends String { Function MyString(text) { ... (A) super(text); value = ">>" + Value + "<<"; } } class MyString2 extends MyString { : (コンストラクタ ファンクション未定義) : } : var c = new MyString2("test"); ... (B)
この例の場合、(B)のnewで呼び出されるコンストラクタは(A)となります。
コンストラクタの引数はnew演算子によるオブジェクトが生成と、super()ステートメントによる派生元クラスのコンストラクタ呼び出しで指定することができます。
var v = new MyString("hello");
コンストラクタが呼び出された場合、必ず派生元クラスのコンストラクタをsuper()ステートメントにより呼び出す必要があります。
class MyString extends String { Function MyString(text) { super(text); value = ">>" + Value + "<<"; } }
通常は、コンストラクタの先頭でsuper()を実行するようにしてください。コンストラクタにsuperステートメントが含まれない場合、自動的に引数なしでsuperを呼び出す処理が先頭に挿入されます。
superステートメントを含むコンストラクタが、例外や制御文により派生元のコンストラクタを呼び出さずに復帰した場合、オブジェクトの生成を中断し初期化途中のオブジェクトは破棄され、例外がthrowされます。
パッケージとクラス定義内のスクリプトの実行契機と変数スコープが通常のCRSスクリプトは異なることに注意してください。
01: package Package1 { 02: Number Pdata1 = 10; 03: String Pdata2 = "sample " + str(Pdata1); 04: class MyClass extends String { 05: MyData Cdata1 = Package1.Pdata1; 06: String Cdata2 = "sample " + str(Cdata1); 07: Function MyClass(t, d1, d2) { 08: super(t); 09: Cdata1 = d1; 10: Cdata2 = d2; 11: } 12: Function FunctionA() { 13: print(value, Package1.Pdata1, Cdata1, Cdata2, "\n"); 14: } 15: } 16: class MyClass2 extends MyClass { 17: Number Cdata3 = 20; 18: Function MyClass2(t) { 19: super(t, 20, "hello"); 20: } 21: } 22: }
このようなパッケージをimportした場合、次のように動作します。
パッケージ内のオブジェクト定義部分 (2行目から3行目)
パッケージのロード時に実行されます。この部分の変数スコープはPackage1です。従って、Pdata2の初期値の設定にPdata1を使う事ができます。
クラス定義部分( 4行目から22行目)
パッケージのロード時点ではコンパイルとクラス名の登録が行われます。実行は行われません。クラス定義内に文法的なエラーがある場合にはロード時に検出されますが、更新するオブジェクトが見つからないなどの実行時エラーは、MyClassのインスタンス生成時まで検出されないことに注意してください。
5行目でMyDateという未定義のクラスが使用されていますが、コンパイル時にはクラスの有効性は検査されないため、MyClassの最初のインスタンス生成時までにMyDataクラスが定義されればエラーとはなりません。
この部分の変数スコープは、クラスのインスタンスとなります。従って、インスタンス生成の状況により、オブジェクトツリーに関連するスコープが変化することに注意してください。13行目で2行目で定義されているPdata1の参照を期待していますが、実際にどのオブジェクトが参照されるかは実行時の状況により不定となります。
/* パッケージ名と同じ名前のオブジェクトの影響2 */ String Package1 { String Pdata1 = "ABC"; MyClass C; } : Package1.C.FunctionA();
この場合、FunctionAを実行中のthisはPackage1.Cとなり、FuntionA内からPackage1を参照すると、パッケージ名のPackage1ではなく、StringのPackage1が参照されます。その結果、Pdata1は、2行目で定義されたNumberオブジェクトではなく、StringのPdata1が参照されます。
また、次の場合、
/* パッケージ名と同じ名前のオブジェクトの影響2 */ String foo { MyClass C { String Package1; } } : foo.C.FunctionA();
変数スコープの原点であるCの直下にあるPackage1が検索ルールに従って優先的に検索されるため、Stringのfoo.C.Package1.Pdata1が参照されますが、未定義となります。
このように、クラス定義内からクラス外部のオブジェクトを参照する場合、スコープ原点がインスタンスになる点に特に注意が必要です。パッケージ名よりも子オブジェクトや先祖のオブジェクトの検索が優先される仕様により、今回の例で期待したようにMyClass.FunctionAから、Package1.Pdata1を確実に参照する方法はありません。
パッケージに定義されているクラス、MyClass2のインスタンスを生成する場合、以下のように実行されます。
1.クラスのブレース内にインラインで記述された部分が、派生元から派生先 (String→MyClass→MyClass2)の順に実行されます。
5行目: MyNumberオブジェクトが生成され、10に初期化されます
6行目: Stringオブジェクトが生成され、"Sample 10"に初期化されます
7行目: MyClassのコンストラクタが定義されます。まだ実行は行われません。
12行目: FunctionAが定義されます。
17行目: Numberオブジェクトが生成され、20に初期化されます。
18行目: MyClass2のコンストラクタが定義されます。まだ実行は行われません。
2.派生先から派生元 (MyClass2→MyClass→String)の順にコンストラクタを検索し、最初に見つかったコンストラクタが呼び出されます。
18行目: MyClass2のコンストラクタが実行されます。
7行目: 19行目のsuper()により、MyClassのコンストラクタが実行されます。
---: 8行目のsuper()によりStringのコンストラクタが実行されます。
以下、呼び出しシーケンス順に復帰します。
クラス定義は必ずパッケージの中に定義する必要があります。パッケージに属さないクラスを定義することはできません。
クラス名は、Biz/Browserにロードされる全てのクラスで一意な名前にする必要があります。現在のCRSでは異なるパッケージであっても重複したクラス名を使用することはできません。また、パッケージ名によるクラス名の修飾にも対応していませんので、クラス名の命名には重複がないように注意してください。
また、クラスはBiz/Browserにロードされると、セッションの切断まで解放されることはありません。この点からもクラス名の命名は、コーディング規約等で重複やオブジェクト名とのバッティングを起こさないようにご注意ください。
クラスは必ず既存のクラスを基にした派生として定義する必要があります。基本となるクラスが何もないまったく新しいクラスを定義することはできません。CRSにより定義したクラスを更に派生させて新しいクラスを定義することは可能ですが、新しいクラスの定義位置よりも前に、基となるクラスが定義されている必要があります。
現在のCRSでは、クラス定義に多くの制約があります。制約の多くは内蔵されているクラスの挙動とスクリプト定義のクラスの挙動の違いによるものです。クラスを利用する場合、これらの制約を考慮して設計する必要があります。
新しく定義したクラスは、配列として利用することはできません。配列として利用する必要がある場合、Arrayオブジェクトに格納するようにしてください。この制約は、CheckBoxクラス(チェックボックスを表示するクラス)の選択肢として使用する、CheckItemクラスなど、配列化が強制されているクラスを派生して新しいクラスを定義しても実際には利用できないことを意味します。
新しく定義するクラスに、内蔵されているクラスと同じように動作するメソッドを定義することはできません。
Functionを定義することで、ほぼメソッドと同じように機能させることは可能ですが、メソッドと異なり再計算式の中で利用したり、引数の数を規制したりすることはできません。また、内蔵されているクラス間の連携で呼び出されるメソッド(例えば、xmlDocument.load()メソッドは、引数で渡されるオブジェクトのread()メソッドを呼び出します)をFunctionで置き換えることはできません。
新しく定義するクラスに、内蔵されているクラスと同じように動作するプロパティを定義することはできません。
子オブジェクトを定義することで、ほぼプロパティと同じように動作しますが、細部の動作が異なります。
プロパティはアクセス制御が行われますが、子オブジェクトは行われません。プロパティは削除不可能ですが、子オブジェクトは削除可能です。また、プロパティ設定によりオブジェクトの動作が起動するものがあります(ListBoxのValueプロパティのセットで選択位置が変り、ListItemの対応するSelectedプロパティが更新されるなど)が、子オブジェクトでこうした挙動を表現することはできません。
新しく定義するクラスに、内蔵されているクラスと同じように動作するイベントを定義することはできません。
ユーザ定義イベントを発行することは可能ですが、内蔵しているイベントと異なりBiz/Designerのイベントビューに表示されることはありません。
新しく定義するクラスに、静的メソッド(Math.abs()のように、インスタンスを必要としないメソッド)や、クラス定数(FileSystem.OPEN_READなど)を定義することはできません。これらに代わりパッケージ内のオブジェクト定義を利用するようにしてください。パッケージ内のFunctionはパッケージ名.ファンクション名で呼び出し可能で、オブジェクトは、パッケージ名.オブジェクト名で参照できます。
クラス定義内のFunctionはクラス別に管理されますが、子オブジェクトをクラス別に管理することはできません。