クラス: wxCloseEvent, wxWindow
ウィンドウの削除は混乱しやすいテーマなので、この概要ではいつ、どのようにウィンドウを削除するか、もしくはユーザの要求に応じてウィンドウを閉じるかについて、理解の手助けになる情報を提供します。
ウィンドウ削除時に発生する一連のイベントにはどのようなものがありますか?
ユーザがフレームやダイアログの閉じるボタンを押下したときや、終了コマンドを実行したときに wxWidgets は wxWindow::Close を呼び出します。そして、これは EVT_CLOSE イベントを生成します: wxCloseEvent 参照。
適切なイベントハンドラを定義し、ウィンドウを破棄するか決定するのはアプリケーションの責務です。なんらかの理由で強制的にアプリケーションを終了させる場合 (wxCloseEvent::CanVeto が false を返却する場合)、常にウィンドウを破棄するべきです。それ以外の場合では終了要求を無視するか、安全に終了できるか決定する前にユーザへ確認するという選択肢があります。ウィンドウを破棄しない場合、EVT_CLOSE ハンドラで wxCloseEvent::Veto を呼び出し、呼び出し元へそのことを通知するようにしてください。この関数を呼ぶことで呼び出し元へ有用な情報を提供します。
wxCloseEvent ハンドラではウィンドウを破棄するときに wxWindow::Destroy のみを使用するようにし、delete 演算子を使用しないでください。これは、いくつかのウィンドウクラスではすべてのイベントが処理されるまでウィンドウを実際に削除しないためです。このようにしない場合、イベントが存在しないウィンドウに送信される危険性があります。
次の章で補足しますが、Close の呼び出しはウィンドウが破棄されることを保証しません。確実にウィンドウを破棄する場合、wxWindow::Destroy を呼び出してください。
どのようにしてアプリケーションからウィンドウを削除できますか?
アプリケーションではフレームワークが行なうように wxWindow::Close イベントを使用するか、もしくは直接 wxWindow::Destroy を呼び出すことができます。Close() を使用している場合は引数に true を渡すことで、このフレームを確実に破棄したいと考えており、終了要求を拒否できないことをイベントハンドラへ通知できます。
Destroy の代わりに Close を使用する利点は EVT_CLOSE イベントハンドラで任意の後処理を行える点です。例えば、ユーザに作業中のドキュメントを保存するように確認した後でドキュメントを閉じるといったことができます。Close は (false を返却したときに) 終了処理を拒否できるのに対し、Destroy は確実にウィンドウを削除します。
デフォルトの振る舞いどのようなものですか?
wxDialog のデフォルトの終了イベントハンドラは wxID_CANCEL イベントを生成する Cancel コマンドを模倣します。このキャンセルイベントのハンドラ自身が Close を呼ぶため、無限ループのチェックが行われます。wxID_CANCEL のデフォルトハンドラは (モードレスの場合に) ダイアログを非表示にするか、(モーダルの場合に) EndModal(wxID_CANCEL) を呼びます。言い換えると、デフォルトではダイアログは 破棄されません。(ダイアログはスタック上に作られるかもしれないので、動的に生成されると仮定することはできません)
wxFrame のデフォルトの終了ハンドラは Destroy() を使用してフレームを破棄します。
ユーザがメニューから終了を選んだときに何をすべきですか?
フレームの wxWindow::Close を単純に呼ぶことができます。これにより、フレームを破棄する独自の終了イベントハンドラが実行されます。
このときにアプリケーションを安全に終了できるかどうかを自前の終了イベントハンドラ、もしくは終了メニューコマンドハンドラから確認することができます。例えば、すべてのファイルが保存されているか確認したいと思うかもしれません。保存して終了するか、保存せずに終了するか、終了コマンドを完全にキャンセルするかをユーザが選択できるようにしてください。
1.xx の OnClose を 2.0 にアップグレードするには何をすべきですか?
wxWidgets 1.xx では OnClose 関数は実際に 'this' を削除せず、呼び出し元 (Close または wxWidgets フレームワーク) へウィンドウを削除するかどうかを通知していました。
コードをアップグレードするには EVT_CLOSE マクロを使用してフレームやダイアログ内でイベントテーブルのエントリを定義してください。イベントハンドラ関数は以下のようになります:
void MyFrame::OnCloseWindow(wxCloseEvent& event)
{
if (MyDataHasBeenModified())
{
wxMessageDialog* dialog = new wxMessageDialog(this,
"Save changed data?", "My app", wxYES_NO|wxCANCEL);
int ans = dialog->ShowModal();
dialog->Destroy();
switch (ans)
{
case wxID_YES: // 保存し、アプリケーションを終了する
SaveMyData();
this->Destroy();
break;
case wxID_NO: // 保存しない; 破棄してアプリケーションを終了する
this->Destroy();
break;
case wxID_CANCEL: // 何もしない - そのため、アプリケーションを終了しない
default:
if (!event.CanVeto()) // この削除が拒否可能か確認する
this->Destroy(); // 拒否不可の場合、とにかくウィンドウを破棄する
else
event.Veto(); // フレームを削除しなかったことを呼び出し元へ通知する
break;
}
}
}
アプリケーションを正しく終了するにはどうすれば良いですか?
wxWidgets アプリケーションは最後のトップレベルウィンドウ (wxFrame または wxDialog) が破棄されたときに自動的に終了します。アプリケーション全体の後処理は wxApp::OnExit (これは仮想関数であって、イベントハンドラではありません) に配置してください。
子ウィンドウは自動的に削除されますか?
はい、子ウィンドウは親ウィンドウのデストラクタで削除されます。これは子ウィンドウであればフレームやダイアログも含みます。そのため、親ウィンドウの終了ハンドラで明示的に子フレームや子ダイアログを終了させたいと思うかもしれません。
他の種類のウィンドウについてはどうでしょう?
これまでは 'マネージド' ウィンドウ、つまりフレームやダイアログについて話してきました。コントロールなど、親を持つウィンドウは直ちに削除され、通常は終了イベントハンドラを持ちません。とはいえ、必要であればそれらを実装することができます。一貫性を保つために、これらの種類のウィンドウを明示的に削除するときは delete 演算子の代わりに引き続き wxWindow::Destroy 関数を使用してください。