スケルトンとは?

いよいよここから、吉里吉里のクラスを生かして、 よりアプリケーションらしいプログラムについて説明していきます。

第一章を熟読し、ある程度理解されている方ならば、ついて来られると思いますが、 残念ながら、ここからも覚えるべき項目が山のようにあります……。 あくまでも、TJSの基本知識は、吉里吉里によるアプリケーションを組む為に必要なモノに過ぎません。 (VC++とAPI(MFC)の関係……と似ています。)

さて、このページで説明するスケルトン、とはプログラムの枠組み、という意味合いです。 これまで、プログラムの実行結果をメッセージ(System.inform)で表示してきましたが、 一般的なアプリケーションにはウィンドウがあったり、ボタンがあったりして、 実にグラフィカルな画面ですよね。

KAG3 紹介スクリプト

ここからは、このような基本的な「画面」の枠組みを作る方法を見ていきます。 ただし、作るのはあくまでも仕組みだけで、特に何をするでもないプログラムです。 今後は、このスケルトンを再利用しつつ、色んなプログラムを作ります

WINDOW

ウィンドウ、とは絵を描くときに使う画板のようなものです。 プログラムは、この画板を作業台として、 画板の上に画像やメニューボタンを配置していくのです。 つまり、基本的に必要不可欠なアイテムと言えます。

そう聞くと難しいように思われますが心配はご無用。 吉里吉里には、このような基本的なアイテムは全てクラスで実装されているのです。 つまり、私たちは用意されたクラス(設計図)を使ってオブジェクト(実物)を作るだけで良いのです。

ウィンドウを管理するクラスは「Window」です。 ウィンドウを作るには、たった二行だけをstartup.tjsに書けば良いです。

var myWindow = new Window();
myWindow.visible = true;

二行目の「visible」はWindowクラスのメンバープロパティで、 ウィンドウの表示、非表示を表します。 値は真偽のどちらかを入れ、真なら表示、偽なら非表示です。 実物を作った直後は非表示になっているので、このプロパティに真を代入して表示させています。

*真偽値について覚えていますか?

WINDOW2

吉里吉里に備わっているWindowクラスは、 メンバープロパティや関数は全て決まっていますので、当然その機能自体を修正する事は出来ません。 従って、このまま使用すると、どうしても実装できない機能が出て来るのです。 なんとか、Windowクラスを改良するいい手段は無いでしょうか?

そこで、クラスの継承を使うのです。

ウィンドウに限らず、ほとんどの機能は吉里吉里のクラスを継承し、 望みのクラスに修正してから使用するのが一般的です。 これにより、実物を作る前に、設計図の段階で好みの機能を実装出来ます。

// Windowクラスを継承する
class MyWindow extends Window
{
    // コンストラクタの設定
    function MyWindow()
    {
        // 基本クラスの初期化関数を呼ぶ
        super.Window();
        visible = true;
    }

    function finalize()
    {
        super.finalize(...);
    }
}
var win = new MyWindow();

新しいクラスMyWindowの初期化関数(コンストラクタ)内で、 自身の表示状態を示すvisibleにtrueを入れています。 これで、このウィンドウは、作られた時点で表示するようになりました。

ウィンドウのサイズ変更

次にウィンドウのサイズを決めます。 KAGの初期設定では横640、縦480になっているので、今回はそれに従う事にします。

サイズを変更するのにはいくつか方法がありますが、 最も簡単なのはメンバー関数「setSize」を使う事です。

setSize( 横幅 , 縦幅 );

これを、初期化関数内に追加します。

// コンストラクタの設定
function MyWindow()
{
    super.Window();
    setSize(640, 480);
    visible = true;
}

タイトルの変更

ウィンドウのタイトルも、もちろん設定出来ます。 タイトルはcaptionというプロパティを変更すると設定されます。

caption = "題名";

// コンストラクタの設定
function MyWindow()
{
    super.Window();
    setSize(640, 480);
    caption = "私のウィンドウ";
    visible = true;
}
sk_window1.png(2848 byte)

最終的にこんな感じのウィンドウが表示されます。

MENU

プログラムを終了する際、右上の×ボタンを押しますが、 一般的にはメニューボタンからも終了できるようになっていますね。 今回は、アプリケーションを終了するためのボタンを実装します。

メニューは、Windowクラスのメンバープロパティ「menu」に追加していきます。 menuは「MenuItemクラス」のオブジェクト(実物)であり、ウィンドウ上部に表示されるメニュー本体です。 つまり、menuの中にメニューを追加すれば、実際に表示されるようになります。

menu.add( 子メニュー )

子メニューについて説明します。
メニューには親子関係が存在し、親をクリックすると子のメニューが開き、 更に、子のメニューを親としてメニューを追加する事も出来ます。

sk_menu1.png(22332 byte)

今回は終了ボタンを作るだけなので、親メニューひとつで良いのですが、 説明のため、システムというメニューを作り、その中に終了というメニューを挿入します。

MENU2

先ほど説明した子メニューの作り方ですが、 まずメニュークラスのオブジェクト(実物)を作ります。 次に、プロパティmenu(メニューオブジェクト)のメンバー関数addによって、 このオブジェクトを追加します。 メニュークラスの名前はMenuItemですから……

new MenuItem( メニューを作成するウィンドウ , 表示名 )

1.作成するウィンドウは、ここでは自分自身ですからthisを指定します。
(thisの復習はこちら)

2.表示名はシステムメニューですから「システム」とでもしておきましょう。

// コンストラクタに追加する
myMenu = new MenuItem(this, "システム(&S)");
menu.add( myMenu );

myMenuという変数名でメニューの実物を作り、それをaddで追加しています。 以降、システムというメニューはmyMenuという名前で参照出来ます。 (myMenuという変数をメンバーとして宣言するのを忘れないでください!)

*表示名の(&S)はショートカットのキーを設定しています。 この例ではALT+Sを押すことで、システムというメニューがアクティブになります(つまり、操作可能になる)
設定しない場合は単純に操作名だけを書いてかまいません。

今度はシステムメニューの中に「終了」メニューを入れたいので、 myMenuの中に子メニューを追加します。

// コンストラクタに追加する
myMenuEnd = new MenuItem(this, "終了(&E)");
myMenu.add( myMenuEnd );

これでメニュー「システム」の中に「終了」を追加できました。 (こちらも、myMenuEndという変数をクラスのメンバーとして宣言してください。)

class MyWindow extends Window
{
    var myMenu; // システムメニュー
    var myMenuEnd; // システムメニュー内の終了メニュー

    // コンストラクタの設定
    function MyWindow()
    {
        // 基本クラスの初期化関数を呼ぶ
        super.Window();

        setSize(640, 480);
        caption = "私のウィンドウ";

        myMenu = new MenuItem(this, "システム(&S)");
        myMenuEnd = new MenuItem(this, "終了(&E)");
        menu.add( myMenu );
        myMenu.add( myMenuEnd );
        visible = true;
    }

    function finalize()
    {
        super.finalize(...);
    }
}
var win = new MyWindow();
sk_window2.png(4048 byte)

メニューが表示されましたが、実際にはまだ機能しません。 機能を実装する前に、イベントという仕組みを知っておく必要があります。

EVENT

吉里吉里は、色々なメッセージを発生しています。 先ほど作ったウィンドウも、何もしていないようで、実は常にバックグラウンドで動いてます。 メッセージは、あまり馴染みが無いようで、あなたにも深く関わっています。

例えばパソコン自身。ウィンドウズの終了時「スタートボタン」「終了オプション」「シャットダウン」 というようなボタンを押して終了しますよね? システム(OS)は常にこれらのメッセージを見張っていて、 スタートボタンが押された!とか終了オプションが選択された!というメッセージを受信し、 対応する処理を行っているのです。

吉里吉里もこれは同様で、メニューボタンが押されたり、 ウィンドウサイズを変えたりすると、特定のメッセージを受信します。 すなわち、これらのメッセージを受けた時の動作をプログラムする事で、 任意の動作をさせる事が可能になる、という事です。

以下はウィンドウクラスで発生するイベントの一例です

sk_event.png(4801 byte)

ACTION

ここからは、実際にメニューボタンが押された際の動作を設計していきますが、 発生したメッセージはどこで受け取ればよいのか、を先に説明します。

クラスMenuItemはクリックすると、自分が属しているウィンドウの「ある関数」に対してイベント発生します。 属しているウィンドウとは……

new MenuItem( メニューを作成するウィンドウ , 表示名 )

ここで指定したウィンドウです。 今回の例ではthisを指定していますので、MyWindowが対象です。

さて、先に述べた「ある関数」とは「action」です。 この関数は特殊で、そのウィンドウクラスが持っている各オブジェクトから発生したイベントを受け取るために存在しています。 ここでaction関数についてリファレンスを見てみましょう。

action メソッドには辞書配列オブジェクトの引数が一つ渡され、ここにイベントの情報が入っています。  辞書配列のメンバのうち、 target はイベントの発生元オブジェクトを表しています。type はイベント名を表しています。

今回動作させたいのは「終了」メニューで、このオブジェクトの名前はmyMenuEndです。
リファレンスによると、メニューがクリックされた時のイベント名は「onClick」です。 よって……targetが「myMenuEnd」で、typeが「onClick」に合致したら、という条件を加えれば良い事が分かります。

ではまず、MyWindowクラスにメンバー関数actionを加えます。 この関数は引数を一つ受け取る、と書かれているので引数も指定します。

function action( ev )
{

}

引数evが発生したイベントの辞書配列です。 引数の名前は何でも良いのですが、evという名称を使うことが通例のようなので従う事にします。

次に、発生イベントが終了メニューをクリックしたのなら……という条件をつける為、 if文で処理を分岐させます。条件を再確認するとtargetが「myMenuEnd」で、typeが「onClick」です

function action(ev)
{
    if (ev.type == "onClick" && ev.target == myMenuEnd)
    {
        // クリックした時に行う処理
    }
}

終了させる命令は、Windowクラスの関数で実装されています。

close()……ウィンドウを閉じる

では、終了メニューを実装したMyWindowの全コードを記しておきます。

class MyWindow extends Window
{
    var myMenu; // システムメニュー
    var myMenuEnd; // システムメニュー内の終了メニュー

    // コンストラクタの設定
    function MyWindow()
    {
        // 基本クラスの初期化関数を呼ぶ
        super.Window();

        setSize(640, 480);
        caption = "私のウィンドウ";

        myMenu = new MenuItem(this, "システム(&S)");
        myMenuEnd = new MenuItem(this, "終了(&E)");
        menu.add( myMenu );
        myMenu.add( myMenuEnd );
        visible = true;
    }

    function finalize()
    {
        // 保持するオブジェクトは後始末で無効化
        invalidate myMenu;
        invalidate myMenuEnd;

        super.finalize(...);
    }

    function action(ev)
    {
        if (ev.type == "onClick" && ev.target == myMenuEnd)
        {
            close();
        }
    }
}
var win = new MyWindow();

実際にシステムをクリックし、終了メニューで閉じることを確認してください。 (ショートカットを設定しているので、ALT+Sを押し、Eキーでも閉じます)

*クラスが保持するオブジェクトは後始末関数で無効化しておく必要がある点に注意してください。 (復習はこちらのページで

FILES

これまで、すべてのプログラムはstartup.tjsに書いてきました。 今までのように小規模なプログラムなら悪い事では無いのですが、 中規模~大規模なプログラムを書くと、全体の見通しが悪くなるため、作業効率が落ちます。

よって、プログラムの共通部分で区切って別のファイルに保存し、 startup.tjsから読み込む、という手法を取るのが一般的です。 共通部分というのは、例えば…

などです。
では、先ほど作ったMyWindowクラスを全てコピーして「MyWindow.tjs」という名前で保存してください。 そして、このファイルをstartup.tjsと同じフォルダに入れます。

MyWindow.tjsの内容

class MyWindow extends Window
{
    var myMenu; // システムメニュー
    var myMenuEnd; // システムメニュー内の終了メニュー

    // コンストラクタの設定
    function MyWindow()
    {
        // 基本クラスの初期化関数を呼ぶ
        super.Window();

        setSize(640, 480);
        caption = "私のウィンドウ";

        myMenu = new MenuItem(this, "システム(&S)");
        myMenuEnd = new MenuItem(this, "終了(&E)");
        menu.add( myMenu );
        myMenu.add( myMenuEnd );
        visible = true;
    }

    function finalize()
    {
        // 保持するオブジェクトは後始末で無効化
        invalidate myMenu;
        invalidate myMenuEnd;

        super.finalize(...);
    }

    function action(ev)
    {
        if (ev.type == "onClick" && ev.target == myMenuEnd)
        {
            close();
        }
    }
}

startup.tjsの内容

Scripts.execStorage("MyWindow.tjs"); var win = new MyWindow();

Scripts.execStorageは指定したストレージをTJS2として実行する関数です。 ストレージシステムやScriptsクラスについてはリファレンスを参照してください。