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

3.9. コントロール(応用編)

3.9.1. フレックスリストボックスコントロール

下図のフレックスリストボックスコントロール(SFZFlexListBoxControl) をカスタムコンテナ CustomContainer に作成します。

図 3.32. フレックスリストボックスコントロール

フレックスリストボックスコントロール
[Note] リストボックスコントロール

リストボックスコントロールは、項目のリストから 1 つの項目を選択する機能を提供します。

SFZFlexListBoxControl クラスは、 画像が扱えるなど SFZListBoxControl クラスよりも高機能な仕様になっています。

カスタムコンテナ CustomContainer 内にフレックスリストボックスコントロールを作成します。

例 3.55. フレックスリストボックスコントロールの作成

// CustomContainer クラスの定義
SFMTYPEDEFRESPONDER(CustomContainer)  //  便利な型を生成するマクロ
class CustomContainer : public SFYContainer
{
    SFMSEALRESPONDER(CustomContainer)  // インスタンスのコピーを禁止するマクロ

    // SFYResponder からこのクラスに至るまでの継承順序を指定するマクロ
    SFMRESPONDERINSTANTIATETHREE(CustomContainer, SFYContainer, SFYWidget, SFYResponder)
public:

    // 小文字アルファベットまたは記号 4 文字からなるタイプは予約されているので
    // CustomContainer のタイプを大文字アルファベット('C', 'C', 'T', 'N')で定義する
    enum CodeEnum {
        CODE_TYPE = four_char_code('C', 'C', 'T', 'N')
    };
    SFMTYPEDEFTYPE(CodeEnum)

public:
    static CustomContainerSmp NewInstance(SFCErrorPtr exception = null);

    // *** 太字が追加部分

    // フレックスリストボックスコントロールを作成する
    SFCError Make(Void);

protected:
    explicit CustomContainer(Void) static_throws;
    virtual ~CustomContainer(Void);

    // 描画ハンドラ
    virtual Void HandleRenderRequest(SFXGraphicsPtr graphics) const;

private:

    // フレックスリストボックスコントロールの結果ハンドラ
    XANDLER_DECLARE_VOIDRESULT(OnListResult)
};

// フレックスリストボックスコントロールを作成する
SFCError CustomContainer::Make(Void)
{
    SFZFlexListBoxControlSmp list;
    SFZFlexListBoxControl::ItemRec item;
    SFBShellSmp shell(SFBShell::GetInstance());
    SFBImageSmp normal(shell->LoadResImage("helloworld.bar", IDI_OBJECT_5001));
    SFBImageSmp select(shell->LoadResImage("helloworld.bar", IDI_OBJECT_5002));
    SFCError error(SFERR_NO_ERROR);

    // フレックスリストボックスコントロールを生成する
    if ((list = SFZFlexListBoxControl::NewInstance(&error)) != null) {

        // 親をコンテナに設定する
        if ((error = list->SetParent(GetThis())) == SFERR_NO_ERROR) {

            // フレックスリストボックスコントロールに結果ハンドラを登録する
            error = list->RegisterHandler(
                SFXEventRange(SFEVT_RESPONDER_RESULT, SFEVT_RESPONDER_RESULT, SFP16_BEGIN, SFP16_END),
                XANDLER_INTERNAL(OnListResult)
            );

            if (error == SFERR_NO_ERROR) {

                // 偶数番目の項目の背景色を設定する
                list->SetEvenListColor(SFXRGBColor(0xFF, 0xFF, 0xCC, 0x00));

                // 奇数番目の項目の背景色を設定する
                list->SetOddListColor(SFXRGBColor(0xFF, 0xCC, 0xFF, 0x00));

                // 通常時の水平アライメントを中央揃えにする
                list->SetHorizontalAlign(SFZFlexListBoxControl::NORMAL, SFZFlexListBoxControl::HORIZONTAL_CENTER);

                // 連続スクロールフラグを false に設定する
                list->SetScrollRepeat(false);

                // スクロールバーの幅を設定する
                list->SetScrollBarWidth(5);

                // 選択時は複数行テキスト表示にする
                list->SetToggle(SFZFlexListBoxControl::TOGGLE_MULTIPLE_BODY_TEXT);

                // 選択項目を 1 つ上の項目に移動させる選択キーを←キーに設定する
                list->SetScrollUpKey(AVK_LEFT);

                // 選択項目を 1 つ下の項目に移動させる選択キーを→キーに設定する
                list->SetScrollDownKey(AVK_RIGHT);

                // アクセスキーを有効にする
                list->SetAccessKeyEnable(true);

                // アイテム構造体を利用して項目を追加する
                // テキストだけでも追加できるが画像を利用する場合は、
                // アイテム構造体を利用した方が簡単

                // アイテム構造体に有効フラグを true に設定する
                item.property.enable = true;

                // アイテム構造体にアクセスキーとコマンド ID を設定する
                // (ID は今回使用しない)
                item.property.key = 0;
                item.property.id = SFZFlexListBoxControl::INVALID_COMMAND_ID;

                // アイテム構造体に画像を設定する
                item.normal.body.icon = normal;
                item.select.body.icon = select;

                // 10 個の項目を追加する
                for (SInt32 i = 0;  i < 10; ++i) {

                    // アイテム構造体にテキストを設定する
                    item.normal.header.text.Set(SFXWideString::Format("%02d", i));
                    item.normal.body.text.Set(SFXWideString::Format("List item %02d", i));
                    item.select.body.text.Set(SFXWideString::Format("The item %02d is selected.", i));

                    // アイテム構造体にアクセスキーを設定する
                    item.property.key = AVK_0 + i;

                    // 末尾に挿入
                    if ((error = list->InsertLast(item)) != SFERR_NO_ERROR) {
                        break;
                    }
                }

                if (error == SFERR_NO_ERROR) {

                    // 最適な領域を計算して実領域を設定する
                    list->SetRealBound(list->GetSuitableBound(GetLocalBound().Deflate(10, 10)).SnapCenter(GetLocalBound().GetCenter()));

                    // フレックスリストボックスコントロールの状態を「可視+活性+操作可能+フォーカス」にまとめて設定する
                    list->SetState(true, true, true, true);

                    list->SetCurrentValue(1);

                    // ★★★ 注意 ★★★ 操作可能状態にしてから現在値を設定すること
                }
            }
        }
    }
    return error;
}

フレックスリストボックスコントロールは、 ESCAPE キー[=クリアキー(AVK_CLR)]が押下されると、 結果イベント [SFXEvent(SFEVT_RESPONDER_RESULT, SFP16_RESULT_ESCAPE, 0)] を受信します。

このとき、クリアキー(AVK_CLR)は ItsWindow に配信される前に処理されたことになります。 このままでは、クリアキー(AVK_CLR)を押下しても ウィンドウ ItsWindow は閉じません。

そこで、 ウィンドウ ItsWindow にクリアキー(AVK_CLR)の SFEVT_KEY イベントを送信するフレックスリストボックスコントロールの結果ハンドラを実装します。

例 3.56. フレックスリストボックスコントロールの結果ハンドラの実装

// フレックスリストボックスコントロールの結果ハンドラの実装
XANDLER_IMPLEMENT_VOIDRESULT(CustomContainer, OnListResult, invoker, reason, result)
{
    SFYResponderSmp parent;

    unused(invoker);
    unused(result);
    switch (reason) {

        // ESCAPE キー[=クリアキー(AVK_CLR)]が押下されたときに発生する結果イベント
        case SFP16_RESULT_ESCAPE:

            if ((parent = GetParent()) != null) {

                // コンテナの親(ItsWindow)にクリアキー(AVK_CLR)の SFEVT_KEY イベントを送信する
                // * コンテナの親(ItsWindow)のキーハンドラが前面から(後から登録した順に)呼び出される
                parent->InvokeForward(SFXEvent(SFEVT_KEY, AVK_CLR, 0), false);

            }
            break;
    }
    return;
}
[Note] InvokeBackward / InvokeForward 関数

SFYResponder::InvokeBackward / SFYResponder::InvokeForward 関数は、 イベントをレスポンダへ送信する場合に利用します。 このとき、イベントは送信先レスポンダの子レスポンダには配信されません。

イベントを送信先レスポンダの子レスポンダにも配信する必要があるときは、 SFYResponder::Distribute 関数を利用します。

カスタムコンテナ CustomContainer にフレックスリストボックスコントロールを作成します。

例 3.57. ItsWindow クラスの変更

// ItsWindow クラスからコンテナの Make 関数を呼び出すように変更
SFCError ItsWindow::Make(Void)
{
    CustomContainerSmp container;
    SFZContainerScrollBarControlSmp bar;
    SFCError error(SFERR_NO_ERROR);

    // カスタムコンテナを作成する
    if ((container = CustomContainer::NewInstance(&error)) != null) {

        // カスタムコンテナの親をウィンドウに設定する
        if ((error = container->SetParent(GetThis())) == SFERR_NO_ERROR) {

            // ...(省略)...

            // カスタムコンテナの状態を「可視+活性+操作可能+フォーカス」にまとめて設定する
            container->SetState(true, true, true, true);

            // カスタムコンテナ内にフレックスリストボックスコントロールを作成する
            container->Make();
        }
    }
    if (error == SFERR_NO_ERROR) {

        // コンテナ専用のスクロールバーコントロールを作成する
        if ((bar = SFZContainerScrollBarControl::NewInstance(&error)) != null) {

            // ...(省略)...

        }
    }
    return error;
}

3.9.2. タブコントロールとタブページ

下図のタブコントロール(SFZTabControl)と タブページ(SFZTabPage)を作成します。

図 3.33. タブコントロールとタブページ

タブコントロールとタブページ

タブコントロールのレイアウト

タブページの追加

SFYResponder::SetParent 関数によりタブページの親レスポンダにタブコントロールを設定すると、 タブページはタブコントロールに追加されます。

領域の設定

SFYResponder::SetRealBound 関数による タブコントロールの実領域の設定は必須です。

[Note] 注意

内部処理により、 タブコントロールの仮想領域は、 常に実領域に一致します。設定しても無効です。

[Note] タブコントロールのコンテント領域

タブコントロールのコンテント領域とは、 タブコントロールの領域からヒント領域、タブ領域、ボーダー領域、スクロールバー領域を除いた矩形領域のことです。 タブページの内容が表示される領域であるので、コンテント領域と呼びます。

タブページの実領域は、タブコントロールのコンテント領域に自動設定されます。

また、タブページの仮想領域は、 タブコントロールにすべてのタブページを追加し終えてから一度だけ 明示的に SFYTabControl::AdjustPages 関数を呼び出すことにより行います。 このとき、仮想領域は自動的に計算されて、始点は (0, 0) [タブページの実領域の始点と同じ]、 領域はタブコントロールのコンテント領域とタブページの子レスポンダを含む最小の矩形領域に設定されます。

[Note] 注意

タブページのすべての子レスポンダがタブコントロールのコンテント領域内に配置される場合、 SFYTabControl::AdjustPages 関数の呼び出しは省略できます。

タブページでは、 SetRealBound/SetVirtualBound 関数を呼び出して実領域/仮想領域を設定しても無効になります。

タブコントロール/タブページの状態

タブコントロールのタブ領域やコンテント領域の描画は、 タブページの状態によって異なります。 詳細は、SFZTabControl クラスの解説を参照してください。

[Note] タブページの初期状態

インスタンス生成直後、タブページの状態は「可視+活性+操作可能+非フォーカス」に初期化されます。

この設定で十分なので、タブページの状態設定は省略できます。

タブコントロールのデザイン

タブ領域やヒント領域、ボーダー領域を表示する関数が利用可能です。 詳細は、SFZTabControl / SFZTabPage クラスのリファレンスを参照してください。

タブページの選択

タブページを選択してコンテント領域内に表示するには、 SFYTabControl::FocusPage 関数を呼び出します。

MyWindow と CusomContainer の親クラスを SFZTabPage クラスに変更します。

例 3.58. タブページの定義


// *** 太字が修正部分

// MyWindow クラスの定義
SFMTYPEDEFRESPONDER(MyWindow)  //  便利な型を生成するマクロ
class MyWindow : public SFZTabPage {

    SFMSEALRESPONDER(MyWindow)  // インスタンスのコピーを禁止するマクロ

    // SFYResponder からこのクラスに至るまでの継承順序を指定するマクロ
    SFMRESPONDERINSTANTIATEFOUR(MyWindow, SFZTabPage, SFYContainer, SFYWidget, SFYResponder)

    // ...(省略)...

};

// CustomContainer クラスの定義
SFMTYPEDEFRESPONDER(CustomContainer)  //  便利な型を生成するマクロ
class CustomContainer : public SFZTabPage {

    SFMSEALRESPONDER(CustomContainer)  // インスタンスのコピーを禁止するマクロ

    // SFYResponder からこのクラスに至るまでの継承順序を指定するマクロ
    SFMRESPONDERINSTANTIATEFOUR(CustomContainer, SFZTabPage, SFYContainer, SFYWidget, SFYResponder)

    // ...(省略)...

};

タブコントロールを配置するウィンドウ TabWindow を定義します。

例 3.59. TabWindow クラスの定義

SFMTYPEDEFRESPONDER(TabWindow)  //  便利な型を生成するマクロ
class TabWindow : public SFZWindow {

    SFMSEALRESPONDER(TabWindow)  // インスタンスのコピーを禁止するマクロ

    // SFYResponder からこのクラスに至るまでの継承順序を指定するマクロ
    SFMRESPONDERINSTANTIATEFOUR(TabWindow, SFZWindow, SFYContainer, SFYWidget, SFYResponder)
public:

    // 小文字アルファベットまたは記号 4 文字からなるタイプは予約されているので
    // TabWindow のタイプを大文字アルファベット('T', 'A', 'B', 'W')で定義する
    enum CodeEnum {
        CODE_TYPE = four_char_code('T', 'A', 'B', 'W')
    };
    SFMTYPEDEFTYPE(CodeEnum)

private:

    // タブコントロール
    SFZTabControlSmp _tab;

public:
    static TabWindowSmp NewInstance(SFCErrorPtr exception = null);

    // タブコントロールとタブページを作成する関数
    SFCError Make();

protected:
    explicit TabWindow(Void) static_throws;
    virtual ~TabWindow(Void);
private:

    // キーハンドラ
    XANDLER_DECLARE_BOOLEVENT(OnKey)

};

// コンストラクタ
TabWindow::TabWindow(Void) static_throws
{
    if (static_try()) {

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

        // TabWindow にキーハンドラを登録する
        static_throw(RegisterHandler(
            SFXEventRange(SFEVT_KEY, SFEVT_KEY, SFP16_BEGIN, SFP16_END),
            XANDLER_INTERNAL(OnKey)
        ));
    }
}

// デストラクタ
TabWindow::~TabWindow(Void)
{
}

// TabWindow を作成する
TabWindowSmp TabWindow::NewInstance(SFCErrorPtr exception)
{
    return static_pointer_cast<TabWindow>(Factory(:: new TabWindow, exception));
}

#define COLOR_TAB COLOR_WHITE // タブコントロールの背景色

// TabWindow 上のタブコントロールを作成する関数
SFCError TabWindow::Make()
{
    MyWindowSmp myWindow;
    CustomContainerSmp customContainer;
    SFCError error(SFERR_NO_ERROR);

    // タブコントロールを作成する
    if ((_tab = SFZTabControl::NewInstance(&error)) != null) {

        // タブコントロールの親をウィンドウに設定する
        error = _tab->SetParent(GetThis());

        if (error == SFERR_NO_ERROR) {

            // タブコントロールの各種設定を行う

            // 実領域を設定する
            // ※ 内部処理により仮想領域は常に実領域に一致する
            _tab->SetRealBound(GetLocalBound());

            // 背景色を設定する
            _tab->SetBackgroundColor(COLOR_TAB);

            // 境界線を表示する
            _tab->SetDrawBorder(true);

            // スクロールバーを表示しない
            _tab->SetScrollBarWidth(0);
            _tab->SetScrollBarControl(SFYScrollBarControlSmp::EmptyInstance());

            // タブ数を 2 に設定する
            _tab->SetFieldValue(2);

            // 状態を「可視+活性+操作可能+フォーカス」に設定する
            _tab->SetState(true, true, true, true);

            // タブページを作成する(MyWindow の継承関係の親クラスは SFZTabPage)
            if ((myWindow = MyWindow::NewInstance(&error)) != null) {

                // タブページの各種設定を行う

                // タブページをタブコントロールに追加する
                // ※1. タブページの親をタブコントロールに設定することにより行う
                // ※2. タブページの実領域はコンテント領域に自動設定される
                error = myWindow->SetParent(_tab);

                if (error == SFERR_NO_ERROR) {

                    // タブに表示されるテキスト(タイトルテキスト)を設定する
                    myWindow->SetTitle("Controls");

                    // ヒントテキストを設定する
                    myWindow->SetHint("This is MyWindow.");

                    // 状態を「可視+活性+操作可能+非フォーカス」に設定する
                    // ※ デフォルトと同じ設定なので、この一行は省略可能
                    myWindow->SetState(true, true, true, false);

                    // タブページ内のレスポンダを作成する
                    myWindow->Make();

                    // タブページを作成する(CustomContainer の継承関係の親クラスは SFZTabPage)
                    if ((customContainer = CustomContainer::NewInstance(&error)) != null) {

                        // タブページの各種設定を行う

                        // タブページをタブコントロールに追加する
                        // ※1. タブページの親をタブコントロールに設定することにより行う
                        // ※2. タブページの実領域はコンテント領域に自動設定される
                        error = customContainer->SetParent(_tab);

                        if (error == SFERR_NO_ERROR) {

                            // タブに表示されるテキスト(タイトルテキスト)を設定する
                            customContainer->SetTitle("Container");

                            // ヒントテキストを設定する
                            customContainer->SetHint("This is CustomContainer");

                            // 状態を「可視+活性+操作可能+非フォーカス」に設定する
                            // ※ デフォルトと同じ設定なので、この一行は省略可能
                            // customContainer->SetState(true, true, true, false);

                            // タブページ内のレスポンダを作成する
                            customContainer->Make();

                            // 2 番目に追加されたタブページを選択する(このタブページの内容がコンテント領域に表示される)
                            // ※ タブページでは、ToFront 関数と SetStateFocus 関数の呼び出しは禁止されている
                            _tab->FocusPage(1);

                            // タブコントロールの全タブページについて仮想領域を設定する。
                            // ※1. "仮想領域=タブコントロールのコンテント領域とタブページの子レスポンダを含む最小の矩形領域【始点は実領域の始点と同じ(0, 0)】"となる
                            // ※2. タブページのすべての子レスポンダがコンテント領域内に配置される場合、AdjustPages 関数の呼び出しは省略可能。
                            // ※3. SFYTabControl::AdjustPages 関数は、1 つのタブコントロールにつき 1 回呼び出すだけでよい
                            _tab->AdjustPages();
                        }
                    }
                }
            }
        }
    }
    return error;
}

// TabWindow のキーハンドラの実装
XANDLER_IMPLEMENT_BOOLEVENT(TabWindow, OnKey, invoker, event)
{
    Bool result(false);

    unused(invoker);

    switch (event.GetP16()) {

        case AVK_CLR:  // クリアキーのとき

            // TabWindow を終了する
            Terminate();

            result = true;

            break;
    }

    return result;
}
[Tip] キーハンドラの呼び出し順序

1 つのレスポンダに 2 つ以上のキーハンドラが登録されている場合は、 最後に登録されたキーハンドラから順に呼び出されます。

参照: キーイベント[SFEVT_KEY から SFEVT_KEY_HOOK_RELEASE]

helloworld アプリの初期画面で 4 キー(AVK_4)が押されると、 TabWindow を表示するようにします。

例 3.60. helloworld アプリケーションクラスの定義

// helloworld アプリケーションクラスの定義
SFMTYPEDEFCLASS(helloworld)  //  便利な型を生成するマクロ
class helloworld : public SFYApplication {

    SFMSEALCOPY(helloworld)  // インスタンスのコピーを禁止するマクロ
private:
    MyWindowSmp _myWindow;
    ItsWindowSmp _itsWindow;
    SFZTextMenuSmp _textMenu;

    // *** 太字が追加/修正部分

    // TabWindow のスマートポインタ
    TabWindowSmp _tabWindow;
public:
    static SFCInvokerPtr Factory(Void);
private:
    explicit helloworld(Void) static_throws;
    virtual ~helloworld(Void);
    SFCError MakeMy(Void);
    SFCError MakeMenu(Void);
    SFCError MakeIts(Void);

    // TabWindow 作成
    SFCError MakeTab(Void);

    // ...(省略)...
};

// TabWindow 作成
SFCError helloworld::MakeTab(Void)
{
    SFCError error(SFERR_NO_ERROR);

    // TabWindow を作成する
    if ((_tabWindow = TabWindow::NewInstance(&error)) != null) {

        // TabWindow の親をルートに設定する
        error = _tabWindow->SetParent(GetThis());

        if (error == SFERR_NO_ERROR) {

            // タブウィンドウの実領域をルートのローカル領域に設定する
            _tabWindow->SetRealBound(GetLocalBound());

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

            // タブコントロールやタブページなどを作成する
            _tabWindow->Make();

            // TabWindow を最前面に移動する
            _tabWindow->ToFront();
        }
    }
    return error;
}

// helloworld アプリケーションクラスの描画ハンドラの実装
XANDLER_IMPLEMENT_VOIDRENDER(helloworld, OnRenderRequest, invoker, reason, graphics)
{
    unused(invoker);

    // helloworld アプリケーションクラス(ルート)のローカル領域(携帯電話の画面)の背景は
    // SetBackgroundColor 関数で設定した色(デフォルト: 白色)で塗り潰される

    // テキストを描画する
    graphics->DrawSingleText("Key 1 - Controls", SFXGrid(20, 20), GetLocalBound(), COLOR_BLACK);
    graphics->DrawSingleText("Key 2 - TextMenu", SFXGrid(20, 40), GetLocalBound(), COLOR_BLACK);
    graphics->DrawSingleText("Key 3 - Container", SFXGrid(20, 60), GetLocalBound(), COLOR_BLACK);

    graphics->DrawSingleText("Key 4 - TabControl", SFXGrid(20, 80), GetLocalBound(), COLOR_BLACK);

    return;
}

// helloworld アプリケーションクラスのキーハンドラの実装
XANDLER_IMPLEMENT_BOOLEVENT(helloworld, OnKey, invoker, event)
{
    Bool result(false);
    unused(invoker);

    // アプリケーションクラスに可視状態の子レスポンダが存在しないときだけキー処理をする
    if (this->GetChildFront(true, false, false, false) == null) {

        switch (event.GetP16()) {

            case AVK_SELECT:

                Terminate();
                result = true;
                break;

            case AVK_1:

                TRACE("key 1");
                if (MakeMy() == SFERR_NO_ERROR) {

                    result = true;
                }
                break;

            case AVK_2:

                TRACE("key 2");
                if (MakeMenu() == SFERR_NO_ERROR) {

                    result = true;
                }

                break;

            case AVK_3:

                TRACE("key 3");
                if (MakeIts() == SFERR_NO_ERROR) {

                    result = true;
                }
                break;

            // 4 キーの SFEVT_KEY イベントを受信したとき
            case AVK_4:
                TRACE("key 4");

                // タブコントロールを配置する TabWindow を作成する
                if (MakeTab() == SFERR_NO_ERROR) {

                    result = true;
                }
                break;
        }
    }
    return result;
}

タブページのフォーカス移動キーは←/→キー(AVK_LEFT/AVK_RIGHT)です。 先ほどフレックスリストボックスコントロールの選択項目の移動する選択キーを←/→キー(AVK_LEFT/AVK_RIGHT)に変更したので、 このままではキー設定がバッティングしてしまいます。

そこで、 フレックスリストボックスコントロールがタブページにある場合 つまり、CustomContainer の親がタブコントロールである場合は、 フレックスリストボックスコントロールの選択項目の移動はデフォルトの↑/↓キー(AVK_UP/AVK_DOWN)で行うことにします。

例 3.61. フレックスリストボックスコントロールの選択キーの設定

// フレックスリストボックスコントロールを作成する
SFCError CustomContainer::Make(Void)
{
    SFZFlexListBoxControlSmp list;
    SFZFlexListBoxControl::ItemRec item;
    SFBShellSmp shell(SFBShell::GetInstance());
    SFBImageSmp normal(shell->LoadResImage("helloworld.bar", IDI_OBJECT_5001));
    SFBImageSmp select(shell->LoadResImage("helloworld.bar", IDI_OBJECT_5002));
    SFCError error(SFERR_NO_ERROR);

    // フレックスリストボックスコントロールを生成する
    if ((list = SFZFlexListBoxControl::NewInstance(&error)) != null) {

        // 親を CustomContainer に設定する
        if ((error = list->SetParent(GetThis())) == SFERR_NO_ERROR) {

            // 結果ハンドラをフレックスリストボックスコントロールに登録する
            error = list->RegisterHandler(
                SFXEventRange(SFEVT_RESPONDER_RESULT, SFEVT_RESPONDER_RESULT, SFP16_BEGIN, SFP16_END),
                XANDLER_INTERNAL(OnListResult)
            );

            if (error == SFERR_NO_ERROR) {

                // ...(省略)...

                SFYResponderSmp parent;
                if ((parent = GetParent()) != null) {

                    if (parent->GetType() != SFZTabControl::CODE_TYPE) {

                        // 親がタブコントロールでない場合: 

                        // 選択項目を 1 つ上に移動する選択キーを←キーに設定する
                        list->SetScrollUpKey(AVK_LEFT);

                        // 選択項目を 1 つ下に移動する選択キーを→キーに設定する
                        list->SetScrollDownKey(AVK_RIGHT);
                    }
                    // 親がタブコントロールの場合は、選択キーはデフォルトの↑/↓キー

               }

                // ...(省略)...

            }
        }
    }
    return error;
}

タブページ MyWindow のキーハンドラの処理により、 クリアーキー(AVK_CLR)が押下されると、 MyWindow をタブコントロールから削除し、終了させます。

タブページ CustomContainer については、 フレックスリストボックスコントロールの結果ハンドラの処理により、 クリアーキー(AVK_CLR)が押下されると、 CustomContainer をタブコントロールから削除し終了させます。

例 3.62. タブページの終了

// MyWindow のキーハンドラの実装
XANDLER_IMPLEMENT_BOOLEVENT(MyWindow, OnKey, invoker, event)
{
    Bool result(false);

    unused(invoker);

    switch (event.GetP16()) {
        case AVK_CLR:

            // MyWindow をタブコントロールから削除し閉じる
            Terminate();

            result= true;
            break;
    }
    return result;
}

// フレックスリストボックスコントロールの結果ハンドラの実装
XANDLER_IMPLEMENT_VOIDRESULT(CustomContainer, OnListResult, invoker, reason, result)
{
    SFYResponderSmp parent;

    unused(invoker);
    unused(result);

    switch (reason) {

        case SFP16_RESULT_ESCAPE:
            if ((parent = GetParent()) != null) {

                if (parent->GetType() == SFZTabControl::CODE_TYPE) {
                    // CustomContainer の親がタブコントロールであるとき :

                    // CustomContainer をタブコントロールから削除し閉じる
                    Terminate();

                }

                else {
                    // CustomContainer の親が ItsWindow であるとき: 

                    // クリアキーイベントを ItsWindow に送信する
                    parent->InvokeForward(SFXEvent(SFEVT_KEY, AVK_CLR, 0), false);

                }
            }
            break;
    }
    return;
}

シミュレータでの実行結果は以下の通りです。

  1. 4 キー(AVK_4)を押すと、TabWindow が表示されます。
  2. ←/→キー(AVK_LEFT/AVK_RIGHT)でタブを切り替えます。
  3. クリアキー(AVK_CLR)でタブページを閉じます。 すべてのタブページを閉じた後、 クリアキー(AVK_CLR)を押すと、 TabWindow は閉じます。

図 3.34. 初期ウィンドウ(helloworld)

初期ウィンドウ(helloworld)

図 3.35. TabWindow のページ 1: MyWindow

TabWindow のページ 1: MyWindow

図 3.36. TabWindow のページ 2: CustomContainer

TabWindow のページ 2: CustomContainer

図 3.37. TabWindow: タブページがないとき

TabWindow: タブページがないとき

参照: タブコントロールとタブページ [SFZTabControl]

3.9.3. ソフトキーコントロール

ソフトキーコントロール(SFZSoftKeyControl)に helloworld(ルート)内に作成し、 ソフトキーコントロールに登録されたソフトキーメニューを helloworld、MyWindow、テキストメニュー、ダイアログに関連付けます。

図 3.38. 標準スタイル


標準スタイル

標準スタイルの場合、 3 つのラベルテキストからなるソフトキーメニューが画面の最下行に表示されます。 左からソフトキー 1(AVK_SOFT1)、セレクトキー(AVK_SELECT)、ソフトキー 2(AVK_SOFT2)に対応します。

[Note] ソフトキーコントロール

ソフトキーコントロール(SFZSoftKeyControl)は、 ソフトキー 1 〜 4(AVK_SOFT1〜4)のキーイベント[SFEVT_KEY]をルートの次に受信し、 対応するソフトキーイベント[SFEVT_RESPONDER_SOFTKEY]アクティブレスポンダ(後述)へ送信する機能を提供します。

以下は、実際にソフトキーコントロールを作成し、使用するコードです。

ソフトキーコントロールの親レスポンダはルートに設定し、 実領域は SFYResponder::GetSuitableBound 関数の戻り値に設定します。

例 3.63. ソフトキーコントロールの作成

// helloworld アプリケーションクラスの定義
SFMTYPEDEFCLASS(helloworld)  //  便利な型を生成するマクロ
class helloworld : public SFYApplication {

    SFMSEALCOPY(helloworld)  // インスタンスのコピーを禁止するマクロ
private:
    MyWindowSmp _myWindow;
    ItsWindowSmp _itsWindow;
    SFZTextMenuSmp _textMenu;
    TabWindowSmp _tabWindow;

    // *** 太字が追加部分

    // SFZSoftKeyControl のスマートポインタ
    SFZSoftKeyControlSmp _softkey;

public:
    static SFCInvokerPtr Factory(Void);
private:
    explicit helloworld(Void) static_throws;
    virtual ~helloworld(Void);
    SFCError MakeMy(Void);
    SFCError MakeMenu(Void);
    SFCError MakeIts(Void);
    SFCError MakeTab(Void);
    SFCError MakeColorDialog(UserColorConstRef color);
    Void SetMenuColors(UserColorConstRef color);

    // ソフトキーメニュー用ラベルテキストを登録する関数
    SFCError RegisterText(Void);

    // ソフトキーメニューを作成する関数
    SFCError MakeSoftMenu(UInt32 key);

    // 携帯画面領域からソフトキーコントロールの領域を除いた矩形を取得する関数
    SFXRectangle GetContentBound(Void) const;

    XANDLER_DECLARE_VOIDRENDER(OnRenderRequest)  // 描画ハンドラ
    XANDLER_DECLARE_BOOLEVENT(OnKey)             // キーハンドラ
    XANDLER_DECLARE_VOIDRESULT(OnMenuResult)     // 結果ハンドラ

    // helloworld アプリケーションクラスのソフトキーハンドラ
    XANDLER_DECLARE_VOIDEVENT(OnSoftKey)

    // テキストメニューのソフトキーハンドラ
    XANDLER_DECLARE_VOIDEVENT(OnMenuSoftKey)

    // MyWindow のソフトキーハンドラ
    XANDLER_DECLARE_VOIDEVENT(OnMyWindowSoftKey)
};

enum {

    // ソフトキーメニューに固有の識別子を割り当てる
    KEY_MENU_TOP    = 1,
    KEY_MENU_WINDOW,
    KEY_MENU_TEXTMENU,
    KEY_MENU_DIALOG

};

// コンストラクタ
helloworld::helloworld(Void) static_throws
{
    if (static_try()) {

        static_throw(RegisterHandler(
            SFXEventRange(SFEVT_RESPONDER_RENDER, SFEVT_RESPONDER_RENDER, SFP16_RENDER_REQUEST, SFP16_RENDER_REQUEST),
            XANDLER_INTERNAL(OnRenderRequest)
        ));
        if (static_try()) {

            static_throw(RegisterHandler(
                SFXEventRange(SFEVT_KEY, SFEVT_KEY, SFP16_BEGIN, SFP16_END),
                XANDLER_INTERNAL(OnKey)
            ));
            if (static_try()) {

                // ソフトキーハンドラを登録する
                static_throw(RegisterHandler(
                    SFXEventRange(SFEVT_RESPONDER_SOFTKEY, SFEVT_RESPONDER_SOFTKEY, SFP16_BEGIN, SFP16_END),
                    XANDLER_INTERNAL(OnSoftKey)
                ));
                // ソフトキーコントロールを作成する
                // ※ アプリケーションクラスはソフトキーコントロールを 1 つまで保持できる
                if ((_softkey = SFZSoftKeyControl::NewInstance()) != null) {

                    // ソフトキーコントロールの各種設定を行う

                    // 親レスポンダをルートに設定する
                    // ※1. ソフトキーコントロールの親レスポンダはルートでなければいけない
                    // ※2. 内部処理によりソフトキーコントロールは常に最前面に配置される
                    // ※3. ソフトキーコントロールはソフトキー 1 〜 4 のキーイベントをルートの次に受信する
                    static_throw(_softkey->SetParent(GetThis()));
                    if (static_try()) {
                    
                        // デフォルトのスタイルは『標準スタイル』

                        // GetSuitableBound 関数を使用して実領域を設定する
                        // ★ソフトキーコントロールの GetSuitableBound 関数の仕様
                        // ※1. 引数には何も指定しない(指定しても無視される)
                        // ※2. 最適な始点の位置も自動計算される
                        _softkey->SetRealBound(_softkey->GetSuitableBound());

                        // 状態を「可視+活性+操作不能+非フォーカス」に設定する
                        _softkey->SetState(true, true, false, false);

                        // ソフトキーメニュー用ラベルテキストを登録する
                        static_throw(RegisterSoftText());
                        if (static_try()) {

                            // 初期画面用ソフトキーメニューを作成する
                            static_throw(MakeSoftMenu(KEY_MENU_TOP));
                            if (static_try()) {

                                // ルートにソフトキーメニューを関連付ける
                                static_throw(_softkey->Bind(GetThis(), KEY_MENU_TOP));
                            }
                        }
                    }
                }
                else {

                    static_throw(SFERR_NO_MEMORY);
                }
            }
        }
    }
}
[Caution] 注意事項

ソフトキーコントロールは、 SFYResponder::GetSuitableBound 関数の引数に矩形領域が指定されても無視します。

また、他のレスポンダの SFYResponder::GetSuitableBound 関数は最適なサイズだけを計算しますが、 ソフトキーコントロールでは、 最適なサイズの他に最適な始点も計算します。 従って、実領域の始点を設定する必要はありません。

"Exit", "Select", "Config", "Back" など全てのソフトキーメニュー用ラベルテキストは、 SFZSoftKeyControl::RegisterText 関数を使用してソフトキーコントロールに登録します。

例 3.64. ソフトキーメニュー用ラベルテキストの登録

enum {

    // 個々のラベルテキストに固有の識別子を割り当てる
    KEY_TEXT_EXIT   = 1,
    KEY_TEXT_SELECT,
    KEY_TEXT_CONFIG,
    KEY_TEXT_BACK

};

// ソフトキーコントロールにソフトキー用ラベルテキストを登録する
SFCError helloworld::RegisterSoftText(Void)
{
    SFCError error(SFERR_NO_ERROR);

    // ソフトキーコントロールにラベルテキストを登録する
    // ※ リソースファイルからまとめて登録することも可能
    if ((error = _softkey->RegisterText(KEY_TEXT_EXIT, "Exit")) == SFERR_NO_ERROR) {

        if ((error = _softkey->RegisterText(KEY_TEXT_SELECT, "Select")) == SFERR_NO_ERROR) {

            if ((error = _softkey->RegisterText(KEY_TEXT_CONFIG, "Config")) == SFERR_NO_ERROR) {

                error = _softkey->RegisterText(KEY_TEXT_BACK, "Back");
            }
        }
    }

    return error;
}

ソフトキーメニューは、 以下の MakeSoftMenu 関数を呼び出して作成します。 この関数では、すべての画面についてのソフトキーメニューを作成しています。

例 3.65. ソフトキーメニューの作成

// ソフトキーメニューを作成する
SFCError helloworld::MakeSoftMenu(UInt32 key)
{
    SFCError error(SFERR_NO_ERROR);

    // ソフトキーメニューが作成されていないか確認する
    if (!_softkey->ContainsMenuKey(key)) {

        // ソフトキーメニューを作成する
        if ((error = _softkey->CreateMenu(key)) == SFERR_NO_ERROR) {

            // ソフトキーメニューにラベルテキストを割り当てる
            switch (key) {

                // 初期画面用ソフトキーメニュー
                case KEY_MENU_TOP:

                    // ソフトキー 1 のラベルテキストを "Exit" に設定する
                    _softkey->SetTextKey(key, SFZSoftKeyControl::SOFTKEY_1, KEY_TEXT_EXIT);

                    // ソフトキー 2 のラベルテキストを "Config" に設定する
                    _softkey->SetTextKey(key, SFZSoftKeyControl::SOFTKEY_2, KEY_TEXT_CONFIG);

                    // セレクトキーのラベルテキストは表示しない
                    _softkey->SetEnable(key, SFZSoftKeyControl::SELECT, false);
                    break;

                // MyWindow などのウィンドウ用ソフトキーメニュー
                case KEY_MENU_WINDOW:

                    // ソフトキー 1 のラベルテキストを "Back" に設定する
                    _softkey->SetTextKey(key, SFZSoftKeyControl::SOFTKEY_1, KEY_TEXT_BACK);

                    // ソフトキー 2 のラベルテキストを "Config" に設定する
                    _softkey->SetTextKey(key, SFZSoftKeyControl::SOFTKEY_2, KEY_TEXT_CONFIG);

                    // セレクトキーのラベルテキストを "Select" に設定する
                    _softkey->SetTextKey(key, SFZSoftKeyControl::SELECT, KEY_TEXT_SELECT);
                    break;

                // テキストメニュー用ソフトキーメニュー
                case KEY_MENU_TEXTMENU:

                    // ソフトキー 1 のラベルテキストを "Exit" に設定する
                    _softkey->SetTextKey(key, SFZSoftKeyControl::SOFTKEY_1, KEY_TEXT_EXIT);

                    // ソフトキー 2 のラベルテキストは表示しない
                    _softkey->SetEnable(key, SFZSoftKeyControl::SOFTKEY_2, false);

                    // セレクトキーのラベルテキストを "Select" に設定する
                    _softkey->SetTextKey(key, SFZSoftKeyControl::SELECT, KEY_TEXT_SELECT);
                    break;

                // ダイアログ用ソフトキーメニュー
                case KEY_MENU_DIALOG:

                    // ソフトキー 1 のラベルテキストは表示しない
                    _softkey->SetEnable(key, SFZSoftKeyControl::SOFTKEY_1, false);

                    // ソフトキー 2 のラベルテキストは表示しない
                    _softkey->SetEnable(key, SFZSoftKeyControl::SOFTKEY_2, false);

                    // セレクトキーのラベルテキストを "Select" に設定する
                    _softkey->SetTextKey(key, SFZSoftKeyControl::SELECT, KEY_TEXT_SELECT);
                    break;

                default:
                    error = SFERR_FAILED;

                    // ソフトキーメニューの作成を中止する
                    _softkey->DestroyMenu(key);
                    break;
            }
        }
    }
    return error;
}

ソフトキーコントロールの領域分だけ表示領域が狭くなるので、 それに合わせて MyWindow や テキストメニューの実領域を調整する必要があります。

以下は、携帯画面からソフトキーコントロールの領域分だけ除いた領域を取得する GetContentBound 関数の実装コードです。

例 3.66. ソフトキーコントロールを除いた領域

// 携帯画面からソフトキーコントロールを除いた領域を取得する
SFXRectangle helloworld::GetContentBound(Void) const
{
    SFXRectangle result;

    result.Set(GetLocalBound());
    result.SetBottom(_softkey->GetRealBound().GetTop());
    return result;
}

他のレスポンダ(MyWindow, ItsWindow, TabWindow, テキストメニュー, ダイアログ)についても、 helloworld クラスと同じ処理を行います。

ソフトキーコントロールを配置することにより、 ダイアログ以外のレスポンダの実領域は変化するため、 GetContentBound 関数を用いてそれらの実領域を設定する必要があります。

例 3.67. 他のレスポンダの登録処理

// MyWindow 作成
SFCError helloworld::MakeMy(Void)
{
    SFCError error(SFERR_NO_ERROR);

    if ((_myWindow = MyWindow::NewInstance(&error)) != null) {

        error = _myWindow->SetParent(GetThis());
        if (error == SFERR_NO_ERROR) {

            // ソフトキーハンドラを MyWindow に登録する
            error = _myWindow->RegisterHandler(
                SFXEventRange(SFEVT_RESPONDER_SOFTKEY, SFEVT_RESPONDER_SOFTKEY, SFP16_BEGIN, SFP16_END),
                XANDLER_INTERNAL(OnWindowSoftKey)
            );

            if (error == SFERR_NO_ERROR) {

                // MyWindow の実領域を、コンテント領域(ソフトキーコントロールを除いた領域)から(10, 10)だけ Deflate した領域に設定する
                _myWindow->SetRealBound(GetContentBound().Deflate(10, 10));

                _myWindow->SetState(true, true, true, true);

                // ソフトキーメニューを作成する
                if ((error = MakeSoftMenu(KEY_MENU_WINDOW)) == SFERR_NO_ERROR) {

                    // MyWindow にソフトキーコントロールのソフトキーメニューを関連付ける
                    error = _softkey->Bind(_myWindow, KEY_MENU_WINDOW);

                    if (error == SFERR_NO_ERROR) {

                        _myWindow->ToFront();
                        _myWindow->Make();
                    }
                }
            }
        }
    }
    return error;
}

// ItsWindow 作成
SFCError helloworld::MakeIts(Void)
{
    // ...(省略)...
}

// TabWindow 作成
SFCError helloworld::MakeTab(Void)
{
    // ...(省略)...
}

// テキストメニューを作成する関数
SFCError helloworld::MakeMenu(Void)
{
    SFCError error(SFERR_NO_ERROR);

    if ((_textMenu = SFZTextMenu::NewInstance(&error)) != null) {

        error = _textMenu->SetParent(GetThis());
        if (error == SFERR_NO_ERROR) {

            error = _textMenu->RegisterHandler(
                SFXEventRange(SFEVT_RESPONDER_RESULT, SFEVT_RESPONDER_RESULT, SFP16_BEGIN, SFP16_END),
                XANDLER_INTERNAL(OnMenuResult)
            );
            if (error == SFERR_NO_ERROR) {

                // ソフトキーハンドラをテキストメニューに登録する
                error = _textMenu->RegisterHandler(
                    SFXEventRange(SFEVT_RESPONDER_SOFTKEY, SFEVT_RESPONDER_SOFTKEY, SFP16_BEGIN, SFP16_END),
                    XANDLER_INTERNAL(OnMenuSoftKey)
                );

                if (error == SFERR_NO_ERROR) {

                    // ...(省略)...

                    // テキストメニューの実領域をコンテント領域に設定する
                    _textMenu->SetRealBound(GetContentBound());

                    _textMenu->SetState(true, true, true, true);

                    // ソフトキーメニューを作成する
                    if ((error = MakeSoftMenu(KEY_MENU_TEXTMENU)) == SFERR_NO_ERROR) {

                        // テキストメニューにソフトキーコントロールのソフトキーメニューを関連付ける
                        error = _softkey->Bind(_textMenu, KEY_MENU_TEXTMENU);

                        if (error == SFERR_NO_ERROR) {

                            _textMenu->ToFront();
                        }
                    }
                }
            }
        }
    }
    return error;
}

// メッセージダイアログを作成する関数
SFCError helloworld::MakeColorDialog(UserColorConstRef color)
{
    SFZMessageDialogSmp dlg;
    SFCError error(SFERR_NO_ERROR);

    if ((dlg = SFZMessageDialog::NewInstance(&error)) != null) {

        error = dlg->SetParent(GetThis());
        if (error == SFERR_NO_ERROR) {

            // メッセージダイアログではソフトキーイベントを処理しないので、ハンドラを登録しない

            // ...(省略)...

            dlg->SetState(true, true, true, true);

            // ダイアログ用のソフトキーメニューを作成する
            if ((error = MakeSoftMenu(KEY_MENU_DIALOG)) == SFERR_NO_ERROR) {

                // ダイアログにソフトキーコントロールのソフトキーメニューを関連付ける
                error = _softkey->Bind(dlg, KEY_MENU_DIALOG);

                if (error == SFERR_NO_ERROR) {

                    dlg->ToFront();
                    dlg->ScheduleTimer(500);
                }
            }
        }
    }
    return error;
}

以下の仕様に基づいてレスポンダ毎にソフトキーハンドラの実装を行います。

  1. アプリケーションクラス(ルート)がアクティブなときは、"Exit" でアプリの終了、"Config" でテキストメニューを開きます。
  2. ウィンドウがアクティブなときは、"Back" でウィンドウを終了、"Config" でテキストメニューを開きます。
  3. テキストメニューがアクティブなときは、"Back" でテキストメニューを終了します。
  4. ダイアログがアクティブなときは、ソフトキーの操作はありません。
[Note] アクティブなレスポンダ

ソフトキーコントロールが関連付けされた、 フォーカスを持つレスポンダの中で最前面に配置されたレスポンダはアクティブレスポンダと呼ばれます。

アクティブレスポンダソフトキーイベント[SFEVT_RESPONDER_SOFTKEY]を受信します。

例 3.68. ソフトキーハンドラの実装

// helloworld アプリケーションクラスのソフトキーハンドラの実装
XANDLER_IMPLEMENT_VOIDEVENT(helloworld, OnSoftKey, invoker, event)
{
    unused(invoker);
    switch (event.GetP16()) {
        case AVK_SOFT1:

            // helloworld アプリを終了する
            Terminate();

            break;
        case AVK_SOFT2:

            // テキストメニューを作成する
            MakeMenu();

            break;
    }
    return;
}

// ウィンドウのソフトキーハンドラの実装
XANDLER_IMPLEMENT_VOIDEVENT(helloworld, OnWindowSoftKey, invoker, event)
{
    switch (event.GetP16()) {
        case AVK_SOFT1:

            // ウィンドウを終了する
            invoker->Terminate();

            break;
        case AVK_SOFT2:

            // テキストメニューを作成する
            MakeMenu();

            break;
    }
    return;
}

// テキストメニューのソフトキーハンドラの実装
XANDLER_IMPLEMENT_VOIDEVENT(helloworld, OnMenuSoftKey, invoker, event)
{
    switch (event.GetP16()) {
        case AVK_SOFT1:

            // テキストメニューを終了する
            invoker->Terminate();

            break;
    }
    return;
}

初期画面においセレクトキーで helloworld アプリを終了する必要がなくなったので、 helloworld のキーハンドラを変更しておきます。

例 3.69. キーハンドラ

// アプリケーションクラスのキーハンドラの実装
XANDLER_IMPLEMENT_BOOLEVENT(helloworld, OnKey, invoker, event)
{
    SFYResponderSmp child;
    Bool result(false);
    unused(invoker);

    // アプリケーションクラスに可視状態の子レスポンダが存在しないときだけキー処理をする
    // * ソフトキーコントロールが必ず最前面に来るので、その次から検索するように変更
    if ((child = GetChildForward(1, true, false, false, false)) == null) {

        switch (event.GetP16()) {

            // セレクトキーを処理しないように変更
            #if 0
            case AVK_SELECT:
                Terminate();
                result = true;
                break;
            #endif

            // ...(省略)...

        }
    }
    return result;
}

シミュレータでの実行結果は以下の通りです。

図 3.39. 初期画面

初期画面

図 3.40. MyWindow 画面

MyWindow 画面

図 3.41. TextMenu 画面

TextMenu 画面

図 3.42. Dialog 画面

Dialog 画面

図 3.43. ItsWindow 画面

ItsWindow 画面

図 3.44. TabWindow 画面

TabWindow 画面