Version 7.14
ハンドルの管理
「今回は、ちょっと総合的なプログラムを作ってみましょう」
void CAnimeDlg::OnBDraw()
{
HDC hCanvasDC = ::GetDC( m_cCanvasStatic.GetSafeHwnd() );
// クリッピングします。
RECT stRect;
::GetClientRect( m_cCanvasStatic.GetSafeHwnd(), &stRect );
HRGN hRgn = ::CreateRectRgnIndirect( &stRect );
::SelectClipRgn( hCanvasDC, hRgn );
// フォントの横幅を広げます。
HFONT hCurrentFont = (HFONT)::GetCurrentObject( hCanvasDC, OBJ_FONT );
LOGFONT stLogFont;
::GetObject( hCurrentFont, sizeof( stLogFont ), &stLogFont );
strcpy( stLogFont.lfFaceName, "MS 明朝" );
stLogFont.lfWidth = 50;
HFONT hFont = ::CreateFontIndirect( &stLogFont );
HFONT hOldFont = (HFONT)::SelectObject( hCanvasDC, hFont );
// 文字列を書き込みます。
const char pchText[] = "あいうえお";
::TextOut( hCanvasDC, 0, 0, pchText, strlen( pchText ) );
// 後片付け。
::SelectObject( hCanvasDC, hOldFont );
::DeleteObject( hFont );
::DeleteObject( hRgn );
}
『ながーい!!』
「って言っても?」
『う”……まぁ、確かにどれも一度見たことのある API だし、ちゃんと使
い方教えてもらったのばかりだけど』
「でしょう。これは前回言った、文字がはみ出るのをクリッピングで防ぐ
例」
『ホントだ、横幅広げても、ちゃんとはみ出ずに書かれてる』
「で、基本は」
準備
処理
後片付け
「の3ステップ」
『準備がやたら多いね……』
「だね。〈どういう風に描くか〉も処理の一種なのかもしれないけど」
『でもどっちかってゆーとやっぱ準備だと思う』
「で、これがたった3ステップでも、実際には複雑。それは、データが多い
から」
『確かにハンドルだけでも5つもあるものね』
「5つならなんとかなるんだけど、これ以上増えると……。で、こういうと
きは、うまく〈まとめる〉と、データが多くなっても管理しやすくなりま
す」
『まとめるってどういうふうに?』
「それがね……」
『な、なに? なんでテンション落ちるの!?』
「いくつか方法はあるんだけど、僕自身、こういうときはこうするのがい
い、っていう明確なのはなくてねー」
『経験と勘ってヤツね』
「だから、今回は方法と、メリット・デメリットの紹介をするから、あとは
一緒に勉強ってことで」
『なんだかなー』
「まとめ方は3通り。まずひとつは、データと処理をくっつける方法」
『どゆこと?』
「あるデータにはこういう処理が必要、っていうのはだいたい決まってるか
ら、それをペアにするってこと。そうすれば、全然関係ないデータに不必要
な処理をする必要がなくなるわけ」
『?』
「たとえばさ、さっきの hOldFont は元々あるフォントのハンドルだから、
DeleteObject() しちゃいけないけど、このままだと」
『確かに、間違えてしちゃったりとかしそうだよね』
「そこでその実現方法として、クラスを使います」
『そういえば、クラスならハンドルがメンバ変数になって中に入ってるんだ
もんね』
「 MFC のものそのものじゃあまり使えないけど、とりあえず使ってみます」
void CAnimeDlg::OnBDraw()
{
CDC *pcCanvasDC = m_cCanvasStatic.GetDC();
// クリッピングします。
CRect cRect;
m_cCanvasStatic.GetClientRect( &cRect );
CRgn cRgn;
cRgn.CreateRectRgnIndirect( &cRect );
pcCanvasDC->SelectClipRgn( &cRgn );
// フォントの横幅を広げます。
CFont *pcCanvasFont = pcCanvasDC->GetCurrentFont();
LOGFONT stLogFont;
pcCanvasFont->GetObject( sizeof( stLogFont ), &stLogFont );
strcpy( stLogFont.lfFaceName, "MS 明朝" );
stLogFont.lfWidth = 50;
CFont cFont;
cFont.CreateFontIndirect( &stLogFont );
CFont *pcOldFont = pcCanvasDC->SelectObject( &cFont );
// 文字列を書き込みます。
const char pchText[] = "あいうえお";
pcCanvasDC->TextOut( 0, 0, pchText, strlen( pchText ) );
// 後片付け。
pcCanvasDC->SelectObject( pcOldFont );
}
『……なんかあんま変わんない、っつーか、混乱する……』
「 API で書いたのと MFC で書いたのとは、やっぱ違うからね。で、 MFC
を使うと、まず DeleteObject() を明示的に呼ばなくていいっていうメリッ
トがあります」
『 Ver 7.03 ( No.123 ) で言ってた、自動的に削除されちゃうって機能が
あるからいらないんだ』
「その回で言ったけど、ポインタならそういうの関係ないし」
『ホントだ、元からあるのは全部ポインタで受け取ってるんだ』
「 MFC がそうできているからね。ハンドルの時は、全部同じハンドルだっ
たけど、 MFC のクラスを使えば」
『とりあえず元からあるのと新しく作ったのは区別できるわけだ』
「そゆこと。でも実際、まだまだ複雑」
『というわけで他のまとめ方は?』
「ふたつめのまとめ方は、さっきの3ステップ〈準備・処理・後片付け〉を
まとめること。でもこれは不可能」
『不可能? あ、後片付けは最後にしなきゃいけないんだ』
「そゆこと。場合によっては先に後片付けできるものもあるんだけど、この
場合はそれができないから」
『質問! その3ステップをまとめるとどういういいことがあるの?』
「後片付けができれば、そのとき片付けた変数はもう使われないってことだ
から、そこから後の処理では無視していいでしょ」
『つまり、変数をポイしちゃって、変数を減らしちゃうわけね』
「そういうこと。そうすれば実質的なデータ数が減るから、複雑さも減るか
らね」
『うんうん。で、3つ目のまとめ方は?』
「3つ目は、〈準備・処理・後片付け〉ごとにまとめる方法。一番最近だ
と…… Ver 5.35 ( No.100 ) の中で使ってる OnInitDialog() を思い出し
て」
『えーっと、ダイアログが作られる時に呼ばれるメンバ関数だよね』
「そうそう。そこで、リージョンとフォントを先に作っておきます。でも、
そのために、 CRgn と CFont は CAnimeDlg のメンバ変数にしておきます」
『なぜに?』
「そうしないと、 CAnimeDlg::OnInitDialog() で作った変数を
CAnimeDlg::OnBDraw() の中で使えないから。普通の変数だと関数から出た
ら消えちゃうからね」
『そっか、メンバ変数ならずっとあるし、どのメンバ関数からも使えるか
ら』
「というわけで、 AnimeDlg.h の中を」
class CAnimeDlg : public CDialog
{
// 以下2行追加。
CRgn m_cRgn;
CFont m_cFont;
// 以下略。
『この m_cRgn と m_cFont がメンバ変数になるんだよね』
「そうそう。次に CAnimeDlg::OnInitDialog() に次の部分を追加します」
BOOL CAnimeDlg::OnInitDialog()
{
// 略。
// TODO: 特別な初期化を行う時はこの場所に追加してください。
// 以下から追加。
CDC *pcCanvasDC = m_cCanvasStatic.GetDC();
// リージョンを作ります。
CRect cRect;
m_cCanvasStatic.GetClientRect( &cRect );
m_cRgn.CreateRectRgnIndirect( &cRect );
// フォントを作ります。
CFont *pcCanvasFont = pcCanvasDC->GetCurrentFont();
LOGFONT stLogFont;
pcCanvasFont->GetObject( sizeof( stLogFont ), &stLogFont );
strcpy( stLogFont.lfFaceName, "MS 明朝" );
stLogFont.lfWidth = 50;
m_cFont.CreateFontIndirect( &stLogFont );
// 追加ここまで。
return TRUE; // TRUE を返すとコントロールに設定した(略)。
}
『やってることは同じね、さっきと。変数がメンバ変数になってるだけ』
「メンバ変数はメンバ変数が入ってるクラスができたときにはもう作られて
るからね。で、いつものとこは」
void CAnimeDlg::OnBDraw()
{
CDC *pcCanvasDC = m_cCanvasStatic.GetDC();
pcCanvasDC->SelectClipRgn( &m_cRgn );
CFont *pcOldFont = pcCanvasDC->SelectObject( &m_cFont );
// 文字列を書き込みます。
const char pchText[] = "あいうえお";
pcCanvasDC->TextOut( 0, 0, pchText, strlen( pchText ) );
// 後片付け。
pcCanvasDC->SelectObject( pcOldFont );
}
『ここも同じね、メンバ変数になってるだけで』
「ここで入れ替えをして、文字を書き込んで、入れ替えを元に戻します」
『ビルドして実行、うん、結果同じ! ……でもさ、なんでここで入れ替え
してんの?』
「ぎく!」
『 CAnimeDlg::OnInitDialog() でやればいいじゃん。なんでわざわざ』
「いや、僕にもよく分からないんだけど、 CAnimeDlg::OnBDraw() を読んだ
後とかは、常に最初の状態、つまり pcOldFont とかがセットされた状態に
なってるんだよね……」
『……つまり水希ちゃんにも理由が分からない、と』
「はい……」
/*
Preview Next Story!
*/
『もしかして、水希ちゃんって意外とダメダメなんじゃない?』
「ギク!」
『ぎくって何よぎくって』
「……次回、謝らなきゃいけないことが……」
『なんですとーっ!?』
「というわけで次回」
< Version 7.15 再描画できるように >
『につづく!』
「デバイスコンテキストまわりって、想像以上に複雑……」
『教える人間がそういうこと言うな!』