第三話 早くもクラス作成、取りあえず画像表示コントロールを作ってみる
そもそもクラス使う必要ないんでね?な人のための説得工作
そんではWin32で作ったゲームとEXCELとメモ帳とデフラグとDOS窓の共通項を探してみましょう。
答え書いちゃうと、Windowアプリであり、ハンドルとデバイスコンテキストを持っているという事なんですな。(.netだとその辺り少し違うんだが、それはそれ)となると、構築する手順も同じような物なので、ゴリゴリせずにそれを共通化して差分だけで何とかしたくなるのが人情という物。それがクラス化という事ですな。(MFCではWindowコントロールの粗方がCWndクラス派生で作られている)
講座でやたらとWindowクラスという単語が出ましたが、このWindowクラス(CWndクラス)、ハンドルとデバコン持っているのは当然として、描画やらサイズ変更やら、メッセージ通信やらメッセージドリブンやらとやたらと多機能に出来ています。もちろんWindowメッセージは粗方網羅。また派生して差分を作る事も可能なので、構築手続きはMFCに丸投げして描画だけ美味しいので自分でやるという事が出来る訳です。(クラスのメリットには共通インタフェースでメモリを投げると自動的に解析するコンテナ作れたりとか、 一連の処理をクラス構築~破棄で閉じれるというのもあるけど、一番は共通化だと思う)
ではさっそく派生クラスを作ってみましょう。(この程度のアプリではゼロからクラス作る必要がないのよ)
CWnd派生で画像を表示するコントロールクラスを作ってみる
これでクラスは完成しました。煮て食うなり焼いて食うなりができるCWndと同等のクラスが出来た訳です、これを派生と言います。MFCフォルダ漁ってCWndのソースコピってファイル名とクラス名変えてカスタマイズなんてダサイ事しなくても良いんですね(*1)。そもそもそれじゃ派生にならんし。
(*1):クラスを理解してなかったVC++2の頃やらかした。
次にファイル読み込みをクラスに実装します。広義で言うとクラスにアクセスするための独自のメンバメソッドを組み込む訳ですね。こうする事で処理をクラス内に閉じる事が出来て処理のスパゲティ化や悪名高いグローバル変数を避けられます。
該当CPPファイルにメソッドが出来れば成功です。
void CGDIStatic::loadPicture(CStringW strFileName) { }
それでは読み込み処理を実装しましょう。こんな感じになります。読み込んでコピーしてBitmapクラスに転送するのが処理の全てとなります。
loadPicture()はメソッド内全部です
#include "StdAfx.h" #include "Chromakey.h" #include "GDIStatic.h" CGDIStatic::CGDIStatic(void) { m_dMagni = 0; m_image = NULL; } CGDIStatic::~CGDIStatic(void) { m_image = NULL; } void CGDIStatic::loadPicture(CStringW strFileName) { BOOL bRet = FALSE; // 画像ファイルを取得(ファイルロックがかかる) Image* imageOrgn = Bitmap::FromFile(strFileName); Status st = imageOrgn->GetLastStatus(); if(st == Ok){ CSize szImage(imageOrgn->GetWidth(), imageOrgn->GetHeight()); // 指定サイズのBitmapを作成 delete m_image; m_image = NULL; m_image = new Bitmap(szImage.cx, szImage.cy); // 仮想グラフィックス領域作成 Graphics* grpTmp = Graphics::FromImage(m_image); // imageOrgnをimageにプリントする grpTmp->DrawImage( imageOrgn, 0, 0, szImage.cx, szImage.cy); // 仮想グラフィックス破棄 delete grpTmp; grpTmp = NULL; // imageOrgnを開放(ファイル開放) delete imageOrgn; imageOrgn = NULL; // 自身のピクセルサイズ取得 CRect rt; GetClientRect(rt); // 比率計算 double dWid1 = (double)m_image->GetWidth(); double dWid2 = (double)rt.Width(); double dMagniW = dWid1 / dWid2; double dHgt1 = (double)m_image->GetHeight(); double dHgt2 = (double)rt.Height(); double dMagniH = dHgt1 / dHgt2; // 大きいほうを使用 m_dMagni = max(dMagniW, dMagniH); } else { // imageOrgnを開放(ファイル開放) delete imageOrgn; imageOrgn = NULL; // 旧画像を解放 delete m_image; m_image = NULL; } // 再描画 RedrawWindow(); }
ヘッダに画像格納クラスであるBitmapクラスと倍率格納変数も実装しましょう。
#pragma once #include "afxwin.h" class CGDIStatic : public CWnd { public: CGDIStatic(void); ~CGDIStatic(void); void loadPicture(CStringW strFileName); protected: Bitmap* m_image; double m_dMagni; // 1倍(オブジェクト全体描画)時の比率 };
これでファイルの読み込みが出来るようになりました。GDIのヘッダ解析してHBITMAP展開して、と比べるとだいぶ簡単ですね。ちなみにnew演算子がC++のメモリ確保、delete演算子がメモリ解放。VC++では一部の例外除いてmallocやfreeは使用しないほうが無難です。(一部malloc等で確保したメモリでないとアクセスできないAPIがあるため)
では次に描画処理を実装しましょう、CWndのフレームワークには描画メッセージメソッドがあるのでそれをオーバーライドして使います。この辺り結構C++です、理屈を言い始めると落ちこぼれる人多数です。
クラスビューでクラス名を選択して、クラスプロパティのメッセージをクリックして下さい、Win32で見慣れたメッセージがずらずら出てくるのでコンボタブをクリックしてOnPaint()を追加して下さい。
void CGDIStatic::OnPaint() { CPaintDC dc(this); // device context for painting // TODO: ここにメッセージ ハンドラ コードを追加します。 // 描画メッセージで CWnd::OnPaint() を呼び出さないでください。 }
追加されましたね?これでCWndの定型描画処理だったOnPaint()がユーザの自由に出来ます。これがオーバーライドで同じ要領でオーバーライドメソッドのWindowProc()を呼び出すなんて事も出来たりします。 では中身を実装。
void CGDIStatic::OnPaint() { CPaintDC dc(this); // device context for painting // TODO: ここにメッセージ ハンドラ コードを追加します。 // 描画メッセージで CWnd::OnPaint() を呼び出さないでください。 CRect rt; GetClientRect(rt); if(!m_image){ dc.FillSolidRect(rt, ::GetSysColor(COLOR_3DFACE)); } else { // DCをGDI+にする Graphics* graphics = Graphics::FromHDC(dc.m_hDC); // 描画サイズ Rect rtOut; rtOut.X = 0; rtOut.Y = 0; rtOut.Width = (INT)((double)m_image->GetWidth() / m_dMagni); rtOut.Height = (INT)((double)m_image->GetHeight() / m_dMagni); // オブジェクト全体のImageを描画していない矩形を取得 CRect rtcOut(0,0, rtOut.Width, rtOut.Height); CRect rtFill; rtFill.SubtractRect(rt, rtcOut); // 背景塗りつぶし dc.FillSolidRect(rtFill, ::GetSysColor(COLOR_APPWORKSPACE)); // DCにImageを転送 graphics->DrawImage( m_image, rtOut, 0, 0, m_image->GetWidth(), m_image->GetHeight(), UnitPixel); } }
これで描画までは仕込みました。
作ったクラスを使おう
アクセスをProtected、変数名をm_GDIStatic1として完了を押してください。ダイアログクラスのヘッダファイルに変数が追加されますのでこれを書き換えます。
// ChromakeyDlg.h : ヘッダー ファイル // #pragma once #include "afxwin.h" #include "GDIStatic.h" // CChromakeyDlg ダイアログ class CChromakeyDlg : public CDialog { // コンストラクション public: CChromakeyDlg(CWnd* pParent = NULL); // 標準コンストラクタ // ダイアログ データ enum { IDD = IDD_CHROMAKEY_DIALOG }; protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV サポート // 実装 protected: HICON m_hIcon; // 生成された、メッセージ割り当て関数 virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); afx_msg void OnBnClickedButtonload(); DECLARE_MESSAGE_MAP() protected: CGDIStatic m_GDIStatic1; };
おそらくここまででもキー叩く量はWin32でダイアログ作るよりも少ないはずです。
それでは今回はここまで。