ダイアログをモーダルにする
[1] 主筆嬢 06/02/06 21:23
念のため、目次。
今回はこの前追加したダイアログをモーダルにしてみる。
[2] 主筆嬢 06/02/06 21:24
繰り返しになるけど、この間追加したダイアログは実はモードレスになってる。
だから追加したダイアログが表示されてるときに、「ほげほげ」と書かれたウインドウを選択すると、そっちにフォーカスが移っちゃう。

まぁ、それでも良い場合は良いんだけど、どうしてもモーダルなダイアログにしたい場合だってある。そんなときどうするか、というのが今回の話。
[3] 主筆嬢 06/02/06 21:26
ダイアログをモーダルにするには、基本的にはリソースを設定してあげるだけで良い。
まずはいつものごとくSun Studio 11とX-Designerを起動する。

[4] 主筆嬢 06/02/06 21:27
上記の画面でAdditionDlgを選択した状態で ボタンを押下して、選択ボックスのリソースを編集するダイアログを表示させる。

画面左上にある「ブリテンボードのリソース」というボタンを押す。

画面上部にあるタブの中から「設定」を選択する。

そうすると、こういう画面が表示される。
[5] 主筆嬢 06/02/06 21:28
「ダイアログのスタイル」と書かれたプルダウンメニューの中から「完全アプリケーションモード付き」を選択する。

[6] 主筆嬢 06/02/06 21:29
ここまで作業したら、あとはX-Designerを閉じて再コンパイルして実行すればいい。
うまくいけば選択ダイアログがモーダルになるはず。
[7] 広告 06/02/06 21:30
[8] 主筆嬢 06/02/06 21:35
でもね、一つだけ問題がある。
それは、ダイアログを表示させるための「CTP2AdditionShell::Show()」メソッドはダイアログが表示されるとすぐに制御を返してしまう、ということ。
[9] 主筆嬢 06/02/06 21:37
例えば、CTP2AdditionShell::Show()をこういう風に書き換えてみる。
// 画面に表示させる
void CTP2AdditionShell::Show()
{
printf( "AAAAAAAA\n" );
// ダイアログを表示させる
XtManageChild( AdditionDlg );
printf( "BBBBBBBB\n" );
}
|
そうして実行してみると、こうなる。

まずはいつもの通り「ほげほげ」のウインドウが表示される。そして、この状態で「了解」ボタンを押す。

そうすると、AdditionShellのダイアログが表示され、それと同時に端末の画面には"AAAAAAAA"と"BBBBBBBB"が表示される。
まぁ、当たり前といえば当たり前の結果だけど。
[10] 主筆嬢 06/02/06 21:39
もちろん、この挙動で何の問題もない場合は良いんだけど、そうではなく、Visual C++のMFCのCDialog::DoModalの様にダイアログが閉じられるまで制御を返して欲しくない場合もある。
特にWindows出身の私としてはそうして欲しいと思う。その方がプログラムの制御構造が単純になる様な気がするし。
[11] 主筆嬢 06/02/06 21:41
そう言う場合にはメッセージループを自作するという方法を使う。まぁ、これはMotifやXt周辺の話になるから詳細は割愛するけど、コードにするとこんな感じ
// 画面に表示させる
void CTP2AdditionShell::Show()
{
XEvent event;
XtAppContext appContext =
XtWidgetToApplicationContext( AdditionDlg );
// ダイアログを表示させる
XtManageChild( AdditionDlg );
printf( "AAAAAAAA\n" );
// メッセージループ
while( true ) {
XtAppNextEvent( appContext, &event );
XtDispatchEvent( &event );
}
printf( "BBBBBBBB\n" );
}
|
[12] 主筆嬢 06/02/06 21:43
こうすれば呼び出し側に制御が戻らなくなる。でも、逆に一生制御が戻らない。
その証拠に、このまま実行させてみる。

まずはAdditionShellを表示させる。そうするとメッセージループの前にある「printf( "AAAAAAAA\n" );」が実行される。
ここでAdditionShellを閉じて、もう一度表示させると、

"BBBBBBBB"が表示されることなく"AAAAAAAA"が表示される。
つまり、メッセージループを抜けていないと言うこと。まぁ「while( true )」でループしてるから当然だけど。
[13] 広告 06/02/06 21:45
[14] 主筆嬢 06/02/06 21:46
これじゃ、「CTP2AdditionShell::Show()」メソッドを呼び出した後のコードが実行されないし、それに、いつかはスタックを食いつぶして大変なことになる。
だから、ダイアログが閉じられたときには、ちゃんとメッセージループを抜けるようにしてあげる必要がある。
[15] 主筆嬢 06/02/06 21:48
そのために、X-DesignerからAdditionDlgに下記のコールバックを追加する。
- 了解ボタンが押された時の「OnOK」
- 取り消しボタンが押されたときの「OnCancel」
- アンマップされたときの「OnUnmap」
[16] 主筆嬢 06/02/06 21:49
そして、それぞれのコールバック関数(ここではCTP2AdditionShellのメソッド)とメッセージループを下記のように記述する。
// 画面に表示させる
void CTP2AdditionShell::Show()
{
XEvent event;
XtAppContext appContext =
XtWidgetToApplicationContext( AdditionDlg );
printf( "AAAAAAAA\n" );
// フラグの値を初期化
CloseFlg = false;
// ダイアログを表示させる
XtManageChild( AdditionDlg );
// メッセージループ
while( !CloseFlg ) {
XtAppNextEvent( appContext, &event );
XtDispatchEvent( &event );
}
// ダイアログを閉じる
XtUnmanageChild( AdditionDlg );
printf( "BBBBBBBB\n" );
}
// 閉じられたときに呼ばれる
void CTP2AdditionShell::OnUnmap( Widget w, XtPointer d )
{
CloseFlg = true;
}
// 了解ボタンが押下されたときに呼ばれる
void CTP2AdditionShell::OnOK( Widget w, XtPointer d )
{
CloseFlg = true;
}
// キャンセルボタンが押下されたとき呼ばれる
void CTP2AdditionShell::OnCancel( Widget w, XtPointer da )
{
CloseFlg = true;
}
|
あと、CloseFlgはCTP2AdditionShellのprotectedのメソッドとして追加しておく。まぁ、グローバル変数でも良いけどさ。
[16] 主筆嬢 06/02/06 21:49
こうしておけば、「了解」「キャンセル」のボタンが押されたとき、それと「ダイアログの左上がダブルクリックされた」ときに「CloseFlg」が真になって、メッセージループを抜ける事が出来るようになる。
そして、メッセージループを抜けた後にアンマネージして画面からダイアログを消す。
[17] 主筆嬢 06/02/06 21:51
この状態で実行してみる。

まず、AdditionShellを表示する。メッセージループ前にある「printf( "AAAAAAAA\n" );」が実行される。

この状態で「了解」や「取り消し」を押すか左上の隅をダブルクリックすると、メッセージループを抜けて「printf( "BBBBBBBB\n" );」が実行される。
[18] 主筆嬢 06/02/06 21:55
これで「CTP2AdditionShell::Show()」は「CDialog::DoModal()」と同じ挙動を示すようになった。
めでたしめでたし。
[19] nabiki_t 06/02/06 21:56
なにが「めでたしめでたし」だ、てぇ抜きやがって……
いくつか説明が抜けてるじゃねぇか……
[20] 主筆嬢 06/02/06 21:58
仕事してない奴に文句言われたくない。
抜けてるならおまえが説明しろ。
[21] nabiki_t 06/02/06 22:00
まず、これはデザイン設計の問題だけど。
X Window SystemではWindowsとは違って、モーダルダイアログを嫌う傾向にある。現にX-Designerのほとんど全てのダイアログはモードレスになっている。「モード」があるのはユーザにとっては使いにくい、という哲学があるらしい。
まぁ、その哲学に従うかどうかはアプリケーションの作者が決めることだがな。
[22] nabiki_t 06/02/06 22:02
それと、「ダイアログのスタイル」のそれぞれの項目の意味は次の通りだ。
| システムモード付き |
画面上に表示されている、他の全てのウインドウにアクセスできないようにする。 |
| 一時アプリケーションモード付き |
アプリケーションの上位のウインドウにアクセスできないようにする。 |
| 完全アプリケーションモード付き |
アプリケーションの他の全てのウインドウにアクセスできないようにする。 |
| モードなし |
制限しない。 |
| 作業領域 |
DialogShellの子ではないときに設定されるらしい。よく知らん。 |
上で言ったとおり、X Window Systemでは余りダイアログの振る舞いを制限するのは好まれない。出来る限り制約が少ない奴を選択した方が良いらしい。
それと一般のアプリケーションでは絶対に「システムモード付き」は使うべきではない。
[23] nabiki_t 06/02/06 22:04
次に、モーダルなダイアログによってアクセスできなくなったウインドウ(ここでは「ほげほげ」と書いてある奴)の挙動についてだ。
これはWindowsとは違って、完全にアクセス出来なくなるわけではないことに注意する必要がある。例えばウインドウの位置やサイズを変更することが出来るし、ウインドウの左上隅をダブルクリックすればそのウインドウを閉じることだって出来てしまう。
もちろん、それぞれのイベントに対するコールバックやイベントハンドラもちゃんと呼ばれる。
つまり、X Window Systemでの「モード付き」というのは、単にフォーカスが当たらなくなるだけと解釈する必要がある。(ただし、システムモード付きにすると、本当にそのウインドウにしかアクセスできなくなる。)
だから、ダイアログによりユーザと対話している最中にリサイズや再描画・クローズなどのイベントが発生しても問題ないようにプログラムを作らなければならない。
[24] nabiki_t 06/02/06 22:05
そして最後に、今回作成したプログラムのソースコードを公開しておく。
とりあえず、以上だ。
[25] 主筆嬢 06/02/06 22:06
へい、お疲れさん
[26] 名無しさん 06/02/06 22:10
目次はこちら。
|