今回は、ウィンドウを作成して表示する方法について見ていきましょう。
CreateAndShowWindow()関数
前回のRegisterWindowClass()関数を呼び出してウィンドウクラスの登録を行った次は、いよいよウィンドウを作成し表示します。
その処理は、同じプロジェクト内のCreateAndShowWindow()関数で行います。
// ウィンドウを作成して表示します。
BOOL CreateAndShowWindow( int p_iCmdShow )
{
// ウィンドウハンドルを取っておく変数。
HWND hWnd;
// ウィンドウを作製します。
hWnd = CreateWindow
( CLASS_NAME
, APP_NAME
, WS_OVERLAPPEDWINDOW
, CW_USEDEFAULT
, 0
, CW_USEDEFAULT
, 0
, NULL
, NULL
, g_hInstance
, NULL
);
// ウィンドウができたかチェックします。
if( hWnd == NULL )
{
return FALSE;
}
// ウィンドウを表示します。
ShowWindow( hWnd, p_iCmdShow );
UpdateWindow( hWnd );
return TRUE;
}
//////////////////////////////////////////////////////////////////// // ウィンドウを作成して表示します。 BOOL CreateAndShowWindow( int p_iCmdShow ) { // ウィンドウハンドルを取っておく変数。 HWND hWnd; // ウィンドウを作製します。 hWnd = CreateWindow ( CLASS_NAME , APP_NAME , WS_OVERLAPPEDWINDOW , CW_USEDEFAULT , 0 , CW_USEDEFAULT , 0 , NULL , NULL , g_hInstance , NULL ); // ウィンドウができたかチェックします。 if( hWnd == NULL ) { return FALSE; } // ウィンドウを表示します。 ShowWindow( hWnd, p_iCmdShow ); UpdateWindow( hWnd ); return TRUE; }
この関数では2つの処理を行っています。ひとつは、CreateWindow()関数を使用してウィンドウを構築すること。もうひとつはShowWindow()関数を使用してウィンドウを表示すること。
この2つについて順に見ていきましょう。
ウィンドウの構築
まず「ウィンドウの構築」、つまりウィンドウを作ります。
ウィンドウの構築はCreateWindow()関数というAPIで行います。ウィンドウも「Windowsのもの」なので、APIを使用して作成します。
「ウィンドウを作製する」ということは、実際には「Windowsに頼んでウィンドウを作製してもらう」ということです。
CreateWindow()関数を呼び出すと、Windowsシステム内にウィンドウが作られます。
ウィンドウは「Windowsシステム内」に作られるため、プログラム側から直接扱うことはできません。Windowsに「お願い」して作ってもらっただけあって、自由度が低いわけです。
ただ、ウィンドウを間接的に操作する手段は与えられます。CreateWindow()関数は、戻り値として「ウィンドウハンドル」というものを返します。これはポインタの一種で、これを使用してウィンドウを操作することができます。ただし、その際にもAPIを使って、「~~という操作をしてください」とWindowsにお願いすることになります。
Windows上で何かをする、ということは「Windowsにお願いをする」ことに他なりません。前回のウィンドウクラスの登録も、Windowsに「これこれこういうウィンドウを作りたいので準備してください」と頼むようなことですし、その他多くのことをWindowsを通して行います。そして、その「お願い」をするためにはAPIを呼び出さなければならないわけです。
そんなのめんどい、と思われるかもしれません。でも、では逆に、ウィンドウすべてを自分一人で作るとなると、それはとても大変なことです。ウィンドウの枠を描き、タイトルバーをグラデーションで塗りつぶし、タイトル文を出力し、メニューの文を出力し……さらに、枠をドラッグしたらサイズを変える、といった標準的な処理も自分でしなければいけません。
そういったことをWindowsが代わりにしてくれるわけですから、APIでお願いした方が早いというものです。
ちなみに、このウィンドウだけでなく、アイコンやメニューといったリソース等、Windowsで使う多くのものは「Createなんたら()」という名前のAPIを使って作ります。この「Create」、この語感はなんか神を思い出させますね。Windows神を奉ってウィンドウを作ってもらいましょう。
話を戻して、CreateWindow()関数について、もう少し見てみましょう。
前回のRegisterClass()関数は、各種設定を構造体にセットして、それをRegisterClass()関数に渡すという形式でしたが、CreateWindow()関数の場合には「ひとつひとつ引数に渡す」という方法になります。統一感があまり感じられませんが、気にせず使うことにしましょう。
それでは、その引数ひとつひとつを見ていきましょう。
・第1引数:前回説明した「ウィンドウクラス」の名前を渡します。この名前を使って「すでに登録してあるこの名前のウィンドウクラスを使いたいんだけど」と指定することで、Windowsはそのウィンドウクラスを元にウィンドウを作成します。ここでは前回ウィンドウクラスを登録する際に指定した「CLASS_NAME」を渡しています。
・第2引数:ウィンドウの名前、つまりタイトルバーに書かれる文字列を渡します。
・第3引数:ウィンドウのスタイルを渡します。アプリケーションによって、タイトルバーがあったりなかったり、枠が太かったり細かったりと、ウィンドウの形は大きく異なりますが、それはこの値を変更することで変更することができます。指定できる値はMSDN Win32 API CreateWindow()関数 リファレンス(英語)をご覧ください。ここでは「WS_OVERLAPPEDWINDOW」を渡しています。これを渡すと、一番標準的なウィンドウのスタイルになります。
・第4引数:ウィンドウの位置(横方向)を指定します。画面の左上角から、ウィンドウの左上角まで、ピクセル単位で指定します。ここでは「CW_USEDEFAULT」を渡しています。これを渡すとWindowsが自動的に位置を決めてくれます。
・第5引数:第4引数と同じく、ウィンドウの位置を渡します。ただしこちらは「縦方向」になります。第4引数にCW_USEDEFAULTを渡している場合にはこの値は無視されるため、ここでは0を渡しています。
・第6引数:ウィンドウのサイズ(横幅)をピクセル単位で指定します。この引数にも「CW_USEDEFAULT」を渡すことができます。この値を渡すと、Windowsが自動的にサイズを決めてくれます。
・第7引数:第6引数と同じく、ウィンドウのサイズを渡します。ただしこちらは「縦方向」、つまり高さを指定します。こちらも第5引数と同じく、第6引数にCW_USEDEFAULTを渡すと無視されるので、ここでは0を渡しています。
・第8引数:「親ウィンドウ」のウィンドウハンドルを渡します。ウィンドウは、大きなウィンドウの上に小さなウィンドウを乗せることができます。この時、乗せられるウィンドウが「親ウィンドウ」となります。この例では、ここで作成するウィンドウが最初なので、このウィンドウの親ウィンドウはありません。そのため「NULL」を渡します。
・第9引数:メニューのハンドルを渡します。ウィンドウにメニューを持たせる場合にはこの引数に「メニューハンドル」を渡しますが、今回はを作らないので「NULL」を渡します。
・第10引数:アプリケーションのインスタンスハンドルを渡します。インスタンスハンドルは「すべてはWinMain()から」で説明たものです。インスタンスハンドルは、WinMain()関数内でg_hInstanceグローバル変数に入れてありますから、それを渡します。
・第11引数:予備用の引数です。この引数に好きな値を入れておくと、ウィンドウ作成後に受け取ることができます。たとえば「ウィンドウについての情報」を持つ構造体を用意しておいて、そのアドレスをこの引数に渡すことで、ウィンドウから構造体にアクセスできるようになるので管理が楽になります。ここでは特に必要ではないので「NULL」を渡します。
引数についての詳しい説明はMSDN Win32 API CreateWindow()関数 リファレンスをご覧ください。
ウィンドウはウィンドウクラスと違ってかなり「いじりがい」のある部分です。特に第3引数のウィンドウスタイルは色々試してどんなウィンドウが作れるのか確認してみるといいでしょう。
ウィンドウとウィンドウクラス
前回説明した「ウィンドウクラス」と、今回の「ウィンドウの構築」の関係について整理しておきましょう。
ウィンドウクラスの登録をする際、WNDCLASS構造体のlpszClassNameメンバ変数に「ウィンドウクラス名」を指定しました。
今回、ウィンドウの構築時に、CreateWindow()関数の第1引数に、そのウィンドウクラス名を渡しています。
つまり、この2つの「ウィンドウクラス名」を一致させることで、登録したウィンドウクラスを元にウィンドウを構築することができるわけです。
この2つを一致させないと、ウィンドウの構築ができません。「ウィンドウクラスの登録」と「ウィンドウの構築」をセットにして憶えましょう。
また、前回説明したように、ウィンドウクラスには「Windowsがすでに登録してあるウィンドウクラス」もあります。
ボタンやエディットボックスといった、主に「ダイアログに貼り付けるパーツ」となるウィンドウのウィンドウクラスがそうです。これらのパーツはウィンドウの一種で、ダイアログが「親ウィンドウ」、これらのパーツが「子ウィンドウ」になります。
ボタン等のウィンドウクラスが登録されている理由は、ボタン等を「自分で一から作らないで済む」ようにするためです。これは、「一から作ると面倒」というだけでなく、「操作や外観を統一するため」という目的もあります。よく使う「操作用パーツ」をOS側で用意することで、そのOS内では「どのパーツがどういうものか」ということが分かりやすくなるわけです(もっとも、最近はWebアプリやウィジェットの普及でそういう面がかなり無視されていますが……)。
「Windowsがすでに登録してあるウィンドウクラス」の一覧はMSDN Win32 API CreateWindow()関数 リファレンスに書かれていますのでそちらをご覧ください。ステップアップとして、今回作成したウィンドウの上にボタンやエディットボックスを貼り付けてみるという練習をしてみるのもいいでしょう。
ウィンドウの表示
話を戻して、ウィンドウの構築後に必要な処理について説明します。
ウィンドウ構築後は、実はウィンドウは表示されていません。Windowsシステム内に「ウィンドウの管理情報」が作られただけで、まだ画面には現れていないのです。ウィンドウを表示させるためにはもう2つ処理が必要です。
まずShowWindow()関数というAPIを使って「表示状態」を設定します。
ShowWindow( hWnd, p_iCmdShow );
// ウィンドウを表示します。 ShowWindow( hWnd, p_iCmdShow );
このAPIは便利な関数で、ウィンドウを最大化したり最小化したり、画面から消したりすることができます。
まず第1引数で、表示状態を設定するウィンドウの「ウィンドウハンドル」を渡します。
「ウィンドウハンドル」はCreateWindow()関数の戻り値で返された値で、ポインタの一種です。型は「HWND型」になります。
図1で説明したように、ウィンドウはWindowsシステム内に置かれているため、直接操作することはできません。操作する際には、操作用のAPI(たとえばShowWindow()関数)に「操作したいウィンドウのウィンドウハンドル」を渡すことで、操作するウィンドウを指定します。つまり、ウィンドウハンドルとは「ウィンドウの番号」ということです。番号をAPIに渡せば、その番号の付いたウィンドウを操作してもらえるわけです。
ここでは、先ほどCreateWindow()関数で作成したウィンドウの表示状態を設定したいので、その時の戻り値で返されたウィンドウハンドルを渡せばいいわけです。
第2引数には「ウィンドウの表示状態」を渡します。
この引数には「最大化」「最小化」「元に戻す」「非表示にする」といった設定を渡すことができます。渡せる設定についてはMSDN Win32 API ShowWindow()関数 リファレンスをご覧ください。
ところが、今回の例ではこのリファレンスに載っている値ではなく、p_nCmdShowを渡しています。
これはWinMain()関数の第4引数、つまりアプリケーションが実行されたときに渡された値を渡しています。
この引数には何が渡されるのかというと、ショートカットの「プロパティ」にある「実行時の大きさ」、この欄の設定がWinMain()関数の第4引数として渡されます。
ショートカットには「実行時の大きさ」という設定があります。ここでは「通常のウィンドウ」「最大化」「最小化」という3つの表示方法を選択することができます。
これらを設定すると、ShowWindow()関数の第2引数で指定できる値そのものがWinMain()関数の第4引数に渡されます。この値をそのままShowWindow()関数の第2引数に渡すことで、ユーザーがショートカットで指定した「実行時の大きさ」でウィンドウが表示されるわけです。
逆に言うと、このようにプログラムで組まないと「実行時の大きさ」は機能しないということです。このことはMSDN Win32 API ShowWindow()関数 リファレンスにちゃんと書かれています。
このように「ちゃんとプログラムしないと標準の機能が備わらない」ことが結構あります。そういうことがないよう、MSDNのリファレンスをよく読み、注意してプログラムを組む必要があることを憶えておいてください。
さて、ShowWindow()関数で「ウィンドウの表示方法」を指定したら、最後にUpdateWindow()関数というAPIを呼び出します。
UpdateWindow( hWnd );
UpdateWindow()関数は、ウィンドウの表示状態を更新するAPIです。この関数を呼び出すことで、ShowWindow()関数で設定した表示状態が反映されます。
第1引数には、ShowWindow()関数と同じようにウィンドウハンドルを渡します。このAPIを使用する際も、操作するウィンドウを指定するためにウィンドウハンドルを渡すわけです。
以上の処理を終えて、ウィンドウがやっと表示されます。
MFCの場合
まず前提として、MFCでは「初めから複数のウィンドウが重なっている」ということを憶えておいてください。
たとえば標準的なSDIアプリケーションの場合、2枚のウィンドウが重なっています。
外枠を持ち、タイトルバーを持ち、メニューを持つウィンドウがメインのウィンドウになります。これを「フレームウィンドウ」と言います。
その上に、白くて四角いウィンドウが乗っています。これを「ビューウィンドウ」と言います。
SDIアプリケーションを作ると、この2つのウィンドウが表示されます。フレームウィンドウは基本的なウィンドウの機能を、ビューウィンドウは文字や図形の描画やマウスクリック等の処理を受け取る、と役割分担をしています。
そして、この2つはクラスが異なります。
フレームウィンドウはCFrameWndクラスの派生クラス、ビューウィンドウはCViewクラスの派生クラスが担当し、それぞれプロジェクトを作成した時に作られます。
具体的には、フレームウィンドウは「CMainFrame」という名前のクラスが作られます。また、ビューウィンドウは「C***View」という名前のクラスが作られます(「***」にはプロジェクト名が入ります)。これらのクラスはMFCのSDIアプリケーションではおなじみなので見たことがあると思います。
フレームウィンドウを管理するCMainFrameクラスは、ウィンドウ全体の処理を行います。画面に表示する情報を管理する「ドキュメントクラス」とビューウィンドウのクラスを結びつける機能も持ちます。
ビューウィンドウクラスはビューウィンドウに文章や絵を描画する役割を持ちます。ビューウィンドウクラスには「OnDraw()メンバ関数」が備わっていて、このメンバ関数内で画面への描画を行います。
// CMxA05_VC6View クラスの描画
void CMxA05_VC6View::OnDraw(CDC* pDC)
{
CSDI01Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: この場所にネイティブ データ用の描画コードを追加します。
}
///////////////////////////////////////////////////////////////////////////// // CMxA05_VC6View クラスの描画 void CMxA05_VC6View::OnDraw(CDC* pDC) { CSDI01Doc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: この場所にネイティブ データ用の描画コードを追加します。 }
また、画面への入力もこのビューウィンドウクラスが受け取ることが多いです。
入力と出力をビューウィンドウクラスに任せ、ウィンドウ全体の管理はフレームウィンドウクラスが行う、そのように処理を分けることで、プログラム全体を見やすくし、修正を簡単にすることができます。
ただ、どちらのクラスも、一番の基本クラスはCWndクラスです。
フレームウィンドウクラスの基本クラスはCFrameWndクラスですが、さらにその基本クラスにCWndクラスがあります。
ビューウィンドウクラスの基本クラスはCViewクラスですが、さらにその基本クラスにCWndクラスがあります。
つまり、共通の基本クラスとしてCWndクラスがあるということです。
CWndクラスはウィンドウの操作を行う基本的な機能を備えています。ですので、どんなウィンドウでも行う処理はCWndクラスのメンバ関数を呼び出して行います。CFrameWndクラスやCViewクラスは、それに加えてフレームウィンドウとしての処理やビューウィンドウとしての処理を持つということです。
この構造を踏まえて、どのようにしてMFCがウィンドウを構築するのか見ていきましょう。
フレームウィンドウとビューウィンドウ、2つのウィンドウを表示するため、MFC内部でのウィンドウの表示も2段階になります。
まずフレームウィンドウは、前回説明したウィンドウクラスの登録後にウィンドウを構築します。というか、ウィンドウクラスの登録と一緒に行います。
フレームウィンドウのウィンドウクラスの登録のためにCFrameWndクラスのLoadFrame()メンバ関数が呼び出されます。
この関数からCWndクラスのCreate()メンバ関数が呼ばれ、さらにその中からCWndクラスのCreateEx()メンバ関数を呼び出します。「ウィンドウの構築」はどのウィンドウでも必要な機能なので、基本クラスであるCWndクラスのメンバ関数に備わっています。
このメンバ関数の中でAPIのCreateWindowEx()関数を呼び出します。このAPIは、CreateWindow()関数と同じくウィンドウを構築するAPIです。そう、ウィンドウの構築にはAPIを使用します。
MFCの他の機能と同様、MFCも結局はAPIを使うわけです。「ウィンドウの構築」といったWindowsシステムに関わる処理は、APIを使用しなければ行えないわけです。
このようにしてフレームウィンドウが構築できたら、今度はビューウィンドウを構築します。
フレームウィンドウとビューウィンドウは「親子関係」にあります。そのような場合には、必ず「親ウィンドウが作られてから、子ウィンドウを作る」必要があります。CreateWindow()関数の第8引数に「親ウィンドウのウィンドウハンドル」を渡さなければならないので、それまでに親ウィンドウを構築できてないといけないわけです。
そのため、ビューウィンドウの作成は「フレームウィンドウが作られた」段階で行われます。
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
return 0;
}
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; return 0; }
フレームウィンドウが作られる直前、CMainFrameクラスのOnCreate()メンバ関数が呼び出されます。
そこからCFrameWndクラスのOnCreate()メンバ関数が呼び出され、流れ流れて同クラスのCreateView()メンバ関数が呼び出され、その中でCWndクラスのCreate()メンバ関数を呼び出します。
{
// 略
CWnd* pView = (CWnd*)pContext->m_pNewViewClass->CreateObject();
// 略
// views are always created with a border!
if (!pView->Create(NULL, NULL, AFX_WS_DEFAULT_VIEW,
CRect(0,0,0,0), this, nID, pContext))
CWnd* CFrameWnd::CreateView(CCreateContext* pContext, UINT nID) { // 略 CWnd* pView = (CWnd*)pContext->m_pNewViewClass->CreateObject(); // 略 // views are always created with a border! if (!pView->Create(NULL, NULL, AFX_WS_DEFAULT_VIEW, CRect(0,0,0,0), this, nID, pContext))
あとはフレームウィンドウの時と同じく、CWndクラスのCreate()メンバ関数からAPIのCreateWindowEx()関数が呼び出されて、ウィンドウが作成されます。
このようにして、SDIアプリケーションの場合にはフレームウィンドウとビューウィンドウが両方とも自動的に作成されます。
ドキュメント・ビュー・アーキテクチャーを使わず、AfxRegisterClass()関数等を使って独自にウィンドウクラスを登録した場合には、同じく自分でウィンドウを構築しなければなりません。つまりCWndクラスのCreate()メンバ関数を呼び出さなければならないということです。
この辺については、前回と同じく、このCodianの「MFCチップス」にある「常駐型アプリケーション」のページをお読みください。
ここまでがウィンドウの構築にあたります。
残りのShowWindow()関数とUpdateWindow()関数は、各アプリケーションのCWinApp派生クラス(C***Appクラス)のInitInstance()メンバ関数の中にそのまま書かれています。
// CMxA05_VC6App クラスの初期化
BOOL CMxA05_VC6App::InitInstance()
{
// 略
// メイン ウィンドウが初期化されたので、表示と更新を行います。
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
return TRUE;
}
///////////////////////////////////////////////////////////////////////////// // CMxA05_VC6App クラスの初期化 BOOL CMxA05_VC6App::InitInstance() { // 略 // メイン ウィンドウが初期化されたので、表示と更新を行います。 m_pMainWnd->ShowWindow(SW_SHOW); m_pMainWnd->UpdateWindow(); return TRUE; }
C***Appクラスが作られた時に、自動的にこれらの関数を呼び出す部分も作ってくれます。
でもよく見ると、どちらもCWndクラスのメンバ関数です。しかも引数が違います。APIの方はウィンドウハンドルを渡していますが、クラスの方は渡していません。この違いについて見てみましょう。
ウィンドウハンドルとCWndクラス
ここでもう一度、ウィンドウハンドルについてまとめます。
ウィンドウを構築したとき、つまりCreateWindow()関数を呼び出したとき、HWND型の値を受け取りました。これがウィンドウハンドルです。
HWND hWnd;
// ウィンドウを作製します。
hWnd = CreateWindow
// 略
// ウィンドウハンドルを取っておく変数。 HWND hWnd; // ウィンドウを作製します。 hWnd = CreateWindow // 略
ウィンドウはWindowsが作製します。Windowsが管理するメモリ上に、ウィンドウの「実体」が置かれています。そして、ユーザーやプログラマーがこの「実体」に直接触れることは許されません。
代わりに、Windowsは「ウィンドウハンドル」を返します。ウィンドウハンドルはその「実体」を指すポインタで、そのポインタを使うことで「どのウィンドウを操作するのか」を指し示すことができます。
「ウィンドウハンドル」の「ハンドル」は「車のハンドル」と同じ意味で、このハンドルを使うことでウィンドウを操作することができます。
ShowWindow()関数のようなウィンドウ操作系関数は、第1引数にそのウィンドウハンドルを渡します。こうすることで「どのウィンドウを操作するのか」ということを指定するわけです。WindowsはShowWindow()関数の呼び出しと共にウィンドウハンドルを受け取り、そのウィンドウハンドルが指すウィンドウの表示設定をWindowsが変えてくれるわけです。
さて、ShowWindow()関数のような「ウィンドウ操作系関数」は、実はとーってもたくさんあります。
MSDN Win32 API ウィンドウ関数一覧には、ウィンドウを操作するAPIのリストがあります。ここのページにあるだけで76関数もあります。
これらの関数のほとんどは、「操作するウィンドウ」を指定するために、第1引数にウィンドウハンドルを渡します。APIを使ってウィンドウ操作をする場合には、ウィンドウハンドルとこれらのAPIを使って、ウィンドウを操作することになります。
では、MFCではどうなのかというと、「クラス」を利用しています。
C++言語の中心的な機能、クラスとは、「変数に関数がくっついた」ようなものです。この機能を使うことで、変数に関数を結びつけ、変数を管理することができます。
具体的には、MFCではウィンドウハンドルをCWndクラスの中に閉じこめています。
CWndクラスは、中にm_hWndというメンバ変数を持っています。この変数にはウィンドウハンドルが入っています。このウィンドウハンドルは、やはりCreateWindow()といったAPIを使って取得したものです。
そして、CWndクラスには、このウィンドウハンドルを使用するAPIが内蔵されています。CWndクラスは「ウィンドウを操作するAPI」と同じ名前のメンバ関数を持ち、その中でAPIを呼び出しています。
先ほど説明したように、CWndクラスのShowWindow()メンバ関数は、同名のShowWindow()関数というAPIを使用しています。このメンバ関数は、APIのShowWindow()関数と同じ名前のメンバ関数を作って、その中でAPIを呼び出しているわけです。
そして、このメンバ関数内でAPIを呼び出すとき、第1引数にm_hWndメンバ変数を渡します。APIの時は関数と変数は別々でしたが、メンバ変数とメンバ関数として扱うことでセットで扱うことができるわけです。
APIの時にはウィンドウハンドルとAPIを別々に扱うことになります。
CWndクラスの場合には、ウィンドウハンドルをメンバ変数として持ち、メンバ関数と結びつけて使用します。
クラスとしてひとまとめにすることで、ウィンドウハンドルの管理が楽になります。ただの変数としてウィンドウハンドルを持つよりも、クラスの中に入れてメンバ関数を通して呼び出すようにすることで、チェック機能を働かせたりすることができます。そういった「付加価値」を加えるため、MFCではCWndというクラスを用意して、ウィンドウハンドルを管理しているわけです。
MFCは「Microsoft Foundation Class library」の略です。Foundationは「基礎」、MFCはAPIを使いやすくするため、基本的な機能を持つクラスを用意しているのです。
HWND型とCWndクラスの関係
最後に、HWND型とCWndクラスとをどう切り替えるか、という点について説明します。
MFCの場合、ウィンドウを扱う場合にはほとんどの場面でCWndクラスかその派生クラス(CFrameWndクラス、CMainFrameクラス、CViewクラス、C***Viewクラス等)を扱うことになります。
ところが、これらのクラスは「ウィンドウハンドルを使う全てのAPI」を網羅しているわけではありません。ちょっと特殊なAPIを使用する場合には、これらのCWndクラスの変数からウィンドウハンドルを取りだして渡す必要があります。
逆に、ウィンドウハンドルをCWndクラスの変数に入れてそれを使わなければならない場合もあります。MFCではウィンドウハンドルを直接使わずCWndクラスのポインタを使う事が多いため、その場合にはウィンドウハンドルをCWndクラスの変数内に入れて渡すことになります。また、普段MFCを使用している場合には、HWND型を使わず、MFCのクラスだけで統一したいということもあるでしょう。
そういった、両方の切り替えについて説明します。
まず憶えておいて欲しいのが、基本的にCWndクラス(とその派生クラス)はポインタで扱う、ということです。
たとえば「ビューウィンドウに該当するCView派生クラスの変数」を取得しようとすると「CViewクラスのポインタ」を取得することになります。「CViewクラスの変数」ではないことに注意してください(詳しい理由はデバイスコンテキストとハンドルで説明します)。
{
// フレームウィンドウにあたる、CMainFrameクラスのポインタを取得します。
CWinApp *pcWinApp = AfxGetApp();
CWnd *pcMainWnd = pcWinApp->GetMainWnd();
// pcMainWndが指す変数は、CMainFrameクラスの変数です。
// 最小化します。
pcMainWnd->ShowWindow( SW_MINIMIZE );
pcMainWnd->UpdateWindow();
}
void CMxA05_VC6View::OnTest1() { // フレームウィンドウにあたる、CMainFrameクラスのポインタを取得します。 CWinApp *pcWinApp = AfxGetApp(); CWnd *pcMainWnd = pcWinApp->GetMainWnd(); // pcMainWndが指す変数は、CMainFrameクラスの変数です。 // 最小化します。 pcMainWnd->ShowWindow( SW_MINIMIZE ); pcMainWnd->UpdateWindow(); }
このサンプルプログラムは、ビューウィンドウクラス(CMxA05_VC6Viewクラス)で、フレームウィンドウクラスのポインタを取得し、ShowWindow()メンバ関数を呼び出して最小化するプログラムです。
フレームウィンドウを取得したい場合、まずCWinAppクラスのポインタを取得する必要があります。SDIアプリケーションの場合、フレームウィンドウはかならずひとつだけなので、そのフレームウィンドウはCWinAppクラスの派生クラス(C***Appクラス)が管理しています(逆に言うと、アプリケーションはかならずひとつウィンドウを持つ必要があります。それは次回以降で説明します)。そのため、フレームウィンドウはCWinAppクラス経由で取得します。
図6で、フレームウィンドウがどのようにして作られるか説明しました。そのフレームウィンドウの作成後に、CMainFrameクラスの変数のポインタが、CWinApp派生クラスの中に格納されます。具体的には、その基本クラスのCWinThreadクラスのm_pMainWndメンバ変数に格納されます。
このm_pMainWndメンバ変数が指すCMainFrameクラスの変数、その中に「フレームウィンドウのウィンドウハンドル」が入っているので、「まずm_pMainWndが入っているCWinAppを取得する」→「CWinAppからCMainFrameを取得する」という手順を踏むわけです。
CWinAppクラスのポインタはAfxGetApp()関数で取得できます。これはMFCの「関数」です。メンバ関数ではないので単独で呼ぶことができます。APIでもありません。
CWinAppクラスのポインタが取得できたら、それを元にフレームウィンドウを管理するクラスのポインタを取得します。
これはCWinAppクラス(正確にはCWinThreadクラス)のGetMainWnd()メンバ関数を呼び出して取得します。このメンバ関数はm_pMainWndメンバ変数に格納しているポインタをそのまま返してくれます(CWndクラスのポインタとして返されますが、指している変数はCMainFrameクラスの変数です)。
このようにしてCWndクラスのポインタを取得できたら、サンプルプログラム7と同じようにAPIと同名のメンバ変数を呼び出せばそのウィンドウを操作することができます。ここでは最小化してみました。
このように、MFCでウィンドウを操作する場合には「CWndクラスのポインタ」を使うのが基本です。
さて、では話を戻して、今度はCWndクラスからウィンドウハンドルを取り出してみましょう。
サンプルプログラム9では、pcMainWndが指すCMainFrameクラスの変数が、フレームウィンドウを管理しています。ということは、pcMainWndを元にCMainFrameクラスの中にある、m_hWndメンバ変数が格納しているウィンドウハンドルを取得できればいいわけです。
そのためのメンバ関数がCWndクラスには用意されています。GetSafeHwnd()メンバ関数です。
{
// フレームウィンドウにあたる、CMainFrameクラスのポインタを取得します。
CWinApp *pcWinApp = AfxGetApp();
CWnd *pcMainWnd = pcWinApp->GetMainWnd();
// pcMainWndが指す変数は、CMainFrameクラスの変数です。
// ウィンドウハンドルを取得します。
HWND hWnd = pcMainWnd->GetSafeHwnd();
// 最小化します。
::ShowWindow( hWnd, SW_MINIMIZE );
::UpdateWindow( hWnd );
}
void CMxA05_VC6View::OnTest2() { // フレームウィンドウにあたる、CMainFrameクラスのポインタを取得します。 CWinApp *pcWinApp = AfxGetApp(); CWnd *pcMainWnd = pcWinApp->GetMainWnd(); // pcMainWndが指す変数は、CMainFrameクラスの変数です。 // ウィンドウハンドルを取得します。 HWND hWnd = pcMainWnd->GetSafeHwnd(); // 最小化します。 ::ShowWindow( hWnd, SW_MINIMIZE ); ::UpdateWindow( hWnd ); }
このサンプルプログラムはサンプルプログラム9と同じことをしていますが、ShowWindow()関数とUpdateWindow()関数はAPIを呼び出しています(頭に「::」が付いていますが、これは「メンバ関数ではなく普通の関数を呼び出す」時に付けるものです)。APIなので、当然ウィンドウハンドルを渡す必要があります。
このウィンドウハンドルは、CWndクラスのGetSafeHwnd()メンバ関数で取得できます。GetSafeHwnd()メンバ関数を呼び出すと、戻り値としてウィンドウハンドルが返されるので、それをHWND型の変数で受け取り、それをAPIに渡せばいいわけです。
では逆に、HWND型のウィンドウハンドルがある場合にそれをCWndクラスの変数に入れるにはどうすればいいのかというと、これはCWndクラスのFromHandle()メンバ関数を使用します。
{
// フレームウィンドウにあたる、CMainFrameクラスのポインタを取得します。
CWinApp *pcWinApp = AfxGetApp();
CWnd *pcMainWnd = pcWinApp->GetMainWnd();
// pcMainWndが指す変数は、CMainFrameクラスの変数です。
// ウィンドウハンドルを取得します。
HWND hWnd = pcMainWnd->GetSafeHwnd();
// ウィンドウハンドルを元にCWndクラスのポインタを取得します。
CWnd *pcWnd = CWnd::FromHandle( hWnd );
// 最小化します。
pcWnd->ShowWindow( SW_MINIMIZE );
pcWnd->UpdateWindow();
}
void CMxA05_VC6View::OnTest3() { // フレームウィンドウにあたる、CMainFrameクラスのポインタを取得します。 CWinApp *pcWinApp = AfxGetApp(); CWnd *pcMainWnd = pcWinApp->GetMainWnd(); // pcMainWndが指す変数は、CMainFrameクラスの変数です。 // ウィンドウハンドルを取得します。 HWND hWnd = pcMainWnd->GetSafeHwnd(); // ウィンドウハンドルを元にCWndクラスのポインタを取得します。 CWnd *pcWnd = CWnd::FromHandle( hWnd ); // 最小化します。 pcWnd->ShowWindow( SW_MINIMIZE ); pcWnd->UpdateWindow(); }
CWndクラスのstaticメンバ関数のひとつ、FromHandle()メンバ関数にウィンドウハンドルを渡すと、そのウィンドウハンドルが入ったCWndクラスのポインタが返されるので、これをサンプルプログラム9と同じように使用すればいいわけです。
このように、CWndクラスの変数を直接受け取るのではなく、やはりポインタとして取得します。これはMFCの多くのクラスで採用している方法なので、この形に慣れてください。
あと、FromHandle()メンバ関数は「staticメンバ関数」ですので、「CWnd::FromHandle」という形式で呼び出すという点に注意してください。
標準的なアプリケーションの場合には、HWND型とCWndクラスが混在すると分かりづらくなるので、どちらかに統一した方がいいでしょう。MFCを使用している場合には、HWND型はあまり使わない方が分かりやすくなると思います。
ただ、特殊なユーティリティアプリケーションの場合、MFCにないAPIを使う事が多くなると思います。そのようなアプリケーションの場合、無理にCWndクラスに入れたりするとわけが分からなくなる可能性があるので、HWND型中心に処理した方が分かりやすいと思います。
まとめ
このあたりまでくれば、「MFCの中でAPIが使われている、MFCはAPIを包み込んでいる」ということは分かってもらえたと思います。
次回は、ウィンドウシステムの中でも分かりづらい「メッセージループ」について見ていきます。