Version 5.26
コントロールをゲットしよう!
「コントロール編もいよいよ大詰め!」
『いつの間にそんなシリーズが? ってゆーかもう26回……』
「あと10回は続くかも」
『げげ』
「さて、前回は新しくボタンを作ってみました」
『そーそー』
「今回は、逆にすでにあるコントロールを操作してみましょう」
『? だって、 m_cDataLstBox みたいにクラスウィザード使えば』
「じゃあ使わない場合」
『……できんの? そんなこと』
「もちろん。前も言ったけど、得体のしれないものも」
『ちゃんとプログラムで実現できる! そりゃアプリもプログラムだからそ
うなんだろうけど……』
「じゃ、試してみようか」
// いつものメンバ関数。
void CFileTestDlg::OnBtnShow()
{
CListBox *pcListBox
= (CListBox *)GetDlgItem( IDC_LST_DATA );
pcListBox->AddString( "あいうえお" );
return;
// あとは取っておきましょう。
『ん? なんか複雑だけど、でも AddString() って使ったのだよね』
「そう、リストボックスに追加するメンバ関数。じゃ、最初から見ていきま
しょう」
『最初はポインタ作ってるね』
「そう、 CListBox のポインタ。このポインタで受け取ってるのは?」
『 GetDlgItem() の戻り値。 API のかな、 MFC のメンバ関数かな』
「こういう類の関数は、 API だとたいがい最初にウィンドウハンドルを渡
すから」
『そういえば前回の ShowWindow() もそうだね。ってことは MFC のメンバ
関数なんだ。 MSDN …… CWnd::GetDlgItem() ね。 CDialog のじゃないん
だ』
「基本的には、ダイアログじゃなくてもコントロールは貼り付けられるから
ね」
『で、引数は……リストボックスの ID だね。戻り値は、そのコントロール
の CWnd ポインタなんだ』
「えーっと、コントロールは全部ウィンドウだって言ったよね」
『うん聞いた』
「じゃ、そのページの下の方の【階層図】っていうの見て」
『ほいクリック。なんか大きな画像が出てきた……でもちょっと見にくい
ね』
「印刷したのがパッケージに入ってるから普通はそっち見ればいいんだけど」
『あたし見れないもん……』
「で、中段左端に」
『 CWnd あるね』
「この図は継承関係を表してて、下のクラスは上のクラスから派生してる
っていうのを示してます」
『そっか、これ見れば CDialog が CWnd から継承してるって分かるんだね』
「で、下段中央に」
『コントロール、 CButton とか CListBox とか。つまりこういうのも全部
CWnd から継承してるんだ』
「まとめるね。コントロールはウィンドウの一種」
『だからコントロールのクラスは CWnd から派生してるんだ』
「そうすれば CWnd の機能はそのまま使えるからね。コントロールもウィン
ドウだからウィンドウハンドルを持ってて、それを CWnd が管理するから」
『だから CDialog::GetDlgItem() の戻り値の CWnd * を CListBox * にキャ
ストしちゃっていいんだ』
「……よくない」
『へ?』
「ま、 MSDN に〈していい〉って書いてあるし、してるサンプルコードが多
いから大丈夫なんだろうけど、ホントはこれはしちゃダメ」
『そうなの?』
「そう。さっきの図で、 CWnd の下に CListBox があるでしょ」
『うん、そりゃ CWnd から CListBox が派生されてるんだから』
「この、上のクラスのポインタから、下のクラスのポインタに無理矢理キャ
ストすることを〈ダウンキャスト〉って言って、ホントはしない方がいい
キャストなんです」
『しちゃいけない?』
「継承してるってことは、 CListBox は CWnd に色々新しいのを付け加えて
あるってことでしょ」
『そりゃ、 AddString() は CWnd にないもんね』
「ってことは、 CWnd を CListBox に無理矢理キャストしちゃうと、 CWnd
の〈足りない状態〉で、 CListBox の操作をするから……」
『……それってかなりやばくない?』
「ま、このコントロール関係のクラスは、そういう問題が起きないように作
られてるみたいだから大丈夫なんだろうけど、ホントはこういうのはしない
方がいいかな」
『そうなんだ……でも、じゃーどうするの?』
「この辺は、〈クラス〉の仕組みそのものが複雑で、それを MFC が無理矢
理使ってるから問題になってるから」
『あ、 API でやればいいんだ!』
「そういうこと。でも API の方が面倒なんで、まずは API と MFC の両方
を使ってしてみましょう」
// いつものメンバ関数。
void CFileTestDlg::OnBtnShow()
{
CButton cShowBtn;
cShowBtn.Attach
( ::GetDlgItem( GetSafeHwnd(), IDC_BTN_SHOW ) );
cShowBtn.SetWindowText( "あいうえお" );
cShowBtn.Detach();
return;
// あとは取っておきましょう。
『あ、 CListBox が CButton になった』
「いやね、今回の方法はもうクラスウィザードで変数と結び付けられちゃっ
てるコントロールには使えないんで、代わりに〈表示〉ボタンで」
『 m_cDataLstBox みたいになってるとダメってこと?』
「そういうこと。 MFC がそうなってるかどうか監視してて、なってるとエ
ラーになっちゃうんだよね」
『キビシー』
「だから、クラスウィザードで結びつけてないボタンを例にします。逆に言
うと、 m_cDataLstBox を関連させないようにしちゃえば CListBox でもで
きるから」
『そういうもんなのね。で、2行目の Attach() って初見。 MSDN 見ると、
CWnd のメンバ関数だね』
「何度も言うけど、コントロールもウィンドウ」
『うん、何度も聞いた』
「 CWnd の中にはウィンドウハンドルを格納してるわけだけど」
『 CWnd::GetSafeHwnd() で取り出せるヤツね』
「そのウィンドウハンドルを CWnd に格納させるのが CWnd::Attach() 」
『?』
「前回見たとおり、ボタンを作るには CButton::Create() を呼ばなきゃい
けなかったでしょ。ってことは、 CButton の変数を作ったときには、ボタ
ンそのものは存在しないわけ」
『うん、あ、だから cShowBtn の中のウィンドウハンドルは空なんだ!』
「そゆこと。だから、そこに Attach() でコントロールのウィンドウハンド
ルを格納させるわけ」
『そのコントロールのウィンドウハンドルは、 API の GetDlgItem() で拾
えるんだね。これって、ホントに MFC のにウィンドウハンドル付け加えた
のだね』
「でしょう、 API はたいがい、最初にウィンドウハンドルとかを渡すから」
『で、 SetWindowText() って呼んでる』
「 CWnd::SetWindowText() は、ホントはウィンドウのタイトルを変えるた
めのメンバ関数。たとえば……」
// いつものメンバ関数。
void CFileTestDlg::OnBtnShow()
{
SetWindowText( "かきくけこ" );
return;
// あとは取っておきましょう。
『うお、ダイアログのタイトルバーが〈かきくけこ〉になった!』
「だけど、ボタンの場合にはボタンの名前が変わります」
『あ、〈あいうえお〉になってる。へー気付かなかった……なんかちょっと
複雑。あ!』
「そう、前回ちょっとしたでしょ」
『ボタンだとその文字、ウィンドウだとタイトル、ね。同じものね〜』
「ウィンドウズの仕様だからしょうがないんだけどね。で、最後に
CWnd::Detach() を呼ぶんだけど、これがちょっとクセモノかな」
『ってゆーか、なんのために呼んでるの?』
「じゃ、これ呼ばないで試してみて」
『ほい……あ、なんかダイアログ出てエラーになった!』
「こっから少し複雑になるから注意してね。まず、 std::ifstream を思い
出して」
『いきなりだね。ファイルからデータを取り出すクラスだよね』
「 fopen() も思い出して」
『 API の、ファイルを開くのだよね』
「 fopen() で開いたファイルは」
『 fclose() で閉じなきゃいけないんだよね。でも std::ifstream の方は
なんにもしないで勝手に閉じてくれるんだよね』
「そう。クラスには〈デストラクタ〉って言って、変数がなくなるときに自
動的に呼ばれる関数があるんです」
『そんなかで fclose() してくれるんだよね』
「そういうこと。で、この〈デストラクタの後処理〉は、 CWnd にも付いて
ます」
『 CWnd はファイルと関係ないじゃん』
「ファイルハンドルの代わりに?」
『ウィンドウハンドルを持ってる……ってことは、 CWnd のデストラクタは
ウィンドウハンドルを閉じちゃう?』
「そう、それはすなわち〈ウィンドウを削除しちゃう〉ってこと」
『えええっ!? それってまずいじゃない。あ! だから CWnd::Detach()
を呼ぶの?』
「そういうこと! CWnd はデストラクタで、自分が持ってるウィンドウを
削除します。でも、今回みたいに〈ずっと表示されてるウィンドウをちょっ
と操作するために CWnd に Attach() する〉場合には」
『削除されちゃったら困る!!』
「だから、削除されないように CWnd::Detach() を呼びます。このメンバ関
数は CWnd が持ってるウィンドウハンドルを NULL に置き換えてくれるから、
削除されません」
『自動でしてくれるのが、仇になっちゃうんだ』
「っていうか、このデストラクタの機能が役立つ場面ってほとんどないか
な……」
『質問!』
「はい火美ちゃん」
『最初の CListBox * のときは、こーゆーこと全然考えなくていーの?』
「そう、ポインタだからね。もしポインタがなくなるときにデストラクタが
呼ばれちゃったら、ポインタの数だけデストラクタが呼ばれちゃうでしょ?」
『そっか、ポインタは本体指し示してるだけなんだもんね』
「そういうこと。デストラクタは本体が消える時の1度だけ呼ばれればいい
から」
『じゃ、そのダウンキャストだっけ、それ以外は MFC 使った方がメリット
多いってことになるの?』
「そういうことになるね。ポインタが返ってきてそれを操作するようになっ
てるのも、こういう煩わしさから解放する意味があるのかも」
『あ、でも、もしかしたら全部 API でやると面倒じゃないとか?』
「そんなことはないんだよねー」
『げげ』
「というわけで次回に続く!」
/*
Preview Next Story!
*/
『最近ちょっと難しくない?』
「それは言えるね。 C++ の細かいこととか教えてるし」
『ウィンドウズのこともねー。なんか〈仕様〉ってやな感じ』
「そういうのに振り回され続けるのがこの仕事……」
『というわけで次回』
< Version 5.27 メッセージを送ろう! >
「につづく!」
『ま、慣れちゃえば問題なし!』
「それはどうかと思うけど……」