ダイアログアプリケーションでも簡単な GUI 操作を行うことができますが、色々な機能を追加していくとだんだんと操作項目が増えてダイアログアプリケーションでは扱い難くなってくると思います。更に一歩進めてドキュメントアプリケーションとして作成してみましょう。
ドキュメントアプリケーションには下記の2つに分類されます。
シングルドキュメントインターフェイス(SDI:Single Document Interface)
親ウィンドウのようなものを持たず、各ウィンドウにメニューやツールバーなどを表示する方式です。
マルチドキュメントインターフェイス(MDI:Multi Document Interface)
親ウィンドウ内に複数の子ウィンドウを表示して管理する方式です。
ここでは MDI アプリケーションを作成してみます。
プロジェクトの作成までは一部のオプション設定が異なるだけでほとんどの操作はダイアログアプリケーションのときと同じですので、説明図を入れるのは操作の異なる部分だけにします。
1. メニューの[ファイル(F)]-[新規作成(N)]-[プロジェクト(P)…]を選択し、『新しいプロジェクト』ダイアログを開きます。
2. フィルタを "C++", "Windows", "デスクトップ" に変更します。
3. テンプレートリストから "MFCアプリ" を選択します。
4. 「次へ(N)」ボタンを押下すると、『新しいプロジェクトを構成します』ダイアログが開きます。
※ 1. ~ 4. の図は『ダイアログに描画する』と同じなので省略します。
5. 「プロジェクト名(J)」に"MFCApplication2"を入力します。
6. 「場所(L)」にプロジェクトを保存するフォルダを入力します。
7. 「ソリューションとプロジェクトを同じディレクトリに配置する(D)」のチェックボックスをオンにします。
8. 「作成(C)」ボタンを押下します。
ダイアログアプリケーションのときと同じく、ここではクラスウィザードでの設定を先に一気に行います。
1. メニューの[プロジェクト(P)]-[クラスウィザード(Z)…]を選択し、『クラスウィザード』ダイアログを開きます。
2. 「クラス名(N)」から"CMFCApplication2View"を選択します。
※ ドキュメントアプリケーションでは OpenGL 描画を View 内で行います。
3. 「メンバー変数」タブを選択します。
4. 「カスタムの追加(U)…」ボタンを押下します。
5. 『メンバー変数の追加』ダイアログが開きます。
6. 「変数の型(T)」コンボボックスに"HGLRC"を入力します。
7. 「変数名(N)」エディットボックスに"m_hGLRC"を入力します。
8. 「アクセス」のラジオボタンから"プライベート(V)"を選択します。
9. 「OK」ボタンを押下します。
続けてクラスウィザードを使い、メソッドを追加します。
10. 「メソッド」タブを選択します。
11. 「メソッドの追加(A)…」ボタンを押下します。
12. 『関数の追加』ダイアログが開きます。
13. 「関数名(U)」エディットボックスに"InitGL"を入力します。
14. 「戻り値の型(Y)」コンボボックスから"bool"を選択します。
15. 「アクセス(A)」コンボボックスから"private"を選択します。
16.「OK」ボタンを押下します。
17.「メソッドの追加(A)…」ボタンを押下し、『関数の追加』ダイアログを開きます。
18. 「関数名(U)」エディットボックスに"DrawGL"を入力します。
19. 「戻り値の型(Y)」コンボボックスから"void"を選択します。
20. 「アクセス(A)」コンボボックスから"private"を選択します。
21.「OK」ボタンを押下します。
22.「メソッドの追加(A)…」ボタンを押下し、『関数の追加』ダイアログを開きます。
23. 「関数名(U)」エディットボックスに"SetDCPixelFormat"を入力します。
24. 「戻り値の型(Y)」コンボボックスから"bool"を選択します。
25. 「アクセス(A)」コンボボックスから"private"を選択します。
26. 「+」ボタンを押下します。
27.「パラメータ(P)」リストボックスに"HDC hdc"を入力します。
28.「OK」ボタンを押下します。
最後にクラスウィザードを使って、ハンドラーを追加します。
29. 「メッセージ」タブを選択します。
30.「メッセージ(S)」リストボックスから"WM_CREATE"を選択します。
31.「ハンドラーの追加(A)」ボタンを押下すると、「既存のハンドラー(H)」に"OnCreate"が追加されます。
32.「メッセージ(S)」リストボックスから"WM_SIZE"を選択します。
33.「ハンドラーの追加(A)」ボタンを押下すると、「既存のハンドラー(H)」に"OnSize"が追加されます。
34.「OK」ボタンを押下します。
正しく実行できていれば【MFCApplication2View.h】,【MFCApplication2View.cpp】のように変更されています。
gl*, glu*, glut* などの OpenGL 関連の API を呼び出すためのヘッダーファイルを追加しておきます。
// MFCApplication2View.cpp : CMFCApplication2View クラスの実装
//
#include "pch.h"
#include "framework.h"
#include <GL/glut.h>
// SHARED_HANDLERS は、プレビュー、縮小版、および検索フィルター ハンドラーを実装している ATL プロジェクトで定義でき、
// そのプロジェクトとのドキュメント コードの共有を可能にします。
#ifndef SHARED_HANDLERS
#include "MFCApplication2.h"
#endif
追加したメンバー変数 m_hGLRC の初期化処理を追加します。
CMFCApplication2View::CMFCApplication2View() noexcept
: m_hGLRC(NULL)
{
// TODO: 構築コードをここに追加します。
}
ビューが破棄されるときにレンダリングコンテキストに使用したデバイスコンテキストの解放とレンダリングコンテキストの削除を行います。
CMFCApplication2View::~CMFCApplication2View()
{
wglMakeCurrent(NULL, NULL); // free current context
wglDeleteContext(m_hGLRC); // Delete rendering context
}
ビューの描画が必要になったときに呼び出される関数です。
void CMFCApplication2View::OnDraw(CDC* /*pDC*/)
{
CMFCApplication2Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
// TODO: この場所にネイティブ データ用の描画コードを追加します。
// OpenGL描画
DrawGL();
}
この関数で OpenGL 関連の初期化を行います。
bool CMFCApplication2View::InitGL()
{
// TODO: ここに実装コードを追加します.
// デバイスコンテキスト取得
CClientDC dc(this);
// ピクセルフォーマット設定
if (!SetDCPixelFormat(dc.m_hDC)) {
return false;
}
// レンダリングコンテキスト生成
m_hGLRC = wglCreateContext(dc.m_hDC);
if (!m_hGLRC) {
return false;
}
// カレントコンテキスト設定
if (!wglMakeCurrent(dc.m_hDC, m_hGLRC)) {
return false;
}
glClearColor(1.0, 1.0, 1.0, 1.0);
return true;
}
この関数で OpenGL 描画を行います。ダイアログアプリケーションと異なり、OpenGL 描画が必要なビューが複数存在する場合があります。そのため、描画を始める前などでどのデバイスコンテキストに描画を行うか指定(wglMakeCurrent)する必要があります。
void CMFCApplication2View::DrawGL()
{
// TODO: ここに実装コードを追加します.
// カレントコンテキスト設定
CClientDC dc(this);
wglMakeCurrent(dc.m_hDC, m_hGLRC);
glClear(GL_COLOR_BUFFER_BIT);
// X, Y, Z軸線描画
glBegin(GL_LINES);
glColor3d(1.0, 0.0, 0.0);
glVertex3d(-100.0, 0.0, 0.0);
glVertex3d(100.0, 0.0, 0.0);
glEnd();
glBegin(GL_LINES);
glColor3d(0.0, 1.0, 0.0);
glVertex3d(0.0, -100.0, 0.0);
glVertex3d(0.0, 100.0, 0.0);
glEnd();
glBegin(GL_LINES);
glColor3d(0.0, 0.0, 1.0);
glVertex3d(0.0, 0.0, -100.0);
glVertex3d(0.0, 0.0, 100.0);
glEnd();
glFlush();
SwapBuffers(dc.m_hDC);
}
この関数では OpenGL 描画を行うデバイス(ここではビュー)のピクセルフォーマットを設定します。
bool CMFCApplication2View::SetDCPixelFormat(HDC hdc)
{
// TODO: ここに実装コードを追加します.
// デバイスコンテキストに適したピクセルフォーマットを設定する
static PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR), // Size of this structure
1, // Version number
PFD_DRAW_TO_WINDOW | // Flags
PFD_SUPPORT_OPENGL |
PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA, // RGBA pixel values
24, // 24-bit color
0, 0, 0, 0, 0, 0, // Don't care about these
0, 0, // No alpha buffer
0, 0, 0, 0, 0, // No accumulation buffer
32, // 32-bit depth buffer
0, // No stencil buffer
0, // No auxiliary buffers
PFD_MAIN_PLANE, // Layer type
0, // Reserved (must be 0
0, 0, 0 // No layer masks
};
const int nPixelFormat = ChoosePixelFormat(hdc, &pfd);
if (!SetPixelFormat(hdc, nPixelFormat, &pfd)) {
return false;
}
return (DescribePixelFormat(hdc, nPixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &pfd) != 0);
}
ビューが作成されるときの処理をここに記述します。
int CMFCApplication2View::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
// TODO: ここに特定な作成コードを追加してください。
// OpenGL初期化
if (!InitGL()) {
return -1;
}
return 0;
}
[解説]
int CWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
ウィンドウが作成されてから表示される前に呼び出されます。
ビューのサイズが変更されるときの処理をここに記述します。
void CMFCApplication2View::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
// TODO: ここにメッセージ ハンドラー コードを追加します。
// カレントコンテキスト設定
CClientDC dc(this);
wglMakeCurrent(dc.m_hDC, m_hGLRC);
// 描画領域設定
glViewport(0, 0, cx, cy);
glLoadIdentity();
glOrtho(-cx / 2.0, cx / 2.0, -cy / 2.0, cy / 2.0, -1.0, 1.0);
}
[解説]
void CWnd::OnSize(UINT nType, int cx, int cy)
ウィンドウのサイズが変更されたときに呼び出されます。引数の nType にはサイズ変更の種類、cx, cy にはクライアント領域の新しい幅と高さが渡されます。
このプログラムでは(ダイアログに描画する のときと同様にかなり手を抜いて) X, Y, Z 軸方向に長さ200の直線を描画しています。MDI アプリケーションではメニューを追加して直線や円を描画するコマンドを作成したり、視点情報を設定するコマンドを追加するなどダイアログよりも多彩な機能が追加しやすくなりますので、色々と試してください。