前のページ次のページ上に戻るホーム SophiaFramework UNIVERSE 5.3

9.17. メニュー(基礎編)

メニューは、 ルート[SFZRoot]内に配置されるように設計されたレスポンダです。

すべてのメニューは SFYMenu クラスを継承し、 タイマー機能(詳細: SFYMenu::ScheduleTimer 関数の解説)とデフォルトのキー操作機能を提供します。

具象メニューはアプリ開発ですぐに使うことができる部品であり、 抽象メニューはカスタマイズされたユーザー定義メニューを作成するための起点(基底クラス)となります。

表 9.17. 具象メニューの種類

クラス名 解説
SFZTextMenu テキストメニューです。
SFZGridMenu グリッドメニューです。
SFZFlexListMenu フレックスリストメニューです。
[Important] 重要

すべての具象メニューにおいて、 SFYResponder::SetParent 関数、 SFYResponder::SetState 関数、 SFYResponder::SetRealBound 関数の呼び出しは必須です。

このとき、 SFYResponder::SetParent 関数は、 SFYResponder::SetState 関数よりも先に呼び出す必要があります。

その他の関数の呼び出しは省略可能です。 なお、テキストメニュー(SFZTextMenu)のサブメニューについては、 SFZTextMenu::SetItemSubMenu 関数内で親レスポンダ状態実領域が自動設定されるので、 これらの関数を呼び出す必要はありません(呼び出しても無効になります)。

SFYResponder::SetFrame 関数を呼び出してフレームをメニューに装着する場合、 フレームの SFYResponder::SetRealBound 関数を呼び出すと、 メニューの実領域がフレーム余白領域に合わせて自動的に設定されるので、 メニューの SFYResponder::SetRealBound 関数の呼び出しは省略されます。

表 9.18. 抽象メニューの種類

クラス名 解説
SFYMenu メニューを表す抽象クラスです。

9.17.1. テキストメニュー[SFZTextMenu]

図 9.45. 動作例

動作例

SFZTextMenu は、 テキスト形式のメニュー(テキストメニュー)のユーザーインターフェースを提供するクラスです。

テキストメニューには、ページ形式(デフォルト)とスクロール形式の 2 種類があります。

SFZTextMenu クラスは、 テキストメニューのデザイン用に外枠のベベルカラー、 項目とタイトルの色と高さ、 項目領域とテキスト表示領域の余白、フォントなどのプロパティを設定する関数を提供します。

また、項目にテキスト、絵文字、イメージを設定する関数や、 タイトルテキストを設定する関数も提供します。

メニューに項目を追加するには、 SFZTextMenu::Insert / SFZTextMenu::InsertFirst / SFZTextMenu::InsertLast 関数を使います。 メニューから項目を削除するには、 SFZTextMenu::Remove / SFZTextMenu::RemoveFirst / SFZTextMenu::RemoveLast 関数を使います。

SFZTextMenu::Clear 関数を呼び出すと、すべての項目がメニューから削除されます。

選択キーは、 SFYMenu::SetSelectUpKey / SFYMenu::SetSelectDownKey / SFYMenu::SetSelectRightKey / SFYMenu::SetSelectLeftKey 関数を使用して設定します。 デフォルトでは、↑キー、↓キー、→キー、←キーが上下左右の選択キーとして割り当てられています。

デフォルトの実装では、 テキストメニュー[SFZTextMenu]は 下記の結果イベント[SFEVT_RESPONDER_RESULT]を受信します。

開発者は、これらの結果イベントを受信するハンドラを登録できます。

ハンドラを登録しない場合、上記結果イベントを受信するとデフォルトのハンドラが起動されます。 デフォルトのハンドラは、メニューを閉じる処理だけを行います。

操作キーは、SFYMenu::SetOperateKey 関数を使用して設定します。 デフォルトでは、セレクトキーが操作キーとして割り当てられています。

アクセスキーは、SFZTextMenu::SetItemAccessKey 関数を使用して設定します。

ESCAPE キーは、SFYMenu::SetEscapeKey 関数を使用して設定します。 デフォルトでは、クリアキーが ESCAPE キーとして割り当てられています。

[Caution] 注意
選択項目が SFZTextMenu::SetItemEnable 関数により無効化("false" が設定)されているとき、 結果イベントは発生しません。
[Note] タイマーのキャンセル

SFYMenu::ScheduleTimer 関数で設定したタイマー処理は、 操作キーまたは ESCAPE キーを押したときに自動的にキャンセルされます。

サスペンド時もタイマーはキャンセルされますが、 レジューム時に SFYMenu::ScheduleTimer 関数の引数に指定した時間でタイマー処理は再開されます。

メニューの有効状態が無効になったときもタイマーはキャンセルされます。

参照: 結果イベント[SFEVT_RESPONDER_RESULT] | SFZTextMenu::HandleOperateKey | SFZTextMenu::HandleEscapeKey | SFZTextMenu::HandleSelectUpKey | SFZTextMenu::HandleSelectDownKey | SFZTextMenu::HandleSelectRightKey | SFZTextMenu::HandleSelectLeftKey

例 9.80. 宣言

SFMTYPEDEFCLASS(USRApplication)
class USRApplication: public SFYApplication {
    SFMSEALCOPY(USRApplication)
private:
    SFZTextMenuSmp _menu;

    // ...(省略)...
private:
    SFCError Make(Void);

    // 結果イベントを受信するハンドラ
    XANDLER_DECLARE_VOIDRESULT(OnResult)
};

例 9.81. 実装

SFCError USRApplication::Make(Void)
{
    SFXRectangle rectangle;
    SInt16 i;
    SFCError error(SFERR_NO_ERROR);

    if ((_menu = SFZTextMenu::NewInstance(&error)) != null) {
        error = _menu->SetParent(GetThis());
        if (error == SFERR_NO_ERROR) {

            // メニューに結果ハンドラを登録する
            error = _menu->RegisterHandler(
                SFXEventRange(SFEVT_RESPONDER_RESULT, SFEVT_RESPONDER_RESULT, SFP16_BEGIN, SFP16_END),
                XANDLER_INTERNAL(OnResult)
            );
            if (error == SFERR_NO_ERROR) {

                // メニュータイトルを設定する
                // SFXWideString オブジェクトの設定 (※リソースファイルから設定することも可能)
                error = _menu->SetTitle("Text Menu");
                if (error == SFERR_NO_ERROR) {
                    for (i = 0; i < 3; ++i) {

                        // 項目を追加する
                        // SFXWideString オブジェクトの設定 (※リソースファイルから設定することも可能)
                        error = _menu->InsertLast(SFXWideString::Format("Menu item %d", i));

                        if (error != SFERR_NO_ERROR) break;
                    }
                    if (error == SFERR_NO_ERROR) {

                        // メニューを画面中央に配置する

                        // 画面を (10, 10) だけ縮小した領域をヒント領域にしてメニューの最適な横幅を取得し、
                        // ヘッダーと 3 つの項目を表示するための縦幅を設定した領域を画面の中央に配置し、
                        // メニューの実領域として設定する
                        _menu->SetRealBound(_menu->GetSuitableBound(GetLocalBound().Deflate(10, 10).SetHeight(_menu->GetTitleHeight() + 3 * _menu->GetItemHeight()), 
                                               SFYResponder::HORIZONTAL_CENTER, 
                                               SFYResponder::VERTICAL_MIDDLE));

                        // メニューの状態を「可視+活性+操作可能+フォーカス」にまとめて設定する
                        _menu->SetState(true, true, true, true);

                        // メニューを最前面に移動する
                        _menu->ToFront();
                    }
                }
            }
        }
    }

    return error;
}

// メニューの結果ハンドラの実装
XANDLER_IMPLEMENT_VOIDRESULT(USRApplication, OnResult, invoker, reason, result)
{
    SFXWideString text;

    // invoker にはメニューが渡される
    // reason には結果イベントの P16 値が渡される

    switch (reason) {

        case SFP16_RESULT_OK:  // 操作キー押下時[ SetItemEnable() により項目が有効化されている必要がある]
            
            // result には選択された項目のインデックスが渡される

            // 選択された項目のテキストを取得する
            text = _menu->GetItemText(result);

            // デバッグ表示する
            TRACE("%S", text.GetCString());

            break;

        case SFP16_RESULT_ESCAPE:  // ESCAPE キー押下時、またはScheduleTimer 関数で設定した時間が経過した時

            // result には 0 が渡される

            // メニューを閉じる
            invoker->Terminate();  
            // "_menu->Terminate();" と記述しても良い

            break;
    }

    return;
}

9.17.2. グリッドメニュー[SFZGridMenu]

図 9.46. 動作例

動作例

SFZGridMenu は、 マトリックス形式のメニュー(グリッドメニュー)のユーザーインターフェースを 提供するクラスです。

グリッドメニューのセルは、すべて同じサイズです。 ローカル領域のサイズと、 行数と列数から自動的に決定されます。

SFZGridMenu クラスは、 グリッドメニューのデザイン用に境界線描画フラグ、 項目とタイトルの色、タイトルの高さ、パディングサイズ、行数と列数、 フォントなどのプロパティを設定する関数を提供します。

また、項目にテキストやイメージを設定する関数や、 タイトルテキストを設定する関数も提供します。

メニューに項目を追加するには、 SFZGridMenu::Insert / SFZGridMenu::InsertFirst / SFZGridMenu::InsertLast 関数を使います。 メニューから項目を削除するには、 SFZGridMenu::Remove / SFZGridMenu::RemoveFirst / SFZGridMenu::RemoveLast 関数を使います。

SFZGridMenu::Clear 関数を呼び出すと、すべての項目がメニューから削除されます。

選択キーは、 SFYMenu::SetSelectUpKey / SFYMenu::SetSelectDownKey / SFYMenu::SetSelectRightKey / SFYMenu::SetSelectLeftKey 関数を使用して設定します。 デフォルトでは、↑キー、↓キー、→キー、←キーが上下左右の選択キーとして割り当てられています。

デフォルトの実装では、 グリッドメニュー[SFZGridMenu]は 下記の結果イベント[SFEVT_RESPONDER_RESULT]を受信します。

開発者は、これらの結果イベントを受信するハンドラを登録できます。

ハンドラを登録しない場合、上記結果イベントを受信するとデフォルトのハンドラが起動されます。 デフォルトのハンドラは、メニューを閉じる処理だけを行います。

操作キーは、SFYMenu::SetOperateKey 関数を使用して設定します。 デフォルトでは、セレクトキーが操作キーとして割り当てられています。

アクセスキーは、SFZGridMenu::SetItemAccessKey 関数を使用して設定します。

ESCAPE キーは、SFYMenu::SetEscapeKey 関数を使用して設定します。 デフォルトでは、クリアキーが ESCAPE キーとして割り当てられています。

[Note] タイマーのキャンセル

SFYMenu::ScheduleTimer 関数で設定したタイマー処理は、 操作キーまたは ESCAPE キーを押したときに自動的にキャンセルされます。

サスペンド時もタイマーはキャンセルされますが、 レジューム時に SFYMenu::ScheduleTimer 関数の引数に指定した時間でタイマー処理は再開されます。

メニューの有効状態が無効になったときもタイマーはキャンセルされます。

参照: 結果イベント[SFEVT_RESPONDER_RESULT] | SFZGridMenu::HandleOperateKey | SFZGridMenu::HandleSelectUpKey | SFZGridMenu::HandleSelectDownKey | SFZGridMenu::HandleSelectRightKey | SFZGridMenu::HandleSelectLeftKey

例 9.82. 宣言

SFMTYPEDEFCLASS(USRApplication)
class USRApplication: public SFYApplication {
    SFMSEALCOPY(USRApplication)
private:
    SFZGridMenuSmp _menu;

    // ...(省略)...
private:
    SFCError Make(Void);

    // 結果イベントを受信するハンドラ
    XANDLER_DECLARE_VOIDRESULT(OnResult)
};

例 9.83. 実装

SFCError USRApplication::Make(Void)
{
    SFBShellSmp shell;
    SFBImageSmp image;
    SFBImageSmp selectedImage;
    SInt16 i;
    SFCError error(SFERR_NO_ERROR);

    if ((_menu = SFZGridMenu::NewInstance(&error)) != null) {
        error = _menu->SetParent(GetThis());
        if (error == SFERR_NO_ERROR) {

            // メニューに結果ハンドラを登録する
            error = _menu->RegisterHandler(
                SFXEventRange(SFEVT_RESPONDER_RESULT, SFEVT_RESPONDER_RESULT, SFP16_BEGIN, SFP16_END),
                XANDLER_INTERNAL(OnResult)
            );
            if (error == SFERR_NO_ERROR) {

                // メニュータイトルを設定する
                // SFXWideString オブジェクトの設定 (※リソースファイルから設定することも可能)
                error = _menu->SetTitle("Text Menu");
                if (error == SFERR_NO_ERROR) {

                    // メニュータイトルのテキストを白色に設定する
                    _menu->SetTitleForeColor(SFXRGBColor(0xFF, 0xFF, 0xFF, 0x00));

                    // メニュータイトルの背景を水色に設定する
                    _menu->SetTitleBackColor(SFXRGBColor(0x00, 0xBF, 0xF3, 0x00));

                    // 行の数と列の数を設定する
                    _menu->SetRowCol(4, 3);

                    // SFBShell インスタンスを取得する
                    shell = SFBShell::GetInstance();
                    if (shell != null) {

                        for (i = 0; i < 12; ++i) {

                            // リソースファイルと ID から項目イメージを取得する
                            image = shell->LoadResImage(NEWWORLD_RES_FILE, IDI_OBJECT_5001 + i);

                            // 選択時の項目イメージを取得する
                            selectedImage = shell->LoadResImage(MENU_RES_FILE, IDI_OBJECT_5001 + i);

                            // 項目を追加する
                            // SFXWideString オブジェクトの設定 (※リソースファイルから設定することも可能)
                            error = _menu->InsertLast(SFXWideString::Format("%d", i), image, selectedImage);

                            if (error != SFERR_NO_ERROR) break;
                        }

                        if (error == SFERR_NO_ERROR) {

                            // 項目名を表示するようにする
                            _menu->SetDrawItemText(true);

                            // メニューの実領域を画面全体に設定する
                            _menu->SetRealBound(GetLocalBound());

                            // メニューの状態を「可視+活性+操作可能+フォーカス」にまとめて設定する
                            _menu->SetState(true, true, true, true);

                            // メニューを最前面に移動する
                            _menu->ToFront();
                        }
                    }
                }
            }
        }
    }

    return error;
}

// メニューの結果ハンドラの実装
XANDLER_IMPLEMENT_VOIDRESULT(USRApplication, OnResult, invoker, reason, result)
{
    // invoker にはメニューが渡される
    // reason には結果イベントの P16 値が渡される

    switch (reason) {

        case SFP16_RESULT_OK:  // 操作キー押下時
            
            // result には選択された項目のインデックスが渡される

            // 選択された項目のインデックスをデバッグ表示する
            TRACE("%d", result);

            break;

        case SFP16_RESULT_ESCAPE:  // ESCAPE キー押下時、またはScheduleTimer 関数で設定した時間が経過した時

            // result には 0 が渡される

            // メニューを閉じる
            invoker->Terminate();  
            // "_menu->Terminate();" と記述しても良い

            break;
    }

    return;
}

9.17.3. メニューを表す抽象クラス[SFYMenu]

SFYMenu は、各種メニューを実装するための起点(基底クラス)となります。

たとえば、 SFZTextMenuSFZGridMenuSFZFlexListMenu などのメニューは、 SFYMenu クラスを継承して実装しています。

SFYMenu は、 一定時間経過後に自動的にメニューを閉じる機能と、 操作キーや選択キー、ESCAPE キーの管理を実装し、 いくつかの仮想関数(ハンドラ)の動作をデフォルトで実装します。

選択キーは、 SFYMenu::SetSelectUpKey / SFYMenu::SetSelectDownKey / SFYMenu::SetSelectRightKey / SFYMenu::SetSelectLeftKey 関数を使用して設定します。 デフォルトでは、それぞれ↑キー、↓キー、→キー、←キーが選択キーとして割り当てられています。

デフォルトの実装では、抽象メニュー[SFYMenu]は 下記の結果イベント[SFEVT_RESPONDER_RESULT]を受信します。

開発者は、これらのイベントを受信するハンドラを登録できます。

ハンドラを登録しない場合、上記結果イベントを受信するとデフォルトのハンドラが起動されます。 デフォルトのハンドラは、メニューを閉じる処理だけを行います。

操作キーは、SFYMenu::SetOperateKey 関数を使用して設定します。 デフォルトでは、セレクトキーが操作キーとして割り当てられています。

ESCAPE キーは、SFYMenu::SetEscapeKey 関数を使用して設定します。 デフォルトでは、クリアキーが ESCAPE キーとして割り当てられています。

[Note] タイマーのキャンセル

SFYMenu::ScheduleTimer 関数で設定したタイマー処理は、 操作キーまたは ESCAPE キーを押したときに自動的にキャンセルされます。

サスペンド時もタイマーはキャンセルされますが、 レジューム時に SFYMenu::ScheduleTimer 関数の引数に指定した時間でタイマー処理は再開されます。

メニューの有効状態が無効になったときもタイマーはキャンセルされます。

参照: 結果イベント[SFEVT_RESPONDER_RESULT] | SFYMenu::HandleOperateKey | SFYMenu::HandleEscapeKey | SFYMenu::HandleSelectUpKey | SFYMenu::HandleSelectDownKey | SFYMenu::HandleSelectRightKey | SFYMenu::HandleSelectLeftKey

SFYMenu を継承するレスポンダでは、 SFYMenu::SFYMenu / SFYWidget::SFYWidget コンストラクタで登録されたハンドラの処理により、 下記のイベントを受信すると、対応する下記の仮想関数(ハンドラ)が最初に呼び出されます。 その後、開発者がレスポンダに登録したハンドラが呼び出されることになります。

[Note] 注意

ハンドラの詳細については、 SFYMenu::SFYMenu / SFYWidget::SFYWidget コンストラクタの解説を参照してください。

[Tip] Tip

ハンドラを登録する手間を省略できるので、 通常、これらのイベント処理は仮想関数をオーバーライドして記述します。

表 9.19. イベント、仮想関数(ハンドラ)とデフォルト動作

イベント 仮想関数(ハンドラ) デフォルトの動作 オーバーライド
SFYMenu::SetOperateKey で設定されたキーの SFEVT_KEY イベント SFYMenu::HandleOperateKey イベントを送信する※1 任意
SFYMenu::SetEscapeKey で設定されたキーの SFEVT_KEY イベント SFYMenu::HandleEscapeKey イベントを送信する※2 任意
SFYMenu::SetSelectUpKey で設定されたキーの SFEVT_KEY イベント SFYMenu::HandleSelectUpKey 任意
SFYMenu::SetSelectDownKey で設定されたキーの SFEVT_KEY イベント SFYMenu::HandleSelectDownKey 任意
SFYMenu::SetSelectRightKey で設定されたキーの SFEVT_KEY イベント SFYMenu::HandleSelectRightKey 任意
SFYMenu::SetSelectLeftKey で設定されたキーの SFEVT_KEY イベント SFYMenu::HandleSelectLeftKey 任意
(SFEVT_RESPONDER_BOUND, SFP16_BOUND_REQUEST) イベント SFYWidget::HandleBoundRequest 推奨
(SFEVT_RESPONDER_BOUND, SFP16_BOUND_OPTIMIZE) イベント SFYWidget::HandleBoundOptimize 推奨
(SFEVT_RESPONDER_BOUND, SFP16_BOUND_REAL) イベント SFYMenu::HandleBoundReal 仮想領域を調整※3 任意
(SFEVT_RESPONDER_BOUND, SFP16_BOUND_VIRTUAL) イベント SFYWidget::HandleBoundVirtual 任意
(SFEVT_RESPONDER_BOUND, SFP16_BOUND_GLOBAL) イベント SFYWidget::HandleBoundGlobal 非推奨[廃止予定]
(SFEVT_RESPONDER_RENDER, SFP16_RENDER_REQUEST) イベント SFYWidget::HandleRenderRequest 任意

※デフォルトの動作にある "−" は何も実装していないことを表す。

[Note] 注釈

※1.SFYResponder::InvokeForward(SFXEvent(SFEVT_RESPONDER_RESULT, SFP16_RESULT_CANCEL, 0), false) を実行します。 SFYMenu::HandleOperateKey 関数を呼び出す直前に、 SFYMenu::ScheduleTimer 関数で設定したタイマー処理をキャンセルする操作が内部的に行われます。

※2.SFYResponder::InvokeForward(SFXEvent(SFEVT_RESPONDER_RESULT, SFP16_RESULT_ESCAPE, 0), false) を実行します。 SFYMenu::HandleEscapeKey 関数を呼び出す直前に、 SFYMenu::ScheduleTimer 関数で設定したタイマー処理をキャンセルする操作が内部的に行われます。

※3.SFYResponder::SetVirtualBound(SFXRectangle(SFXGrid::ZeroInstance(), GetRealBound().GetSize())) を実行します。

以下にユーザー定義メニューを作成するときに最低限必要なコードを示します。

例 9.84. 宣言

SFMTYPEDEFRESPONDER(USRMenu)
class USRMenu: public SFYMenu {
    SFMSEALRESPONDER(USRMenu)
    SFMRESPONDERINSTANTIATETHREE(USRMenu, SFYMenu, SFYWidget, SFYResponder)
public:

    // レスポンダのタイプを定義する
    // 小文字と記号のみからなるタイプは予約されているので使えない
    enum CodeEnum {
        CODE_TYPE = four_char_code('U', 'M', 'N', 'U')
    };
    SFMTYPEDEFTYPE(CodeEnum)

public:
    static USRMenuSmp NewInstance(SFCErrorPtr exception = null);
protected:
    explicit USRMenu(Void) static_throws;
    virtual ~USRMenu(Void);

    // 親クラスで定義されている仮想関数のうち、実装が推奨される仮想関数
    virtual Void HandleOperateKey(Void);
    virtual Void HandleSelectUpKey(Void);
    virtual Void HandleSelectDownKey(Void);
    virtual Void HandleBoundRequest(SFXRectanglePtr rectangle) const;
    virtual Void HandleBoundOptimize(SFXRectanglePtr rectangle) const;
    virtual Void HandleBoundReal(Void);
    virtual Void HandleBoundVirtual(Void);
    virtual Void HandleRenderRequest(SFXGraphicsPtr graphics) const;
};

例 9.85. 実装

USRMenu::USRMenu(Void) static_throws
{
    if (static_try()) {

        // レスポンダのタイプを設定する
        SetType(CODE_TYPE);

        // 初期化処理を記述する
    }
}

USRMenu::~USRMenu(Void)
{
    // 終了処理を記述する
}

USRMenuSmp USRMenu::NewInstance(SFCErrorPtr exception)
{
    return static_pointer_cast<USRMenu>(Factory(::new USRMenu, exception));
}

Void USRMenu::HandleOperateKey(Void)
{
    // 操作キー押下時に必要な処理があれば、ここに記述する
    // 通常、以下のようにイベントを送信する
    // _select 変数は選択されている項目の番号を表すものとする
    InvokeForward(SFXEvent(SFEVT_RESPONDER_RESULT, SFP16_RESULT_OK, _select), false);

    return;
}

Void USRMenu::HandleSelectUpKey(Void)
{
    // 上キー押下時に必要な処理があれば、ここに記述する
    // 例:選択されている項目を 1 つ戻すなど

    return;
}

Void USRMenu::HandleSelectDownKey(Void)
{
    // 下キー押下時に必要な処理があれば、ここに記述する
    // 例:選択されている項目を 1 つ進めるなど

    return;
}

Void USRMenu::HandleBoundRequest(SFXRectanglePtr rectangle) const
{
    // メニューに最適な大きさを計算して rectangle パラメータに設定する
    // この関数内では、rectangle の原点は変更せず、rectangle のサイズだけを設定する(推奨)

    return;
}

Void USRMenu::HandleBoundOptimize(SFXRectanglePtr rectangle) const
{
    // メニューに最適な大きさを rectangle パラメータ内の大きさに
    // 収まるように計算し、rectangle パラメータに設定する
    // この関数内では、rectangle の原点は変更せず、rectangle のサイズだけを設定する(推奨)

    return;
}

Void USRMenu::HandleBoundReal(Void)
{
    // 実領域が変更された場合に再計算が必要なものがあれば、ここに記述する

    return;
}

Void USRMenu::HandleBoundVirtual(Void)
{
    // 仮想領域が変更された場合に再計算が必要なものがあれば、ここに記述する

    return;
}

Void USRMenu::HandleRenderRequest(SFXGraphicsPtr graphics) const
{
    // メニューを描画する

    return;
}