目次
※ SophiaFramework UNIVERSE 5.1 の SFY レスポンダシステム(新 GUI フレームワーク)については下記 URL のサイトをご覧ください。
SophiaFramework UNIVERSE 5.1 SFY レスポンダシステム(新 GUI フレームワーク)
GUI フレームワークとは
GUI フレームワークとは、ウィンドウ や ボタン、チェックボックス などの GUI コンポーネント を画面上に配置するだけで自動的に イベント の分岐や画面の再描画を行う、GUI フレームワークです。
複雑な イベント の分岐処理を記述したり、GUI コンポーネント を独自に描画したり、GUI コンポーネント の状態を管理する必要はありません。
SophiaFramework UNIVERSE の GUI フレームワーク では、レスポンダ(Responder) と呼ばれる GUI コンポーネント が提供されます。
レスポンダ一覧
■ アプリ
クラス名 | 解説 |
---|---|
SFRApplication | アプリの基底クラス ※ |
■ ウィンドウ
クラス名 | 解説 |
---|---|
SFRWindow | ウィンドウの基底クラス ※ |
SFRPlainWindow | 枠のない平面的なウィンドウ |
SFRFrameWindow | 枠のある平面的なウィンドウ |
SFRTitleWindow | 枠とタイトルバーを伴った立体的なウィンドウ |
■ ダイアログ
クラス名 | 解説 |
---|---|
SFRDialog | ダイアログの基底クラス ※ |
SFRPlainDialog | 枠のない平面的なダイアログ |
SFRFrameDialog | 枠のある平面的なダイアログ |
SFRTitleDialog | 枠とタイトルバーを伴った立体的なダイアログ |
SFRMessageDialog | 最高1つのボタンを持つメッセージダイアログ |
SFRMultiDialog | 最高3つのボタンを持つ選択ダイアログ |
■ メニュー
クラス名 | 解説 |
---|---|
SFRMenu | メニューの基底クラス ※ |
SFRTextMenu | テキストで構成された項目を表示するメニュー |
■ ペイン
クラス名 | 解説 |
---|---|
SFRPane | ペインの基底クラス ※ |
SFRPlainPane | 枠のない平面的なペイン |
SFRTabPane | SFRTabControl 専用のペイン |
■ コントロール
クラス名 | 解説 |
---|---|
SFRControl | コントロールの基底クラス ※ |
SFRLabelControl | ラベルコントロール |
SFRButtonControl | ボタンコントロール |
SFRCheckboxControl | チェックボックスコントロール |
SFRRadiobuttonControl | ラジオボタンコントロール |
SFRComboboxControl | コンボボックスコントロール |
SFRTabControl | タブコントロール |
SFREditboxControl | テキスト入力コントロール |
SFRBrowserControl | HTML ブラウザを表示するコントロール |
※ レスポンダの基底クラス( 抽象クラス )は、開発者独自の GUI コンポーネントを作成するときに使用します。
GUI フレームワークの使い方
GUI フレームワーク を利用するには、SFRApplication クラスを継承した アプリクラス を1つ用意します。 アプリウィザード を利用してプロジェクトを作成する場合は、「GUI フレームワークを利用する」のオプションを選択することで、雛型となる アプリクラス が自動生成されます。
アプリクラス は、通常 HelloWorld や MyApp などのアプリ本体と同じ名前のクラスとなります。
SophiaFramework UNIVERSE チュートリアル : HelloWorld アプリ
例:ウィンドウの生成
SFRWindowPtr window; window = ::new SFRTitleWindow(SFRApplication::GetInstance(), SFXRectangle(0, 0, 240, 320), "my window");
例:ボタンの配置
SFRButtonControlPtr button; button = ::new SFRButtonControl(window, SFXRectangle(10, 10, 128, 24), "my button");
例:デバッグ出力の表示
button->RegisterHandler(SREVT_CONTROL, HANDLER_BEFORE, HANDLER_FUNCTION(OnButton)); HANDLER_IMPLEMENT_VOIDCONTROL(MyApp, OnButton, result, control) { TRACE("my button was pushed."); return; }
ウィンドウ 上に ボタン や チェックボックス などの コントロール を配置し、それらの コントロール が操作されたときに呼び出される関数を登録することによって、複雑な GUI アプリを簡単に作成できます。
GUI フレームワークの構造
SophiaFramework UNIVERSE の GUI フレームワーク は、SFRApplication クラスを継承する アプリクラス が ウィンドウクラス を管理し、 ウィンドウクラス が コントロールクラス を管理するという階層構造になっています。
図:継承関係
図:所有関係
ダイアログクラス と メニュークラス は ウィンドウクラス と同一階層にあります。 ペインクラス は コントロールクラス を管理することも コントロールクラス に管理されることもあります。
この階層構造により、BREW 環境の イベントの適切なディスパッチや、自動的な画面再描画が可能になります。
イベント処理
ユーザの操作によって発生した イベント は BREW 環境からアプリに送信されます。アプリが イベント を受信すると、GUI フレームワーク の イベント処理エンジン は イベント を処理します
イベント処理エンジン は、トレーサ と呼ばれる機能を利用して、可視 / 不可視 などの状態や配置関係に応じ、BREW 環境から渡されたイベントを然るべき レスポンダ に配信します。そして、あらかじめ開発者が登録した ハンドラ と呼ばれる関数を実行します。
イベントドリブン
BREW アプリは、ユーザの操作や外的要因によって発生した割り込みを イベント として伝え、ハンドリングする、イベントドリブン型の構造 をしています。
アプリは イベント の種類に応じて、初期化やキー操作などを分岐処理します。
アプリの起動から終了までの間には、さまざまな イベント が BREW 環境からアプリに送信されます。
シーケンス図:ライフサイクル
GUI フレームワーク の イベント処理エンジン は、さまざまな イベント を然るべき処理関数へ自動的にディスパッチしてくれるので、複雑な イベント処理 を簡単に記述できます。
アプリの起動
ユーザがアプリを起動すると、最初に イベント処理エンジン が起動されます。イベント処理エンジン は SFRApplication クラスを継承した アプリクラス のインスタンスを1つだけ作成します。
次に BREW 環境からアプリに SFEVT_APP_START イベント が送信され、アプリの初期化処理が行われます。
シーケンス図:アプリの起動
ウィンドウ の作成など初期化処理は、アプリクラス の コンストラクタ 内、または SFEVT_APP_START イベント のハンドラ内で行います。
アプリの終了
終了メニュー や 電源キー の押下によってアプリを終了させる場合、最初に BREW 環境から イベント処理エンジン に SFEVT_APP_NO_CLOSE イベント が送信されます。 イベント処理エンジン は、このイベントに対して false を返します。
※ SFEVT_APP_NO_CLOSE イベント について : アプリを終了しても構わない場合、このイベントに対して false を返します。アプリを終了させないようにするには、このイベントに対して true を返します。
次に BREW 環境から SFEVT_APP_STOP イベント が送信され、アプリの終了処理が行われます。
SFEVT_APP_STOP イベント の処理が正常に終了した場合、 イベント処理エンジン が解放され、アプリクラス のインスタンスも自動的に解放されます。
シーケンス図:アプリの終了
この例ではユーザが ウィンドウ 上の 終了ボタン を押下した時に、SFRApplication::Terminate() 関数が実行されアプリは終了します。
ウィンドウ の破棄など終了処理は、アプリクラス の デストラクタ 内、または SFEVT_APP_STOP イベント のハンドラ内で行います。
トレーサ
イベント は、種類毎にどの レスポンダ に配信されるか定義されています。
例えば、キーイベント は、フォーカスが設定されていてユーザの操作対象になっている レスポンダ に配信されます。また、サスペンドイベント や レジュームイベント はすべての レスポンダ に配信されます。
図:SFEVT_KEY イベントの流れ
図:SFEVT_APP_RESUME イベントの流れ
イベント処理エンジン では、イベントの配信規則を管理するトレーサによってイベントは種類に応じて適切に配信されます。
すべての レスポンダ は 親レスポンダ の トレーサ を継承します。 明示的にオーバーライドしない場合、アプリクラス に登録されている 標準トレーサ がイベントの配信規則として使用されます。
トレーサ を登録するには RegisterTracer() 関数を使います。トレーサ に設定できる規則は次の通りです。 レスポンダ の状態に応じてイベント配信規則を設定できます。
トレーサに設定可能な規則 | 解説 |
---|---|
TRACER_NONE | 子レスポンダへ配信しない |
TRACER_FORWARD | 前面から背面の子レスポンダへ配信する |
TRACER_BACKWARD | 背面から前面の子レスポンダへ配信する |
TRACER_FOCUS | フォーカスされている子レスポンダへ配信する |
TRACER_PROVIDE | イベントの重複処理を許す |
例:トレーサーの登録
class MyApp : public SFRApplication { public: explicit MyApp(Void) static_throws; }; MyApp::MyApp(Void) static_throws { if (static_try()) { // ソフトキー 1 〜 3 のイベントをすべてのレスポンダに前面から配信する // // トレーサは子レスポンダへ継承されるので、 // ウィンドウやコントロールでもこの設定は有効となる static_throw(RegisterTracer(SFEVT_KEY, AVK_SOFT1, AVK_SOFT3, BEHAVIOR_NONE, TRACER_FORWARD)); } return; }
標準トレーサ
GUI フレームワーク は、 標準トレーサ として以下の表のような トレーサ を保持しています。
配信順序が「−」の場合、イベントは子階層のレスポンダにイベントを配信しません。
重複処理が「−」の場合、イベントを重複して処理しません。イベントがあるレスポンダで処理された場合、それ以降、そのイベントが他のレスポンダに配信されることはありません。
状態フィルタが ○ の場合、レスポンダ の状態に応じてイベントの配信が可能です。空欄の場合は、状態に関係なく イベント の配信が可能です。
SophiaFramework UNIVERSE リファレンス : SFCEventEnum ( SophiaFramework UNIVERSE のイベント )
表:標準トレーサ一覧
イベント範囲 | 配信順序 | 重複処理 | 状態フィルタ ※ | |||
V | E | F | T | |||
SFRApplication クラスが保持する BREW イベントのトレーサ | ||||||
SFEVT_APPLICATION_CLASS_BEGIN ~ SFEVT_APPLICATION_CLASS_END |
− | − | ||||
SFEVT_APP_RESUME | 前面→背面 | 許可 | ||||
SFEVT_APP_SUSPEND | 前面→背面 | 許可 | ||||
SFEVT_KEY_CLASS_BEGIN ~ SFEVT_KEY_CLASS_END |
フォーカス | − | ○ | ○ | ○ | ○ |
SFEVT_CONTROL_CLASS_BEGIN ~ SFEVT_CONTROL_CLASS_END |
フォーカス | − | ○ | ○ | ○ | ○ |
SFEVT_CTL_TAB | フォーカス | 許可 | ○ | ○ | ○ | ○ |
SFEVT_CTL_TEXT_MODECHANGED | フォーカス | 許可 | ○ | ○ | ○ | ○ |
SFEVT_DIALOG_CLASS_BEGIN ~ SFEVT_DIALOG_CLASS_END |
フォーカス | − | ○ | ○ | ○ | ○ |
SFEVT_SHELL_CLASS_BEGIN ~ SFEVT_SHELL_CLASS_END |
− | 許可 | ||||
SFEVT_DEVICE_CLASS_BEGIN ~ SFEVT_DEVICE_CLASS_END |
− | 許可 | ||||
SFEVT_CLIPBOARD_CLASS_BEGIN ~ SFEVT_CLIPBOARD_CLASS_END |
− | 許可 | ||||
SFRApplication クラスが保持する SophiaFramework UNIVERSE イベントのトレーサ | ||||||
SREVT_RESPONDER_INITIALIZE | − | 許可 | ||||
SREVT_RESPONDER_TERMINATE | − | − | ||||
SREVT_RESPONDER_TERMINATE, SRP16_TERMINATE_TRY |
前面→背面 | 許可 | ||||
SREVT_RESPONDER_RENDER | − | 許可 | ○ | |||
SREVT_RESPONDER_STATUS | − | − | ||||
SREVT_APPLICATION | − | − | ||||
SFRWindow クラスが保持する SophiaFramework UNIVERSE イベントのトレーサ | ||||||
SREVT_WINDOW | − | − | ||||
SFRDialog クラスが保持する SophiaFramework UNIVERSE イベントのトレーサ | ||||||
SREVT_DIALOG | − | − | ||||
SFRMenu クラスが保持する SophiaFramework UNIVERSE イベントのトレーサ | ||||||
SREVT_MENU | − | − | ||||
SFRPane クラスが保持する SophiaFramework UNIVERSE イベントのトレーサ | ||||||
SREVT_PANE | − | − | ||||
SFRControl クラスが保持する SophiaFramework UNIVERSE イベントのトレーサ | ||||||
SREVT_CONTROL | − | − |
※ 状態フィルタについて
状態フィルタ | 解説 |
---|---|
V | 可視 / 不可視 |
E | 操作可能 / 操作不可能 |
F | フォーカス状態 / 非フォーカス状態 |
T | ターゲット状態 / 非ターゲット状態 |
ハンドラ
ユーザ操作によって発生した イベント やアプリ間通信の イベント などのさまざまなイベントを実際に処理する関数を ハンドラ と呼んでいます。
イベント は種類によって処理される優先順位が決まっています。例えば キーイベント では、ユーザは最前面のレスポンダを操作しているために、最子階層のフォーカスされている レスポンダ が最初に イベント を処理します。
図:イベント処理の優先順位
このような動作を実現するためには、親レスポンダ は 子レスポンダ より後でイベントを処理しなければいけません。そこで、ハンドラ には 前置ハンドラ と 後置ハンドラ の2種類が存在します。
前置ハンドラ は子レスポンダにイベントを配信する前に呼び出される ハンドラ です。一方、後置ハンドラ は子レスポンダでの処理が終わった後に呼び出される ハンドラ です。
ハンドラ を登録するには RegisterHandler() 関数を使用します。ハンドラ に設定可能な規則は次の通りです。
ハンドラに設定可能な規則 | 解説 |
---|---|
HANDLER_BEFORE | 子レスポンダより先にイベントを処理する |
HANDLER_AFTER | 子レスポンダより後でイベントを処理する |
ハンドラ を作成するには、次のマクロを使用します。( 特殊化バージョンもあります。)
マクロ名 | 解説 |
---|---|
HANDLER_DECLARE_BOOLEVENT | 宣言用マクロ |
HANDLER_IMPLEMENT_BOOLEVENT | 実装用マクロ |
例:キーハンドラの登録と実装
class MyWin : public SFRPlainWindow { public: explicit MyWin(Void) static_throws; private: HANDLER_DECLARE_BOOLEVENT(OnKey) }; MyWin::MyWin(Void) static_throws : SFRPlainWindow(...) { if (static_try()) { // キーイベントのハンドラ OnKey を登録する static_throw(RegisterHandler(SFEVT_KEY, HANDLER_AFTER, HANDLER_FUNCTION(OnKey))); } return; } HANDLER_IMPLEMENT_BOOLEVENT(MyWin, OnKey, event) { switch (event.GetP16()) { case AVK_SELECT: DoSelect(); return true; case AVK_CLR: DoClear(); return true; } return false; }
ハンドラ は イベント を処理した場合は true を、そうでなければ false を返します。 ハンドラ の戻り値が true の場合、イベント処理エンジン は必要に応じて再描画イベント を生成して 描画処理エンジン を起動します。
描画処理
イベント処理エンジン が処理を終えると、必要に応じて 描画処理エンジン が起動されます。描画処理エンジン は、レスポンダの状態や配置関係に基づき最速で描画するように 描画ハンドラ を実行します。
イベントループ毎に必要な 描画ハンドラ だけが呼び出されるので、複雑な GUI 画面でもパフォーマンスは劣化しません。
描画のタイミング
画面はイベントループの最後に描画されます。ハンドラ が true を返し イベント処理エンジン が 描画処理エンジン を起動すると、描画処理エンジン は再描画領域が登録されているかどうかを検査します。
シーケンス図:イベントが処理された場合
ハンドラ が false を返し 描画処理エンジン が起動されない場合や、再描画領域が存在しない場合は、描画しません。
シーケンス図:イベントが処理されなかった場合
描画ハンドラ
すべての描画処理は、描画ハンドラ と呼ばれる描画処理専用のハンドラ内で行われます。
描画ハンドラ も ハンドラ の一種で、RegisterHandler() 関数を使って登録します。ハンドラの実装には次のマクロを使用します。
マクロ名 | 解説 |
---|---|
HANDLER_DECLARE_VOIDRENDER | 宣言用マクロ |
HANDLER_IMPLEMENT_VOIDRENDER | 実装用マクロ |
描画ハンドラ は ハンドラ の引数に SFXGraphics クラスのインスタンスを取得します。ハンドラ では、このインスタンスを使って描画します。
例:描画ハンドラの登録と実装
class MyWin : public SFRPlainWindow { public: explicit MyWin(Void) static_throws; private: HANDLER_DECLARE_VOIDRENDER(OnRender) }; MyWin::MyWin(Void) static_throws : SFRPlainWindow(...) { if (static_try()) { // 描画ハンドラ OnRender を登録する static_throw(RegisterHandler(SREVT_RESPONDER_RENDER, SRP16_RENDER_CONTENT, HANDLER_BEFORE, HANDLER_FUNCTION(OnRender))); } return; } HANDLER_IMPLEMENT_VOIDRENDER(MyWin, OnRender, graphics) { // 描画ハンドラ内での描画には graphics インスタンスを使う graphics->FillRectangle(GetContentWorld(), SFXRGBColor(0x00, 0xFF, 0xFF, 0x00)); return; }
再描画領域の登録
再描画が必要なタイミングでは再描画領域を登録し、実際の描画は 描画ハンドラ で行います。
例えば、ラベルコントロールにおいて SetText() 関数でラベルの文字列を変更するとラベルコントロールを再描画しなければいけません。 この場合、SetText() 関数内では InvalidateContent() 関数を利用して再描画領域を 描画処理エンジン に登録します。
そして、描画処理エンジンに制御が移った段階で、登録された再描画領域は他で登録されたものと一括して再描画されます。
例:再描画領域の登録
class MyLabel : public SFRControl { public: explicit MyLabel(SFRResponderPtr director) static_throws; Void SetText(SFXAnsiStringConstRef param); private: HANDLER_DECLARE_VOIDRENDER(OnRender) }; MyLabel::MyLabel(SFRResponderPtr director) static_throws : SFRControl(director, ...) { if (static_try()) { // 描画ハンドラ OnRender を登録する static_throw(RegisterHandler(SREVT_RESPONDER_RENDER, SRP16_RENDER_CONTENT, HANDLER_BEFORE, HANDLER_FUNCTION(OnRender))); } return; } Void MyLabel::SetText(SFXAnsiStringConstRef param) { if (param != _text) { _text = param; // 再描画すべきことを描画処理エンジンに登録する InvalidateContent(); } return; } HANDLER_IMPLEMENT_VOIDRENDER(MyLabel, OnRender, graphics) { SFXRectangle rect(GetContentWorld()); // 実際の描画は描画ハンドラ内で行う graphics->FillRectangle(rect, SFXRGBColor(0xFF, 0xFF, 0xFF, 0x00)); graphics->DrawString(_text, rect, SFXRGBColor(0x00, 0x00, 0x00, 0x00)); return; }
再描画領域を登録すると、イベントループの最後に 描画ハンドラ が呼び出されます。
InvalidateContent() 関数には矩形の引数を取るオーバーロード関数を使って再描画領域を限定すると、高速な再描画処理を実現できます。
明示的な再描画
描画処理エンジン はイベントループの最後に起動されるために、ネットワークコールバックやタイマーコールバックなどイベントループ外でのコールバック呼び出しでは起動されません。
シーケンス図:コールバック呼び出し
ネットワーク処理が完了したタイミングなどで画面を再描画するには、描画処理エンジン を明示的に起動する必要があります。
シーケンス図:描画処理エンジンの起動
SFRApplication クラスに再描画イベントを送信することで、描画処理エンジン を明示的に起動できます。( SFRApplication クラス以外のクラスへの再描画イベントの送信は保証されていません。)
例:明示的な再描画
CALLBACK_IMPLEMENT_SFXHTTPCONNECTION(MyWin, OnConnect, error) { SFRApplicationPtr app(SFRApplication::GetInstance()); // ラベルの文字列を変更する _label->SetText("OnConnect"); // 描画処理エンジンを明示的に起動する // // 以下のコードを記述しないと画面が再描画されず、 // SetText() 関数の結果が画面に反映されない app->Invoke(SFXEvent(SREVT_RESPONDER_RENDER, SRP16_RENDER_INVOKE, false)); return; }
SFXEvent(SREVT_RESPONDER_RENDER, SRP16_RENDER_INVOKE, false) イベントの送信で描画処理エンジンを明示的に起動できます。
※ SFXEvent(SREVT_RESPONDER_RENDER, SRP16_RENDER_INVOKE, true) イベントを送信すると、再描画領域の登録に関係なく描画ハンドラが呼び出され、すべてのレスポンダが強制的に再描画されます。