・概要
プッシュボタンと言えば長方形と相場は決まっていて、Visual Studioの リソースエディタでボタンを貼り付けると長方形のボタンが親ウィンドウ(ダイアログ)に 表示されます。あとは作成されたボタンのプロパティウィンドウから通知メッセージに対する ハンドラを作成し、必要な処理を実装するだけです。
さらにCButtonクラスを派生させてサブクラスにするとより高度な処理が
可能になります。この場合はコンストラクタを呼び出したあと、Create関数を呼び出して
ボタンオブジェクト(ウィンドウオブジェクト)をつくりCButtonの派生クラスに結び付けます。
CButton::Create関数の宣言は
virtual BOOL Create( LPCTSTR lpszCaption, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );
となっています。ここで注目する点は第三引数の const RECT& rectで、ここで 指定した位置と大きさでボタンを作成します。型がRECTなので当然長方形ということになります。
いずれにしてもボタンは長方形で、 今回のお題の三角のボタンはできません。ではどうするかというとCButtonクラスを使うのは 当然ですが大まかには以下のような手順になるかと思います。
SetWindowRgnでウィンドウリージョンを設定するとその外側をシステムは 描画しないので三角形のボタンが描画されますが、これだけではボタンクリック時の 凹凸がうまく表現できません。そこでオーナードローで自分で描画します。
これが面倒で、ボタンが押された状態、押されていない状態、使用不可の状態、 フォーカスがある状態などすべてをDrawItem内で自分で描画しないといけないです。今回は ボタンの凹凸とフォーカスがあたった状態のみを対象にします。
・準備
実際にプロジェクトで三角ボタンのダイアログをつくってみます。
プロジェクトの種類(P)ペイン : Visual C++ テンプレートペイン(T) : MFCアプリケーション プロジェクト名 : Test(任意)でOKを押す。
<アプリケーションの種類ダイアログ> アプリケーションの種類 :ダイアログベース(D) <ユーザインターフェイス機能ダイアログ> メインフレームのスタイル :すべてのチェックボックスをはずす <高度な機能> 高度な機能 :すべてのチェックをはずすCTestAppクラスとCTestDlgクラスが生成されるので完了をクリック。
クラスウィザードを起動。 カテゴリ(C) : MFC テンプレート : MFCクラス で 追加(A)をクリック。 基本クラス(B) : CButton クラス名 : CTriangleButtonでCButtonの派生クラスを作成してください。
・CTriangleButton実装
本題の三角ボタンを表すCButtonクラスの派生クラス、 CTriangleButtonの実装を行います。実装内容の要点は
です。
ヘッダファイルを以下のように記述してください。
//TriangleButton.hヘッダファイル #pragma once // CTriangleButton class CTriangleButton : public CButton { DECLARE_DYNAMIC(CTriangleButton) public: CTriangleButton(); //コンストラクタ virtual ~CTriangleButton(); //デストラクタ protected: CPoint m_point[3]; //三角形の各頂点 public: BOOL Create(LPCTSTR lpszCaption, //ボタン作成 CRect rect, CWnd *pParent, UINT nID); virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); //オーナードロー afx_msg void OnBnClicked(); //ボタンクリック protected: DECLARE_MESSAGE_MAP() }; |
実装の中心はオーバーロードしたCreate関数と オーバーライドしたDrawItem関数です。二つの関数の実装を以下のように行ってください。
// TriangleButton.cpp : 実装ファイル // #include "stdafx.h" #include "Test.h" #include "TriangleButton.h" // CTriangleButton IMPLEMENT_DYNAMIC(CTriangleButton, CButton) //コンストラクタ CTriangleButton::CTriangleButton() { } //デストラクタ CTriangleButton::~CTriangleButton() { } BEGIN_MESSAGE_MAP(CTriangleButton, CButton) ON_CONTROL_REFLECT(BN_CLICKED, &CTriangleButton::OnBnClicked) END_MESSAGE_MAP() //ボタン構築 & リージョン作成 BOOL CTriangleButton::Create(LPCTSTR lpszCaption, CRect rect, CWnd *pParent, UINT nID) { CRgn rgn; m_point[0].SetPoint(rect.Width() / 2, 0); m_point[1].SetPoint(0, rect.Height()); m_point[2].SetPoint(rect.Width(), rect.Height()); while (TRUE) { if (! rgn.CreatePolygonRgn(m_point, 3, WINDING)) break; if (! CButton::Create(lpszCaption, WS_VISIBLE | WS_TABSTOP | WS_CHILD | BS_PUSHBUTTON | BS_OWNERDRAW, rect, pParent, nID)) break; if (! SetWindowRgn(rgn, TRUE)) break; return TRUE; } if (rgn.GetSafeHandle() != NULL) rgn.DeleteObject(); if (GetSafeHwnd() != NULL) DestroyWindow(); return FALSE; } //オーナードロー void CTriangleButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) { CDC *pDC = CDC::FromHandle(lpDrawItemStruct->hDC); CRect rect = lpDrawItemStruct->rcItem; BOOL bSelect = lpDrawItemStruct->itemState & ODS_SELECTED; BOOL bFocus = lpDrawItemStruct->itemState & ODS_FOCUS; //ボタンの描画 CBrush *pOrigBrush = NULL; CBrush brush; brush.CreateSolidBrush(bSelect ? RGB(0, 0, 240) : RGB(0, 0, 255)); pOrigBrush = pDC->SelectObject(&brush); pDC->Polygon(m_point, 3); pDC->SelectObject(pOrigBrush); brush.DeleteObject(); //境界の描画 CPen *pOrigPen = NULL; CPen lpen(PS_SOLID, 2, ::GetSysColor(COLOR_BTNFACE)); CPen dpen(PS_SOLID, 2, RGB(0, 0, 100)); pDC->MoveTo(m_point[0]); pOrigPen = pDC->SelectObject(bSelect ? &lpen : &dpen); pDC->LineTo(m_point[1]); pDC->SelectObject(bSelect ? &dpen : &lpen); pDC->LineTo(m_point[2]); pDC->SelectObject(bSelect ? &lpen : &dpen); pDC->LineTo(m_point[0]); pDC->SelectObject(pOrigPen); lpen.DeleteObject(); dpen.DeleteObject(); //ボタン表題描画 CFont *pOrigFont = NULL; CFont font; font.CreatePointFont(80, _T("MS Gothic")); pOrigFont = pDC->SelectObject(&font); CString str; GetWindowText(str); CSize size(pDC->GetTextExtent(str)); CPoint point = rect.CenterPoint(); point.Offset(-1 * size.cx / 2, bSelect ? size.cy + 10 : size.cy + 9); CRect txtrect(point, size); pDC->SetBkMode(TRANSPARENT); pDC->DrawText(str, &txtrect, DT_CENTER); pDC->SelectObject(pOrigFont); font.DeleteObject(); //フォーカス枠描画 if (bFocus) { CPoint point[3]; point[0] = m_point[0] + CPoint(0, bSelect ? 8 : 7); point[1] = m_point[1] + CPoint(bSelect ? 8 : 7, -5); point[2] = m_point[2] + CPoint(bSelect ? -8 : -7, -5); CPen pen(PS_DOT, 1, RGB(0, 0, 0)); pOrigPen = pDC->SelectObject(&pen); pOrigBrush = (CBrush*)pDC->SelectStockObject(NULL_BRUSH); pDC->Polygon(point, 3); pDC->SelectObject(pOrigPen); pDC->SelectObject(pOrigBrush); pen.DeleteObject(); } } //ボタンクリック void CTriangleButton::OnBnClicked() { TRACE(_T("CTriangleButton::OnBnClicked\n")); } |
ちょっと長いので簡単に説明しておきます。
1.Create関数
※SetWindowRgnの解説を読むと「必要がなくなると、システムが自動的に
リージョンハンドルを削除します」との記述があるので、このAPIが成功したら、最後に後始末を
する必要がないように思われます(rgn.DeleteObject()を実行しなくてもよい)。
ということで、CTriangleButtonのメンバ変数に持たずにローカル変数で処理してもGDIリソースの
メモリリークは起こらないかと思います。(間違いでしたらご連絡いただくとありがたいです)
2.DrawItem関数
こちらの関数はちょっと長いので難しそうですが、 細かく?ボタンを描いているだけで実はそれほど難解ではないです。 注目してほしい点は1点で、最初の4行だけです。
CDC *pDC = CDC::FromHandle(lpDrawItemStruct->hDC); //(A) CRect rect = lpDrawItemStruct->rcItem; //(B) BOOL bSelect = lpDrawItemStruct->itemState & ODS_SELECTED; //(C) BOOL bFocus = lpDrawItemStruct->itemState & ODS_FOCUS; //(D)
ボタンの描画が必要になるとフレームワークからこの関数が呼び出されます。その際、引数に DRAWITEMSTRUCT構造体へのポインタ(lpDrawItemStructを受け取っています。詳しくは MSDNをご覧いただきたいですが、この構造体の各メンバから必要な情報を(A)から(D)に受け取って います。
(A)はデバイスコンテキストのハンドルからCDCの一時オブジェクトへのポインタを得て、 これで以下すべてのボタン描画を行います。
(B)は描画する四角形を取得します。(実際にはリージョンを設定しているので、 その外側は描画されません。
(C)はボタンが押されているか押されていないかを判断します。
(D)はボタンがフォーカスを持っているかどうかを判断します。
ここから以下は上記を使ってボタンが押された状態、押されてない状態とフォーカスを 持った状態、持っていない状態ごとにボタン、境界の影、表題を描いているだけです。 ただできるだけ簡略化したつもりなので実際をみてみると非常に雑です (私のセンスがないだけです)。なのでボタンらしく振舞うには影のつけ方などを 工夫する必要があるでしょう。
最後に上記サンプルではプロパティシートからボタンクリックのメッセージハンドラ (OnBnClicked関数)を実装しています。実装といってもクリックしたことをデバッガに 表示しているだけです。
・CTriangleButtonの使用
では実際にCTriagleButtonを使ってみます。
アプリケーションウィザードでメインウィンドウ(CTestDlgダイアログ)のスケルトンができていますので、
そこに三角ボタンを作成します。CTestDlgのヘッダに以下のように記述して、CTriangleButtonの
メンバ変数を加えます。
// TestDlg.h : ヘッダーファイル #pragma once #include "TriangleButton.h" //三角ボタンのヘッダをインクルード // CTestDlg ダイアログ class CTestDlg : public CDialog { // ・ // ・ protected: CTriangleButton m_TrigButton; //三角ボタン // ・ // ・ }; |
これだけではダイアログにボタンはつくられないので、CTestDlg::OnInitDialogで Create関数を呼び出すと三角ボタンが構築されます。
//TestDlg.cpp : 実装ファイル BOOL CTestDlg::OnInitDialog() { CDialog::OnInitDialog(); SetIcon(m_hIcon, TRUE); // 大きいアイコンの設定 SetIcon(m_hIcon, FALSE); // 小さいアイコンの設定 CRect rect; GetWindowRect(&rect); CPoint point = rect.CenterPoint(); point.Offset(-50, -50); m_TrigButton.Create(_T("三角ボタン"), CRect(point, CSize(100, 100)), this, 1); return TRUE; // フォーカスをコントロールに設定した場合を除き、TRUE を返します。 } |
上記はダイアログのクライアント領域の中心に縦、横それぞれ100の四角形に 収まる三角形のボタンを作成しています。実行すると以下のようになります。