2010. 11. 1. 10:19

CBitmapButton의 투명(Transparent)와 Hover 지원하기

CBitmapButton의 최대 단점중 하나는 (혹은 많은 요구사항중 하나는),

  • 투명 처리 (Transparent)
  • Hover Button 처리 (마우스 커서가 위(OnMouse)일때, 다른 Bitmap 처리하기)

입니다.
물론, 더 확장된 내용은 CodeProject등에 CButtonst등이 있는걸로 아는데, 굉장히 복잡도가 높습니다.

그래서, 한번 MFC의 CBitmapButton class를 살펴보니, CBitmapButton::DrawItem(...)이 재정의 된걸 확인하였습니다. 아차~! CBitmapButton::DrawItem(...)의 BitBlt(...)를 BitBltTransparent(...)로 변경하면 쉽게 투명 처리가 되겠구나 했습니다.

// CBitmapButton - push-button with 1->4 bitmap images
class CBitmapButton : public CButton
{
	...
	virtual void DrawItem(LPDRAWITEMSTRUCT lpDIS);
};

void CBitmapButton::DrawItem(LPDRAWITEMSTRUCT lpDIS)
{
	...
	pDC->BitBlt(rect.left, rect.top, rect.Width(), rect.Height(),
		&memDC, 0, 0, SRCCOPY);
	...
}


즉, 위의 BitBlt(...)를 BitBltTransparent(...)로 변경하면 작업은 끝나는 겁니다.
그리고, Hover Button을 위해서는, WM_MOUSELEAVE/_TrackMouseEvent(...)를 통해 쉽게 해결이 됩니다.
그럼 CBitmapButtonTrans class의 사용예는 다음과 같습니다.

// header file
#include "bitmapbuttontrans.h"
class C~ : public CView / CDialog / CMainFrame / ... 
{
...
private:
	CBitmapButtonTrans	m_cBtn;
}

// c++ file

int C~::OnCreate(LPCREATESTRUCT lpCreateStruct) // OnCreate, OnInitDialog Timing
{
	if (C~::OnCreate(lpCreateStruct) == -1)
		return -1;
	...
	m_cBtn.Create(TEXT(""), WS_CHILD|WS_VISIBLE|BS_OWNERDRAW, CRect(x,y,xx,yy), this, -1);
	m_cBtn.LoadBitmaps(IDB_BITMAP_1, IDB_BITMAP_2, IDB_BITMAP_3, IDB_BITMAP_4);
	m_cBtn.SizeToContent();
	m_cBtn.SetHoverBitmapID(IDB_BITMAP_5);
	m_cBtn.SetTransBitmap(RGB(255,0,255));
	...
}

와 같습니다. 만일, CDialog 기반이라면, 위 m_cBtn.Create(...)등은 필요없습니다.
일반적인 기능은 CBitmapButton과 동일합니다. 다만, SetHoverBitmapID(...)와 SetTransBitmap(...)이 추가되었습니다. SetHoverBitmapID(...)는 OnMouse일때 표시될 BITMAP ID입니다. 그리고, SetTransBitmap(...)은 투명처리할 Bitmap의 color입니다. 만일 CMainFrame, CView 기반이라면, .Create(..., -1)에 -1 부분에 실제 ID를 넣어, ON_COMMAND 처리를 해줘야 합니다.

소스 코드를 공유합니다. (view plain link를 누르시면 제대로된 코드를 확인할 수 있습니다.)

bitmapbuttontrans.h
#pragma once


// CBitmapButtonTrans

class CBitmapButtonTrans : public CBitmapButton
{
	DECLARE_DYNAMIC(CBitmapButtonTrans)

public:
	CBitmapButtonTrans();
	virtual ~CBitmapButtonTrans();
	virtual VOID SetTransBitmap(IN COLORREF clrTrans) {m_clrTrans = clrTrans;}
	virtual VOID SetHoverBitmapID(IN UINT nIDBitmapResourceHover);

protected:
	COLORREF			m_clrTrans;
	BOOL				m_bMouseHover;
	TRACKMOUSEEVENT		m_stTrackMouse;
	UINT				m_nIDBitmapResourceHover;
	CBitmap				m_cBitmapHover;

protected:
	DECLARE_MESSAGE_MAP()
public:
	virtual void DrawItem(LPDRAWITEMSTRUCT /*lpDrawItemStruct*/);
	afx_msg BOOL OnEraseBkgnd(CDC* pDC);
	afx_msg void OnMouseMove(UINT nFlags, CPoint point);
	afx_msg void OnMouseLeave();
	afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
};

bitmapbuttontrans.cpp
// BitmapButtonTrans.cpp : implementation file
//

#include "stdafx.h"
#include "LayoutMgr.h"
#include "BitmapButtonTrans.h"


// CBitmapButtonTrans

IMPLEMENT_DYNAMIC(CBitmapButtonTrans, CBitmapButton)

CBitmapButtonTrans::CBitmapButtonTrans()
{
	m_clrTrans				= RGB(255,0,255);
	m_bMouseHover			= FALSE;
	m_nIDBitmapResourceHover= 0;
	ZeroMemory(&m_stTrackMouse, sizeof(m_stTrackMouse));
}

CBitmapButtonTrans::~CBitmapButtonTrans()
{
}


BEGIN_MESSAGE_MAP(CBitmapButtonTrans, CBitmapButton)
	ON_WM_ERASEBKGND()
	ON_WM_MOUSEMOVE()
	ON_WM_MOUSELEAVE()
	ON_WM_CREATE()
END_MESSAGE_MAP()



// CBitmapButtonTrans message handlers



void CBitmapButtonTrans::DrawItem(LPDRAWITEMSTRUCT lpDIS)
{
	ASSERT(lpDIS != NULL);
	// must have at least the first bitmap loaded before calling DrawItem
	ASSERT(m_bitmap.m_hObject != NULL);     // required

	// use the main bitmap for up, the selected bitmap for down
	CBitmap* pBitmap = &m_bitmap;
	UINT state = lpDIS->itemState;
	if ((state & ODS_SELECTED) && m_bitmapSel.m_hObject != NULL)
		pBitmap = &m_bitmapSel;
	else if ((state & ODS_FOCUS) && m_bitmapFocus.m_hObject != NULL)
		pBitmap = &m_bitmapFocus;   // third image for focused
	else if ((state & ODS_DISABLED) && m_bitmapDisabled.m_hObject != NULL)
		pBitmap = &m_bitmapDisabled;   // last image for disabled
	if ((TRUE == m_bMouseHover) && NULL != m_cBitmapHover.GetSafeHandle())
	{
		pBitmap = &m_cBitmapHover;
	}

	// draw the whole button
	CDC* pDC = CDC::FromHandle(lpDIS->hDC);

	CRect rect;
	rect.CopyRect(&lpDIS->rcItem);

	CLayoutMgr::DrawBitmapTrans(pDC, pBitmap, rect.left, rect.top, m_clrTrans);
}

BOOL CBitmapButtonTrans::OnEraseBkgnd(CDC* pDC)
{
	return FALSE;
}

void CBitmapButtonTrans::OnMouseMove(UINT nFlags, CPoint point)
{
	if (FALSE == m_bMouseHover)
	{
		::_TrackMouseEvent(&m_stTrackMouse);
		m_bMouseHover = TRUE;
		Invalidate(FALSE);
	}
	CBitmapButton::OnMouseMove(nFlags, point);
}

void CBitmapButtonTrans::OnMouseLeave()
{
	m_bMouseHover = FALSE;
	Invalidate(FALSE);
	CBitmapButton::OnMouseLeave();
}

VOID CBitmapButtonTrans::SetHoverBitmapID(IN UINT nIDBitmapResourceHover)
{
	m_nIDBitmapResourceHover = nIDBitmapResourceHover;
	if (0 != m_nIDBitmapResourceHover)
	{
		m_cBitmapHover.DeleteObject();
		m_cBitmapHover.LoadBitmap(m_nIDBitmapResourceHover);
	}
}
int CBitmapButtonTrans::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CBitmapButton::OnCreate(lpCreateStruct) == -1)
		return -1;

	m_stTrackMouse.cbSize		= sizeof(TRACKMOUSEEVENT);
	m_stTrackMouse.dwFlags		= TME_LEAVE;
	m_stTrackMouse.dwHoverTime	= HOVER_DEFAULT;
	m_stTrackMouse.hwndTrack	= GetSafeHwnd();

	return 0;
}

layoutmgr.h / layoutmgr.cpp (유틸리티 class인데, CBitmapButtonTrans에 넣으셔도 됩니다)

// header
class CLayoutMgr
{
...
public:
	static VOID DrawBitmapTrans(IN OUT CDC* pDC, IN CBitmap* pcBitmap, IN int x, IN int y, IN COLORREF crMask);
...
}
// cpp file
VOID CLayoutMgr::DrawBitmapTrans(IN OUT CDC* pDC, IN CBitmap* pcBitmap, IN int x, IN int y, IN COLORREF crMask)
{
	CBitmap		*pOldBitmapImage	= NULL;
	CBitmap		*pOldBitmapTrans	= NULL;
	COLORREF	clrOldBack			= 0;
	COLORREF	clrOldText			= 0;
	BITMAP		bm					= {0,};
	CDC			dcImage;
	CDC			dcTrans;
	CBitmap		bitmapTrans;

	if ((NULL == pcBitmap) || (NULL == pDC))
	{
		return;
	}

	pcBitmap->GetBitmap(&bm);
	dcImage.CreateCompatibleDC( pDC );
	pOldBitmapImage = dcImage.SelectObject( pcBitmap );
	clrOldBack = pDC->SetBkColor( RGB( 255, 255, 255 ) );
	clrOldText = pDC->SetTextColor( RGB( 0, 0, 0 ) );
	dcTrans.CreateCompatibleDC( pDC );
	bitmapTrans.CreateBitmap( bm.bmWidth, bm.bmHeight, 1, 1, NULL );
	pOldBitmapTrans = dcTrans.SelectObject( &bitmapTrans );
	dcImage.SetBkColor( crMask );
	dcTrans.BitBlt( 0, 0, bm.bmWidth, bm.bmHeight, &dcImage, 0, 0, SRCCOPY );
	pDC->BitBlt( x, y, bm.bmWidth, bm.bmHeight, &dcImage, 0, 0, SRCINVERT );
	pDC->BitBlt( x, y, bm.bmWidth, bm.bmHeight, &dcTrans, 0, 0, SRCAND );
	pDC->BitBlt( x, y, bm.bmWidth, bm.bmHeight, &dcImage, 0, 0, SRCINVERT );
	dcTrans.SelectObject( pOldBitmapTrans );
	pDC->SetBkColor( clrOldBack );
	pDC->SetTextColor( clrOldText );
	bitmapTrans.DeleteObject();
	dcTrans.DeleteDC();
	dcImage.SelectObject( pOldBitmapImage );
	dcImage.DeleteDC();    
}