4. MFC Programming : 쉽게 배우는 MFC 윈도우 프로그래밍(CH 6)
이 글은 공부하면서 배운 것들을 간략하게 작성한 것이다. 그만큼 두서도 없고, 기재되어 있는 코드도 어느정도 MFC 구조에 대한 이해가 없으면 파악하기 어려울 수 있다. 이 점을 참고하기를 바란다.
메뉴란 응용 프로그램에서 선택할 수 있는 명령의 집합으로, 계층 구조로 이루어진 인터페이스 요소이다. 메뉴에는 여러가지 종류의 메뉴가 있다. 다음은 윈도우가 지원하는 메뉴 종류이다.
- Top-Level Menu : 타이틀 바 아래쪽에 위치함, 일반적으로 생각하는 메뉴
- Drop-Down Menu : 최상위 메뉴를 선택하면 펼처지는 메뉴
- Context Menu : 마우스 오른쪽 버튼을 누르면 나오는 메뉴, 마우스 커서의 위치에 따라서 뜨는 내용이 달라질 수 있다.
- Pop-Up Menu : 사용자의 행위 결에 따라서 튀어나오는 메뉴(Context Menu와 Drop-Down Menu가 이에 해당한다.)
- System Menu : 타이틀 바 왼쪽에 있는 아이콘을 클릭하면 펼쳐지는 메뉴
다음은 실습의 내용이다. MFC를 이용하여 메뉴, 툴바, 상태바의 형태와 동작을 살펴보는 것을 목적으로 한다. 메뉴를 생성하는 방법은 크게 두 가지가 있다. 하나는 매뉴 리소스를 정의하여 실행 파일에 포함시킨 뒤 프로그램 실행 중에 로드하여 사용하는 것이고, 다른 하나는 코드를 실행하여 메뉴를 생성한 후 윈도우에 붙여서 사용하는 것이다. 일반적으로 사용되는 방법은 첫 번째 것이다.
메뉴를 일일이 코드로 구현할 필요는 없다. Visual Studio는 사용자가 메뉴를 그리고, 그 메뉴를 리소스로 등록하여 아주 약간의 수정만으로 정의한 리소스를 사용할 수 있도록 기능을 제공한다. 메뉴를 그리는 방법에 대해 간단하게만 언급하자면, 리소스 폴더를 우클릭하면, 리소스 추가 항목이 있고, 메뉴를 추가하는 항목이 있을 것이다. 선택해서 원하는 메뉴를 만들면 된다. 그 후에는 C{프로젝트 생성시 쓴 name}App.cpp 파일의 InitInstance()를 약간 수정하면 된다. 코드는 다음과 같다.
BOOL C{App-Name}App::InitInstance(){
... 생략 ...
CFrameWnd* pFrame = new CMainFrame;
if (!pFrame)
return FALSE;
m_pMainWnd = pFrame;
// create and load the frame with its resources
pFrame->LoadFrame(IDR_MENU //여기에 등록된 메뉴 아이디를 넣는다
,WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, nullptr,
nullptr);
... 생략 ...
}
리소스 파일을 이용하는 것 외에도 바로 메인 메뉴를 코드로 만들어 사용하는 방법도 있다. 다음의 코드를 CMainFrame::OnCreate()에 추가해 주면 된다.
CMenu menuMain;
menuMain.CreateMenu();
//메뉴를 그리는 과정(계층도를 그리는 것과 유사하다.)
CMenu menuPopup;
menuPopup.CreatePopupMenu();
menuPopup.AppendMenu(MF_STRING, 201, _T("RED(&R)"));
menuPopup.AppendMenu(MF_STRING, 202, _T("BLUE(&B)"));
menuPopup.AppendMenu(MF_STRING, 203, _T("GREEN(&G)"));
menuMain.AppendMenu(MF_POPUP, (UINT)menuPopup.Detach(), _T("COLOR(&C)"));
//그린 메뉴를 윈도우에 붙인다.
SetMenu(&menuMain);
//메뉴 객체와 메뉴를 분리한다.
menuMain.Detach();
메뉴를 만들어 보았으니 이제는 만든 메뉴에서 항목을 선택할 때, 특정 명령이 나올 수 있도록 해보자. 메뉴에서 특정 항목을 선택할 때마다 윈도우는 WM_COMMAND 메시지를 송출한다. 그러나 WM_COMMAND 메시지는 메뉴 항목을 선택할 때뿐만 아니라 컨트롤에서도 발생하므로 항목별로 처리 함수를 작성해줄 필요가 있다. Visual Studio는 처리 함수를 쉽게 작성할 수 있도록 기능들을 제공한다. 다음은 예제이다.
void CChildView::OnPaint()
{
//m_color은 CChildView 클래스에 정의되어 있다.
CPaintDC dc(this); // device context for painting
CFont font;
font.CreatePointFont(300, _T("궁서"));
dc.SelectObject(&font);
dc.SetTextColor(m_color);
CRect rect;
GetClientRect(&rect);
CString str = _T("Menu Test");
dc.DrawText(str, &rect, DT_CENTER|DT_VCENTER|DT_SINGLELINE);
}
void CChildView::OnRedLighter()
{
m_color = RGB(255,0,0);
Invalidate();
}
메뉴 항목 갱신하기. 때로는 선택에 따라서 메뉴 항목에 변경 상항이 있는 경우도 있다. 그럴 때는 OnUpdate*(CCmdUI*) 함수를 이용하여 메뉴의 객체를 바꾸면 된다.
void CChildView::OnUpdateRedLighter(CCmdUI* pCmdUI)
{
//메뉴 관련 변경 내용을 넣으면 됩니다.
//pCmdUI->Enable() : 활성화 상태 변경
//pCmdUI->SetCheck() : 체크 상태 변경
//pCmdUI->SetRadio() : 라디오 표시 상태 변경
//pCmdUI->SetText() : 문자열 변경
}
컨텍스트 메뉴는 마우스 오른쪽 버튼을 클릭할 때 열린다. 아우스 커서의 위치 또는 현재 작업하는 내용에 따라 메뉴 항목을 표시한다는 특징이 있다. 윈도우 운영체제는 컨텍스트 메뉴를 지원하기 위해 WM_CONTEXTMENU 메시지를 제공한다. 사용법은 기존의 이벤트 함수를 추가하는 것과 같다. 다음은 예제 코드이다.
void CChildView::OnContextMenu(CWnd* pWnd, CPoint point)
{
CMenu menu;
menu.LoadMenu(IDR_MAINFRAME);
CMenu* pMenu = menu.GetSubMenu(0);
pMenu->TrackPopupMenu(TPM_LEFTALIGN, point.x, point.y, pWnd, 0);
//TrackMenu(UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect)
}
시스템 메뉴를 사용하는 방법은 다음과 같다.
//추가하거나 수정해야 할 코드
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct){
...생략...
CMenu* pSysMenu = GetSystemMenu(FALSE);
//닫기 비활성화
pSysMenu->EnableMenuItem(SC_CLOSE, MF_GRAYED);
//부리자 항목을 추가한다
pSysMenu->AppendMenu(MF_SEPARATOR);
//ID가 16인 항목을 추가한다
pSysMenu->AppendMenu(MF_STRING, 16, _T("이 프로그램은 ..."));
return 0;
}
void CMainFrame::OnSysCommand(UINT nID, LPARAM lParam)
{
if((nID & 0xFFF0) == 16){
MessageBox(_T("시스템 메뉴를 테스트합니다."), _T("이 프로그램은..."));
return;
}
CFrameWnd::OnSysCommand(nID, lParam);
}
윈도우는 가속키를 지원한다. 가속키의 예시로 대표적인 것은 Cntrl + C, Cntrl + V 같은 것이 있다. Visual Studio는 쉽게 가속키를 설정할 수 있도록 기능을 지원한다.
툴 바는 일반적으로 메뉴 항목의 기능을 빠르게 수행하는 목적으로 사용되며, 때로는 메뉴 항목에 없는 기능을 제공한다.
종류로는 CControlBar, CToolBar로 두 개의 클래스로 기능을 나누어 제공한다. 툴 바 리소스는 MFC 앱을 초기에 생성할 때 제공된다.(당연하지만 편집도 가능하다.) 다음은 메인 프레임 클래스에서 툴 바를 정의한 부분이다.
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct){
...생략...
if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
...생략...
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);
}
툴 바의 각 요소에 처리 함수를 추가하는 것은 앞서 처리함수를 처리하는 방법과 같다.
상태바는 윈도우의 맨 아래에 위치해 있어 프로그램의 현재 상태를 보여준다. CControllBar, CStatusBar로 나뉘어져 기능을 제공한다. 메인 프레임에 스테이터스 바는 다음과 같이 정의되어 있다.(당연히 편집해서 사용할 수 있다.)
int CMainFrame::OnCreate(LPREATESTRUCT lpCreateStruct){
...생략...
if (!m_wndStatusBar.Create(this))
{
TRACE0("Failed to create status bar\n");
return -1; // fail to create
}
m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT));
...생략...
}