次のページ 前のページ 目次へ

14. オンラインヘルプを作る

時間計測もできたところでハイスコアの登録・・・といきたいところですが、 その前にダイアログの処理例ということで、 オンラインヘルプ機能をつけてみましょう。

14.1 ダイアログの概要

ダイアログというのは、 主にユーザーとの対話を目的としたウィンドウ(画面)のことです。 それなら、わざわざフォームと別にせず一緒でも良さそうなものですが、 次のような理由から独立したものとして存在しています。

  1. ユーザーの注意をひきたい(モーダル)
  2. 処理の流れを中断したくない(モーダル)
  3. サブウィンドウを使いたい(モードレス)
  4. フォームを切り替えるほどの物ではない・・・ etc.
ダイアログには、 モーダル・ダイアログとモードレス・ダイアログの2種類がありますが、 前者は表示したダイアログを閉じるまで元の画面は使えないため、 重大な意志確認(削除する/しない等)の場面にうってつけです。 一方モードレスの方は、フォームと一緒に表示され並行して操作できるので、 フォトメモリーの描画色選択など、付加的な操作や情報表示などに向いています。

プログラム的には、 ダイアログの表示を指示した後 (モーダル・ダイアログであれば)それを閉じるまで OS から戻らないため、 現在の処理を抜ける事なく別な画面を表示できるというメリットがあります。 つまりアラートボックスはモーダル・ダイアログの簡易版なんですね。

今回は、フォームを切り替える程でもない・・という消極的理由ですが、 モーダル・ダイアログを使ってオンライン・ヘルプ画面を作ることにします。

14.2 モーダル・ダイアログの使い方

ダイアログ・・・といっても、通常のフォームとそんなに差はありません。 表示したい物を別途フォームとして作成しておき、 呼び出し元の画面に、モーダルであれば MODALDIALOG コントロールを、 モードレスであれば MODELESSDIALOG コントロールを貼り付け、 必要なところで ToolOpenID メッセージを使って表示させるという手順です。

制限事項は、 モーダルの場合ダイアログを開く際に使う MESSAGE 構造体は システムから渡された物をそのまま使う事、 更にその内容をイベント関数を抜けるまで変更してはいけない事、 ダイアログのフォームメソッド内では、AplForm の値が元の物と異なるという事です。

以下に、画面とコードの具体例を挙げます。

【ダイアログに使うフォーム (HelpForm)】

ダイアログ用フォーム 1. 通常と同じようにフォームエディタで新規作成し、各コントロールを配置する (ダイアログが小さい場合、左上に寄せて配置)

2. 通常と同じく必要なイベント関数を記述する (ダイアログ中のAplFormは通常フォームのそれとは異なる事に注意!

3. ダイアログを抜ける際は、 AplForm->IsExit = true; と記述する事 (この例では、[ゲームに戻る]ボタンのイベント関数内に記述)



【呼び出し元のフォーム (GameForm)】

呼び出し元のフォーム 1. モーダルダイアログコントロールを貼り付ける(GameHelpDg)

2. MDDLG_FORM_RES_ID_PID プロパティに、上記ダイアログのリソースID (HelpForm) を指定

3. MDDLG_FORM_RES_NAME_PID プロパティに、リソースファイル名を指定(WST3FM00)

4. GameHelpDgのその他のプロパティで見た目の形や位置を指定(お好みで)



【プログラムからの呼び出し方(説明ボタンのイベント関数)】
void GameHelpBtnBtnSelect(APLFORMPTR AplForm, MESSAGE* Message, const MESSAGE* BtnMsg)
{
    /* 一旦時間計測を止める */
    TSTOP();

    /* ヘルプダイアログの表示(必ずAPMからのMessageを利用) */
    Message->CommonMsg.MessageID = ToolOpenID;
    ControlCall( GameHelpDg, AplForm->Control, Message );

    /* ヘルプダイアログはモーダルなので、終わるまでここで待っている状態 */

    /* 戻ってきたので、時間計測を継続する */
    TCONT();
}
上の例では、TSTOP(), TCONT() がダイアログとは関係ない部分ですが、 このようにモーダル・ダイアログの場合、呼び出してから戻ってくるまで プログラムの実行はこちらから見た場合 ControlCall() の位置で止まっているというのがポイントです。

フォームの遷移となると、上記のように処理の流れの中に入れる事はできず、 どうしても状態の保存・復帰など面倒な作業が必要になりますが、 モーダル・ダイアログを使えばそれらは不要になり、 アラートボックスを呼ぶような感覚で使えるのがありがたいところです。

しかし、現在の状態を保持しているという事は 裏を返せば現在のメモリ消費量に更に上乗せして消費しているので、 メモリを大量に食うダイアログや、 ダイアログから更にダイアログを呼び出すのは避けた方が良いでしょう。

14.3 ヘルプの画面構成と概要

とりあえずダイアログの表示が出来たので、 本題のオンラインヘルプ表示を作ってみましょう。 ここでは単に任意の長さの説明文書(ファイル) を読める程度の簡単な物を考えてみます。

イメージとしてはテキストエディタと縦スクロールバーがあり、 ヘルプ文書は別ファイルに用意しておいて、 ダイアログの表示時にファイルから読み込んで表示するといった感じです。 筆者は上図のように HelpForm というダイアログを作ってみましたが、 こうして構成を考えていくと、 どのイベント関数で何を行えばいいのか大体当たりがついてきますね。:-)

【HelpForm の構成品と必要な処理概要】
HelpVBar
縦スクロールバー。
ダイアログのFormSetUp()で初期化。操作されたらエディタ側の表示もスクロールさせる。
HelpMsg
テキストエディタ。
ダイアログのFormSetUp()で別ファイルを読み込んで表示させておく。
HelpDmy
ユーザーコントロール。
テキストエディタがペンで操作されないように上に被せるダミー。 ただのダミーでも寂しいので、上をタッチされたら上スクロール、 下をタッチされたら下スクロールさせる。
HelpCloseBtn
文字列ボタン
押されたら AplForm->IsExit = true; でダイアログを閉じ、フォームに戻る。
HelpTitleStr
固定文字列
単に【四川省の説明】と表示するのみ。
HelpForm
フォーム部品
FormSetUp() でファイルの読み込みとテキストエディタへの設定を行い、 スクロールバー等の初期設定も行う。 FormKeyExec() でアイゲッティ以降の機種のスクロールボタン、 決定ボタンでの操作も付加してみる。
それでは以下、具体的にやっていきましょう。

14.4 ファイルの読み込み方

まずファイルへのアクセスですが、 ザウルスの場合は大きくわけて3つの方法があります。

1つ目はリソースやDBも扱えるDRFファイルコントロール、 2つ目はC言語でよく使われる fopen() タイプのもの、 3つ目は通信デバイスやフラッシュファイルシステムのみ使える最も低レベルなものです。 今回は、単にヘルプ文書のファイルを読み込むだけですので、 Cの標準ライブラリで十分でしょう。

ところで、オープンするファイル名の指定ですが、 moreソフトが本体/カードのどちらにあっても問題が起きないように、 moreソフトのあるドライブ名を調べてファイル名を作る必要があります ここでは GetAplVolumeName() を用いてドライブ名を得、 それにファイル名を付け足すことで行っています。
    char        fname[APL_DRIVE_NAME_MAX + 14];

        /* まずファイル名作成領域をヌルクリアしておく */
    memset( fname, 0, sizeof(fname) );
        /* 本moreソフトのあるドライブ名を得る */
    GetAplVolumeName( GetMyAplName(), (void *)fname );
        /* ボリューム名付きのヘルプファイル名を作成 */
    strcat( fname, "WST3HELP.ZDT" );
それから、ヘルプファイルの読み込み場所は、 UCHAR buf[2048]; のように固定で用意するのではなく、 stat() でファイルサイズを調べ、 動的に確保する方が柔軟性があって良いでしょう。 以下の例では、NewHeapPtr() で確保していますが、 当然ながらこれも使用が終わったら DisposeHeapPtr()で必ず解放しなければなりません。
    struct stat sbuf;
    char        *hptr;

        /* ヘルプファイルの大きさを得る */
    stat( fname, &sbuf );

        /* st_size だけのヒープを確保。+4 は終端の余裕分 */
    hptr = NewHeapPtr( sbuf.st_size + 4 );
ファイルの読み込みは、 次のように fopen() でファイルを開き、 ファイル識別子を得た上で fread() や fwrite() にて読み書きを行います。 そして全ての操作が終わったら、 fclose() でファイルを閉じてファイル識別子や関連資源の解放を行わねばなりません。 解放処理を忘れると俗に言うメモリリークとなるので十分注意しましょう。
    FILE    *fp;

        /* ヘルプファイルを開く */
    if ( (fp = fopen( fname, "r" )) != NULL ) {
            /* ファイルを読み込む */
        rbytes = fread( hptr, 1, sbuf.st_size, fp );
            /* 終端に NULL 文字を追加(一応気持ちで・・) */
        hptr[rbytes] = '\0';
            /* ファイルを閉じる(必須) */
        fclose( fp );

        /* --- hptr の領域に読み込んだ内容をエディタに設定 --- */

    }

        /* 確保した読み込み領域を解放 */
    DisposeHeapPtr( hptr );

14.5 エディタへの設定

ヘルプファイルの内容をメモリに読み込んだなら、 次は画面に表示させる為にテキストエディタに設定しなければなりません。 これには文字列の先頭アドレスと長さ(バイト数)を指定して、 SetDataIDメッセージを送ることで行います。 これによりエディタ内部領域にテキストがコピーされますので、 設定が終わればファイルの読み込みに使ったメモリを解放できます (というか解放しないとメモリリークの原因となる)。

ところで、テキストエディタのヘルプを見ると、 SetDataID を行った後は自動的にカーソルONになるという事です。 ですので、この例では EditorCursorOffID メッセージを送って、 カーソルを消すようにしています。 クラスライブラリ・リファレンスにはこうした注意事項も書かれてますので、 コントロールを使う場合は必ず確認するようにしましょう。
        /* テキストエディタに書き込む */
    lm.CommonMsg.MessageID	= SetDataID;
    lm.SetData.DataAddress	= (void *)hptr;
    lm.SetData.DataSize		= rbytes;
    ControlCall( HelpMsg, AplForm->Control, &lm );

        /* カーソルをOffにする(注意事項参照) */
    lm.CommonMsg.MessageID = EditorCursorOffID;
    ControlCall( HelpMsg, AplForm->Control, &lm );

14.6 スクロールバーの初期化と使い方

スクロールバーは、 データが多すぎて全て表示できない場合などに用いるコントロールです。

例えば、データが20行あり、実際に表示できるのは8行だけだとした場合、 スクロールが必要なのは 20 - 8 = 12行分 ですので、 全体の長さ=20、表示部の長さ=8、表示開始位置=0〜11 を設定して初期化します。 実際には全体長の設定ToolInitID メッセージによる初期化、 続いて表示長と表示開始位置を設定するという順番になります。

今回の場合は、テキストエディタに入っている内容に従って設定しますので、 一旦 HelpMsgコントロールから各値を得て、 HelpVBarコントロールに設定してあげましょう。
    MESSAGE	lm;
    USHORT	all, disp, offset;

        /* エディタから値を読み取る */
    GetCtrlProperty( HelpMsg, EDIT_ALL_DATA_NO_PID, usValue, all, AplForm->Control, USHORT );
    GetCtrlProperty( HelpMsg, EDIT_DISP_DATA_NO_PID, usValue, disp, AplForm->Control, USHORT );
    GetCtrlProperty( HelpMsg, EDIT_DISP_OFFSET_PID, usValue, offset, AplForm->Control, USHORT );

        /* スクロールバーへ全体長の設定 */
    SetCtrlProperty( HelpVBar, ALL_DATA_NO_PID, usValue, all, AplForm->Control );

        /* スクロールバーの初期化 */
    lm.CommonMsg.MessageID = ToolInitID;
    ControlCall( HelpVBar, AplForm->Control, &lm );

        /* 表示行数と表示開始位置の設定 */
    SetCtrlProperty( HelpVBar, DISP_DATA_NO_PID, usValue, disp, AplForm->Control );
    SetCtrlProperty( HelpVBar, DISP_OFFSET_PID, usValue, offset, AplForm->Control );
このスクロールバーの初期化・設定処理は通常 FormSetUp() 内に書いておき、 後はスクロールバーのイベント関数でテキストエディタとの同期スクロールを実現することになります。

スクロールバーのイベント関数は幾つかありますが、 今回は BarUpdate と、BarButtonStillSelect の2つを使います。 前者はスクロールバーのノブが操作された場合の対応イベントで、 後者はスクロールバー上のボタン操作に対応したものです。 どちらもスクロールバーから値を得てテキストエディタに ScrollUpdateメッセージを送ることで実現します。
void HelpVBarBarUpdate(APLFORMPTR AplForm, MESSAGE* Message,const MESSAGE* BarMsg)
{
    MESSAGE		lm;

        /* エディタをバーに合わせてスクロール */
    lm.CommonMsg.MessageID = ScrollUpdateID;
    lm.ScrollUpdate.AllDataNo = BarMsg->BarUpdate.BarAllDataNo;
    lm.ScrollUpdate.DispDataNo = BarMsg->BarUpdate.BarDispDataNo;
    lm.ScrollUpdate.DispOffset = BarMsg->BarUpdate.BarDispOffset;
    ControlCall( HelpMsg, AplForm->Control, &lm );
}

void HelpVBarBarButtonStillSelect(APLFORMPTR AplForm, MESSAGE* Message, const MESSAGE* BarMsg )
{
    MESSAGE		lm;
    USHORT		all,disp,offset;

    switch( BarMsg->BarButtonStillSelect.ButtonID ) {
        case ScrollDownBtnID:	/* 上向き矢印ボタン */
            lm.CommonMsg.MessageID = ScrollDownID;
            break;
        case ScrollUpBtnID:	/* 下向き矢印ボタン */
            lm.CommonMsg.MessageID = ScrollUpID;
            break;
        case BarPrevSearchBtnID: /* 上向き拡張ボタン */
            lm.CommonMsg.MessageID = PrevPageID;
            break;
        case BarNextSearchBtnID: /* 下向き拡張ボタン */
            lm.CommonMsg.MessageID = NextPageID;
            break;
        default:
            return;
    }

    ControlCall( HelpVBar, AplForm->Control, &lm );

        /* スクロールバーから値を読み取る */
    GetCtrlProperty( HelpVBar, ALL_DATA_NO_PID, usValue, all, AplForm->Control, USHORT );
    GetCtrlProperty( HelpVBar, DISP_DATA_NO_PID, usValue, disp, AplForm->Control, USHORT );
    GetCtrlProperty( HelpVBar, DISP_OFFSET_PID, usValue, offset, AplForm->Control, USHORT );

        /* エディタをバーに合わせてスクロール */
    lm.CommonMsg.MessageID = ScrollUpdateID;
    lm.ScrollUpdate.AllDataNo = all;
    lm.ScrollUpdate.DispDataNo = disp;
    lm.ScrollUpdate.DispOffset = offset;
    ControlCall( HelpMsg, AplForm->Control, &lm );
}

14.7 ユーザーコントロールのタッチによるスクロール

今回はテキストエディタの上に全く同じ大きさのユーザーコントロールを配置し、 不要なペンイベントでエディタが操作されないようにカバーしてあります。 そうなると、ただ単に置いておくのでは勿体ないので、 上の方をタッチしたら逆送り、 下の方をタッチしたら順送りにページ・スクロールさせてみます。

ユーザーコントロールの場合、 その領域内がタッチされたところで AreaPressイベント が起きるので、 引数の AreaMsg->AreaPress.Offset から座標値を得て上半分か下半分かを判定すれば良いでしょう。 この時の注意点としては、渡される座標はこのコントロールの左上からの 相対座標である事です。
void HelpDmyAreaPress(APLFORMPTR AplForm, MESSAGE* Message, const MESSAGE* AreaMsg)
{
    MESSAGE    lm;
    USHORT     all,disp,offset;

    if ( AreaMsg->AreaPress.Offset.y < (186*4)/2 ) {
        lm.CommonMsg.MessageID = PrevPageID;
    }else{
        lm.CommonMsg.MessageID = NextPageID;
    }
        /* スクロールバーを1ページ分動かす */
    ControlCall( HelpVBar, AplForm->Control, &lm );

        /* スクロールバーから値を読み取る */
    GetCtrlProperty( HelpVBar, ALL_DATA_NO_PID, usValue, all, AplForm->Control, USHORT );
    GetCtrlProperty( HelpVBar, DISP_DATA_NO_PID, usValue, disp, AplForm->Control, USHORT );
    GetCtrlProperty( HelpVBar, DISP_OFFSET_PID, usValue, offset, AplForm->Control, USHORT );

        /* エディタをバーに合わせてスクロール */
    lm.CommonMsg.MessageID = ScrollUpdateID;
    lm.ScrollUpdate.AllDataNo = all;
    lm.ScrollUpdate.DispDataNo = disp;
    lm.ScrollUpdate.DispOffset = offset;
    ControlCall( HelpMsg, AplForm->Control, &lm );
}

14.8 アイゲッティ以降のスクロールボタン対応

アイゲッティ以降のザウルスには、スクロールボタンとして十字型のボタンが付いています。 これはソフトウェア的に見ると KeyDownイベント が流れてくるだけですので、 FormKeyExec() 内で受け取って処理します。

処理内容は、どのボタンが押されたかによってスクロールバーの値を更新し、 それに基づいてテキストエディタの方もスクロールさせるというものです。 ここではスクロールボタンの他に、順送り・逆送りキーによるページスクロールや、 決定キーによるヘルプ画面の終了なども組み込んでみました。

void HelpFormKeyExec(APLFORMPTR AplForm, MESSAGE* Message, const MESSAGE* KeyMsg)
{
    MESSAGE    lm;
    USHORT     all,disp,offset;

    switch( KeyMsg->KeyDown.KeyCode ) {
        case KC_NEXT:				/* 順送り */
            lm.CommonMsg.MessageID = NextPageID;
            break;
        case KC_PREV:				/* 逆送り */
            lm.CommonMsg.MessageID = PrevPageID;
            break;
        case KC_CURSOR_UP:			/* 上 */
            lm.CommonMsg.MessageID = ScrollDownID;
            break;
        case KC_CURSOR_DOWN:			/* 下 */
            lm.CommonMsg.MessageID = ScrollUpID;
            break;
        case KC_DECIDE:				/* 決定 */
        case KC_BACK:				/* 戻る */
            AplForm->IsExit = true;
            return;
        case KC_CURSOR_RIGHT:		/* 右向きカーソル */
        case KC_CURSOR_LEFT:		/* 左向きカーソル */
        default:
            return;
    }

        /* !スクロールバーの処理が必要な時だけ下に流す! */
    ControlCall( HelpVBar, AplForm->Control, &lm );

        /* スクロールバーから値を読み取る */
    GetCtrlProperty( HelpVBar, ALL_DATA_NO_PID, usValue, all, AplForm->Control, USHORT );
    GetCtrlProperty( HelpVBar, DISP_DATA_NO_PID, usValue, disp, AplForm->Control, USHORT );
    GetCtrlProperty( HelpVBar, DISP_OFFSET_PID, usValue, offset, AplForm->Control, USHORT );

        /* エディタをバーに合わせてスクロール */
    lm.CommonMsg.MessageID = ScrollUpdateID;
    lm.ScrollUpdate.AllDataNo = all;
    lm.ScrollUpdate.DispDataNo = disp;
    lm.ScrollUpdate.DispOffset = offset;
    ControlCall( HelpMsg, AplForm->Control, &lm );
}
以上、少々長くなりましたが、 これで任意のテキストファイルを表示できるダイアログは完成です。 表示する内容は別ファイル(ここでは WST3HELP.ZDT)にありますので、 他のmoreソフトを作った時にも今のソースは流用していけることと思います。


次のページ 前のページ 目次へ