개발은 하는건가..

[MFC] 오른쪽 버튼 메뉴 띄우기 (custom contextmenu) 본문

C, C++, MFC

[MFC] 오른쪽 버튼 메뉴 띄우기 (custom contextmenu)

수동애비 2022. 9. 14. 13:24
반응형

컨텍스트 메뉴를 띄울  버튼이나 마우스 클릭 이벤트 핸들러를 추가 후 다음과 같이 사용하여 띄운다.

void CMainWnd::OnRButtonDown(UINT nFlags, CPoint pt)
{
    CGCSContextMenu cm;
   	
    // ::GetCursorPos(&pt);
    // ::ScreenToClient(pThis->GetSafeHwnd(), &pt);

    cm.CreatePopupMenu();

    // cm.AddMenu(커맨드ID, 메뉴텍스트, 메뉴 아이콘 이미지 파일 경로);

    cm.AddMenu(4000, _T("메뉴1"), _T("메뉴1용 아이콘 파일 전체경로"));
    cm.AddMenu(4001, _T("메뉴2"), _T("메뉴2용 아이콘 파일 전체경로"));

    cm.ShowPopupMenu(parentWindow, pt);

    // 선택한 메뉴의 커맨드ID 가 parentWindow 의  ON_COMMAND 메세지의 WPARAM 으로 전달된다.
}



OOL CMainWnd::OnCommand(WPARAM wParam, LPARAM lParam)
{
	if (wParam == 4000) {
		// 메뉴1의 처리
	}
	else if (wParam == 4001){
		// 메뉴2의 처리
	}

	return CBaseDialog::OnCommand(wParam, lParam);
}

 

CGCSContextMenu.h 파일 구현체

#pragma once
#include <afxwin.h>

#define MENU_DEF_ITEM_WIDTH			140
#define MENU_DEF_ITEM_HEIGHT		30

#define MENU_DEF_BORDER_COLOR		RGB(52, 103, 115)
#define MENU_DEF_NOR_BG_COLOR		RGB(15, 25, 40)
#define MENU_DEF_OVER_BG_COLOR		RGB(45, 55, 75)


class CGCSContextMenu : public CMenu
{
public:
	CGCSContextMenu();
	virtual ~CGCSContextMenu();
	
	struct CSMenuItem {
		UINT		nCmdId;
		TCHAR		szMenu[32];
		void*		pGdiIconImg;

		CSMenuItem::CSMenuItem() { ZeroMemory(this, sizeof(CSMenuItem)); };
	};

	void			AddMenu(UINT nCmdId, CString strMenu, CString &strIconPath);
	void			ShowPopupMenu(CWnd *pParent, CPoint &clientPt);
	CSMenuItem*		GetMenuItem(UINT nCmdId);

	inline void		SetBorderColor(UINT nColor) { m_nBorderColor = nColor; };
	inline void		SetBgColor(UINT nNormalColor, UINT nOverColor) { m_nDefaultBgColor = nNormalColor; m_nOverBgColor = nOverColor; };	
	inline void		SetItemSize(UINT nItemWidth, UINT nItemHeight) { m_nMenuItemWidth = nItemWidth; m_nMenuItemHeight = nItemHeight; };

private:
	UINT			m_nMenuItemWidth;		
	UINT			m_nMenuItemHeight;

	UINT			m_nBorderColor;			// 테두리 라인 색상
	UINT			m_nDefaultBgColor;		// 기본 배경 색상
	UINT			m_nOverBgColor;			// 마우스 오버 배경 색상

	CPtrArray		m_paMenuItems;			// 메뉴 아이템 정보들 배열
	   
	void			DrawingText(CDC *pDC, TCHAR *pStr, RECT* rc, int size, UINT color, UINT format, UINT bold);
// Override Methods
public:	
	virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
	virtual void MeasureItem(LPMEASUREITEMSTRUCT /*lpMeasureItemStruct*/);

};



class CMemoryDC : public CDC
{
public:
	CMemoryDC(CDC *pDC, CWnd *pParentWnd) {
		if (pDC == NULL || pParentWnd == NULL)
		{
			return;
		}

		pParentWnd->GetClientRect(m_rcParent);

		m_pOldBitmap = NULL;
		m_pParentWnd = pParentWnd;
		m_pDC = pDC;

		CreateCompatibleDC(m_pDC);
		m_Bitmap.CreateCompatibleBitmap(m_pDC, m_rcParent.Width(), m_rcParent.Height());
		m_pOldBitmap = SelectObject(&m_Bitmap);
	}

	CMemoryDC(CDC *pDC, CRect *pParentRect) {
		if (pDC == NULL || pParentRect == NULL)
		{
			return;
		}

		m_rcParent = *pParentRect;
		m_pOldBitmap = NULL;

		m_pDC = pDC;

		CreateCompatibleDC(m_pDC);
		m_Bitmap.CreateCompatibleBitmap(m_pDC, m_rcParent.Width(), m_rcParent.Height());
		m_pOldBitmap = SelectObject(&m_Bitmap);
	}

	virtual ~CMemoryDC(void) {
		if (m_pDC == NULL)
		{
			return;
		}

		m_pDC->BitBlt(m_rcParent.left, m_rcParent.top, m_rcParent.right, m_rcParent.bottom, this, 0, 0, SRCCOPY);
		SelectObject(m_pOldBitmap);
		m_Bitmap.DeleteObject();
		DeleteDC();
	}

private:
	CWnd		*m_pParentWnd;
	CDC			*m_pDC;
	CBitmap		m_Bitmap;
	CBitmap		*m_pOldBitmap;
	CRect		m_rcParent;
};

 

CGCSContextMenu.cpp 파일 구현체

#include "../pch.h"
#include "GCSContextMenu.h"



CGCSContextMenu::CGCSContextMenu()
{
	m_nMenuItemWidth	= MENU_DEF_ITEM_WIDTH;
	m_nMenuItemHeight	= MENU_DEF_ITEM_HEIGHT;

	m_nBorderColor		= MENU_DEF_BORDER_COLOR;
	m_nDefaultBgColor	= MENU_DEF_NOR_BG_COLOR;
	m_nOverBgColor		= MENU_DEF_OVER_BG_COLOR;

}


CGCSContextMenu::~CGCSContextMenu()
{
	for (int i = 0; i < m_paMenuItems.GetCount(); i++)
	{
		CSMenuItem *pMi = (CSMenuItem*)m_paMenuItems.GetAt(i);

		if (pMi != NULL)
		{
			delete pMi;
		}
	}

	m_paMenuItems.RemoveAll();
}


void CGCSContextMenu::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
	if (lpDrawItemStruct->hDC == NULL)
	{
		return;
	}
	
	CSMenuItem* pMenuItem = GetMenuItem(lpDrawItemStruct->itemID);

	if (pMenuItem == NULL)
	{
		return;
	}


	CRect rcItem = lpDrawItemStruct->rcItem;
	UINT bgColor;
		
	CDC itemDc;
	itemDc.Attach(lpDrawItemStruct->hDC);

	if (itemDc.GetSafeHdc() != NULL)
	{
		// Mem DC 는 소멸 시점에 버퍼에 내용을 Flush 하므로  상위 dc 가 detach 하기 전에 소멸되도록 해야 함.
		//CMemDC memDC(itemDc, rcItem);
		//CDC *pDC = &memDC.GetDC();

		CMemoryDC	dc(&itemDc, &rcItem);
		CDC *pDC = &dc;
				
		bgColor = (lpDrawItemStruct->itemState == 257) ? m_nOverBgColor : m_nDefaultBgColor;

		pDC->FillSolidRect(0, 0, rcItem.Width(), rcItem.Height(), bgColor);
		
		DrawingText(pDC,
			pMenuItem->szMenu, 
			CRect(30, 0, rcItem.Width(), rcItem.Height()), 
			22, 
			RGB(230, 240, 250), 
			DT_LEFT | DT_VCENTER,
			FW_NORMAL);

		Gdiplus::Image* pIcon = (Gdiplus::Image*)pMenuItem->pGdiIconImg;

		if (pIcon != NULL)
		{
			Gdiplus::Graphics grs(pDC->GetSafeHdc());

			int px = 4;
			int py = ((rcItem.Height() - pIcon->GetHeight()) / 2);	// rcItem 은 컨텍스트 영역 전체 기준의 절대 좌표이다.
			grs.DrawImage(pIcon, Gdiplus::Rect(px, py, pIcon->GetWidth(), pIcon->GetHeight()));
		}
	}

	//TRACE(_T("state=%d, type=%d, ctlid=%d, itemId=%d\n"), lpDrawItemStruct->itemState, lpDrawItemStruct->CtlType, lpDrawItemStruct->CtlID, lpDrawItemStruct->itemID);

	itemDc.Detach();
}


void CGCSContextMenu::MeasureItem(LPMEASUREITEMSTRUCT lpms)
{
	if (lpms->CtlType != ODT_MENU)
		return;

	lpms->itemWidth = m_nMenuItemWidth;
	lpms->itemHeight = m_nMenuItemHeight;
}


void CGCSContextMenu::AddMenu(UINT nCmdId, CString strMenu, CString &strIconPath)
{
	CSMenuItem* pMi = new CSMenuItem();

	
	pMi->nCmdId = nCmdId;
		
	_tcsncpy(pMi->szMenu, strMenu, _countof(pMi->szMenu));

	if (strIconPath.IsEmpty() == FALSE)
	{
		pMi->pGdiIconImg = (void*)Gdiplus::Image::FromFile(strIconPath);
	}

	Gdiplus::Image*	m_pTitleImg = (Gdiplus::Image*)pMi->pGdiIconImg;

	m_paMenuItems.Add(pMi);

	AppendMenu(MF_OWNERDRAW, nCmdId);
	
}


void CGCSContextMenu::ShowPopupMenu(CWnd *pParent, CPoint &clientPt)
{
	MENUINFO MenuInfo = { 0 };
	MenuInfo.cbSize = sizeof(MENUINFO);
	GetMenuInfo(&MenuInfo);

	MenuInfo.hbrBack = ::CreateSolidBrush(RGB(0, 0, 0));
	MenuInfo.fMask = MIM_BACKGROUND | MIM_STYLE;
	MenuInfo.dwStyle = MIM_APPLYTOSUBMENUS;
	SetMenuInfo(&MenuInfo);

	CPoint	pt = clientPt;

	pParent->ClientToScreen(&pt);

	TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, pParent);
	DestroyMenu();
}

void CGCSContextMenu::DrawingText(CDC *pDC, TCHAR *pStr, RECT* rc, int size, UINT color, UINT format, UINT bold)
{
	if (pStr == NULL || pDC == NULL) {
		return;
	}

	CFont fFont, *pOldFont;
	int oldBkMode;
	UINT oldColor = pDC->GetTextColor();
	CRect rcClient;

	fFont.CreateFont(size, 0, 0, 0,
		bold,
		FALSE, FALSE, FALSE,
		DEFAULT_CHARSET,
		OUT_CHARACTER_PRECIS,
		CLIP_CHARACTER_PRECIS,
		PROOF_QUALITY,
		DEFAULT_PITCH | FF_DONTCARE,
		_T("맑은 고딕"));

	pOldFont = (CFont*)pDC->SelectObject(&fFont);

	oldBkMode = pDC->SetBkMode(TRANSPARENT);
	pDC->SetTextColor(color);

	if ((format & DT_VCENTER) && !(format & DT_SINGLELINE))
	{
		CSize sizeEx = pDC->GetTextExtent(pStr);

		if (sizeEx.cx > 0)
		{
			int lineCnt = (sizeEx.cx / (rc->right - rc->left)) + 1;

			// count crlf new line
			for (int i = 0; i < _tcslen(pStr); i++)
			{
				if (pStr[i] == _T('\n'))
				{
					lineCnt++;
				}
			}

			int predictHeight = lineCnt * sizeEx.cy;

			if (predictHeight < (rc->bottom - rc->top))
			{
				int offset = ((rc->bottom - rc->top) - predictHeight) / 2;

				rc->top += offset;
			}
		}
	}

	pDC->DrawText(pStr, rc, format);

	pDC->SetTextColor(oldColor);
	pDC->SetBkMode(oldBkMode);
	pDC->SelectObject(pOldFont);
	fFont.DeleteObject();
}



CGCSContextMenu::CSMenuItem* CGCSContextMenu::GetMenuItem(UINT nCmdId)
{
	for (int i = 0; i < m_paMenuItems.GetCount(); i++)
	{
		CSMenuItem* pMi = (CSMenuItem*)m_paMenuItems.GetAt(i);

		if (pMi->nCmdId == nCmdId)
		{
			return pMi;
		}
	}


	return NULL;
}
Comments