MFC 기초 | Microsoft Foundation Class 시작 가이드
이 글의 핵심
MFC는 Windows API를 C++ 클래스로 감싼 프레임워크입니다. CWinApp으로 애플리케이션을, CFrameWnd로 메인 윈도우를 관리합니다. 메시지 맵으로 이벤트를 처리하고, Document/View로 데이터와 UI를 분리합니다.
들어가며
MFC(Microsoft Foundation Class)는 1992년 마이크로소프트가 발표한 C++ 클래스 라이브러리입니다. Windows API를 객체 지향 방식으로 감싸 생산성과 유지보수성을 크게 향상시켰습니다. 2000년대 중반까지 Windows 애플리케이션 개발의 표준이었습니다.
// MFC의 가장 간단한 윈도우
class CMyFrameWnd : public CFrameWnd
{
public:
CMyFrameWnd()
{
Create(NULL, _T("My First MFC App"));
}
};
class CMyApp : public CWinApp
{
public:
virtual BOOL InitInstance()
{
m_pMainWnd = new CMyFrameWnd;
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}
};
CMyApp theApp; // 전역 객체
왜 MFC를 배워야 할까?
MFC가 여전히 중요한 이유:
- 레거시 시스템 - 2010년 이전 Windows 앱 대부분이 MFC 기반
- 빠른 개발 - Win32 API보다 훨씬 적은 코드로 구현
- 시스템 이해 - WPF, Qt 등 최신 프레임워크의 기초 개념
- 유지보수 - 오래된 기업 소프트웨어 유지보수에 필수
1. MFC 프로젝트 생성
1.1 Visual Studio에서 프로젝트 생성
1. 새 프로젝트 → Visual C++ → MFC
2. MFC 애플리케이션 마법사
3. 애플리케이션 종류:
- 단일 문서 (SDI)
- 다중 문서 (MDI)
- 대화 상자 기반
4. 문서/뷰 지원: 예
5. 완료
1.2 생성된 파일 구조
MyApp.sln // 솔루션 파일
MyApp/
MyApp.cpp // CWinApp 파생 클래스
MyApp.h
MainFrm.cpp // CFrameWnd 파생 클래스 (메인 윈도우)
MainFrm.h
MyAppDoc.cpp // CDocument 파생 클래스
MyAppDoc.h
MyAppView.cpp // CView 파생 클래스
MyAppView.h
Resource.h // 리소스 ID
MyApp.rc // 리소스 스크립트
res/
MyApp.ico // 아이콘
Toolbar.bmp // 툴바 비트맵
2. MFC 클래스 계층
2.1 핵심 클래스 구조
CObject
├─ CCmdTarget
│ ├─ CWinThread
│ │ └─ CWinApp // 애플리케이션
│ ├─ CWnd
│ │ ├─ CFrameWnd // 메인 윈도우
│ │ ├─ CDialog // 대화상자
│ │ ├─ CView // 뷰
│ │ └─ CButton, CEdit, CListCtrl... // 컨트롤
│ └─ CDocument // 문서 (데이터)
├─ CFile // 파일 I/O
├─ CString // 문자열
└─ CArray, CList, CMap... // 컬렉션
2.2 주요 클래스 역할
// CWinApp - 애플리케이션 전체 관리
class CMyApp : public CWinApp
{
public:
virtual BOOL InitInstance(); // 초기화
virtual int ExitInstance(); // 종료
virtual BOOL PreTranslateMessage(MSG* pMsg); // 메시지 전처리
};
// CFrameWnd - 메인 윈도우
class CMainFrame : public CFrameWnd
{
public:
virtual BOOL PreCreateWindow(CREATESTRUCT& cs); // 윈도우 생성 전
protected:
afx_msg void OnPaint(); // 그리기
afx_msg void OnSize(UINT nType, int cx, int cy); // 크기 변경
DECLARE_MESSAGE_MAP()
};
// CDocument - 데이터 관리
class CMyDoc : public CDocument
{
public:
virtual BOOL OnNewDocument(); // 새 문서
virtual void Serialize(CArchive& ar); // 저장/로드
virtual BOOL OnSaveDocument(LPCTSTR lpszPathName); // 저장
protected:
CString m_strData;
int m_nValue;
};
// CView - UI 표시
class CMyView : public CView
{
public:
virtual void OnDraw(CDC* pDC); // 그리기
virtual void OnInitialUpdate(); // 초기화
protected:
afx_msg void OnLButtonDown(UINT nFlags, CPoint point); // 마우스
afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); // 키보드
DECLARE_MESSAGE_MAP()
};
3. 메시지 맵 (Message Map)
3.1 메시지 맵 선언
// MainFrm.h
class CMainFrame : public CFrameWnd
{
protected:
DECLARE_MESSAGE_MAP() // ★ 메시지 맵 선언
afx_msg void OnPaint();
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnFileNew();
afx_msg void OnFileOpen();
};
// MainFrm.cpp
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
ON_WM_PAINT() // WM_PAINT → OnPaint()
ON_WM_SIZE() // WM_SIZE → OnSize()
ON_WM_LBUTTONDOWN() // WM_LBUTTONDOWN → OnLButtonDown()
ON_COMMAND(ID_FILE_NEW, OnFileNew) // ID_FILE_NEW 명령 → OnFileNew()
ON_COMMAND(ID_FILE_OPEN, OnFileOpen)
END_MESSAGE_MAP()
void CMainFrame::OnPaint()
{
CPaintDC dc(this); // BeginPaint/EndPaint 자동
dc.TextOut(10, 10, _T("Hello, MFC!"));
}
void CMainFrame::OnSize(UINT nType, int cx, int cy)
{
CFrameWnd::OnSize(nType, cx, cy);
CString str;
str.Format(_T("크기: %d x %d"), cx, cy);
SetWindowText(str);
}
void CMainFrame::OnLButtonDown(UINT nFlags, CPoint point)
{
CString str;
str.Format(_T("클릭: (%d, %d)"), point.x, point.y);
MessageBox(str);
}
void CMainFrame::OnFileNew()
{
MessageBox(_T("새 파일"));
}
void CMainFrame::OnFileOpen()
{
CFileDialog dlg(TRUE); // TRUE = 열기, FALSE = 저장
if (dlg.DoModal() == IDOK) {
CString path = dlg.GetPathName();
MessageBox(path);
}
}
3.2 주요 메시지 맵 매크로
// 윈도우 메시지
ON_WM_CREATE() // WM_CREATE
ON_WM_DESTROY() // WM_DESTROY
ON_WM_PAINT() // WM_PAINT
ON_WM_SIZE() // WM_SIZE
ON_WM_MOVE() // WM_MOVE
// 마우스 메시지
ON_WM_LBUTTONDOWN() // WM_LBUTTONDOWN
ON_WM_LBUTTONUP() // WM_LBUTTONUP
ON_WM_RBUTTONDOWN() // WM_RBUTTONDOWN
ON_WM_MOUSEMOVE() // WM_MOUSEMOVE
// 키보드 메시지
ON_WM_KEYDOWN() // WM_KEYDOWN
ON_WM_KEYUP() // WM_KEYUP
ON_WM_CHAR() // WM_CHAR
// 명령 메시지
ON_COMMAND(id, func) // 메뉴/툴바 명령
ON_UPDATE_COMMAND_UI(id, func) // UI 업데이트
// 알림 메시지
ON_BN_CLICKED(id, func) // 버튼 클릭
ON_EN_CHANGE(id, func) // 에디트 변경
ON_LBN_SELCHANGE(id, func) // 리스트박스 선택 변경
4. 간단한 MFC 애플리케이션
4.1 최소한의 MFC 프로그램
// MyApp.cpp
#include <afxwin.h>
class CMyFrameWnd : public CFrameWnd
{
public:
CMyFrameWnd()
{
Create(NULL, _T("My MFC Window"));
}
protected:
DECLARE_MESSAGE_MAP()
afx_msg void OnPaint()
{
CPaintDC dc(this);
dc.TextOut(10, 10, _T("Hello, MFC!"));
}
afx_msg void OnLButtonDown(UINT nFlags, CPoint point)
{
CString str;
str.Format(_T("클릭: (%d, %d)"), point.x, point.y);
MessageBox(str);
}
};
BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
ON_WM_PAINT()
ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()
class CMyApp : public CWinApp
{
public:
virtual BOOL InitInstance()
{
m_pMainWnd = new CMyFrameWnd;
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}
};
CMyApp theApp; // ★ 전역 객체 (진입점)
5. Document/View 아키텍처
5.1 기본 개념
┌─────────────┐ ┌──────────────┐
│ CDocument │◄──────┤ CView │
│ (데이터) │ │ (UI 표시) │
└─────────────┘ └──────────────┘
▲ ▲
│ │
└───────────┬───────────┘
│
┌────────────────┐
│ CFrameWnd │
│ (메인 윈도우) │
└────────────────┘
5.2 CDocument - 데이터 관리
// MyDoc.h
class CMyDoc : public CDocument
{
DECLARE_DYNCREATE(CMyDoc)
protected:
CString m_strText;
CArray<CPoint, CPoint&> m_points;
public:
// 데이터 접근자
CString GetText() const { return m_strText; }
void SetText(const CString& str) { m_strText = str; SetModifiedFlag(); }
void AddPoint(CPoint pt) { m_points.Add(pt); SetModifiedFlag(); }
int GetPointCount() const { return (int)m_points.GetSize(); }
CPoint GetPoint(int index) const { return m_points[index]; }
// 오버라이드
virtual BOOL OnNewDocument();
virtual void Serialize(CArchive& ar);
protected:
DECLARE_MESSAGE_MAP()
};
// MyDoc.cpp
IMPLEMENT_DYNCREATE(CMyDoc, CDocument)
BEGIN_MESSAGE_MAP(CMyDoc, CDocument)
END_MESSAGE_MAP()
BOOL CMyDoc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;
m_strText = _T("새 문서");
m_points.RemoveAll();
return TRUE;
}
void CMyDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring()) {
// 저장
ar << m_strText;
ar << m_points.GetSize();
for (int i = 0; i < m_points.GetSize(); i++) {
ar << m_points[i];
}
} else {
// 로드
ar >> m_strText;
int count;
ar >> count;
m_points.SetSize(count);
for (int i = 0; i < count; i++) {
ar >> m_points[i];
}
}
}
5.3 CView - UI 표시
// MyView.h
class CMyView : public CView
{
DECLARE_DYNCREATE(CMyView)
protected:
CMyDoc* GetDocument() const;
public:
virtual void OnDraw(CDC* pDC);
virtual void OnInitialUpdate();
protected:
DECLARE_MESSAGE_MAP()
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
};
// MyView.cpp
IMPLEMENT_DYNCREATE(CMyView, CView)
BEGIN_MESSAGE_MAP(CMyView, CView)
ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()
CMyDoc* CMyView::GetDocument() const
{
return (CMyDoc*)m_pDocument;
}
void CMyView::OnInitialUpdate()
{
CView::OnInitialUpdate();
// 초기화
}
void CMyView::OnDraw(CDC* pDC)
{
CMyDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// 텍스트 그리기
pDC->TextOut(10, 10, pDoc->GetText());
// 점들 그리기
for (int i = 0; i < pDoc->GetPointCount(); i++) {
CPoint pt = pDoc->GetPoint(i);
pDC->Ellipse(pt.x - 3, pt.y - 3, pt.x + 3, pt.y + 3);
}
}
void CMyView::OnLButtonDown(UINT nFlags, CPoint point)
{
CMyDoc* pDoc = GetDocument();
pDoc->AddPoint(point); // 문서에 점 추가
Invalidate(); // 다시 그리기
CView::OnLButtonDown(nFlags, point);
}
6. MFC 컨트롤
6.1 버튼 (CButton)
class CMyDialog : public CDialog
{
public:
CButton m_btnOK;
CButton m_btnCancel;
protected:
virtual BOOL OnInitDialog()
{
CDialog::OnInitDialog();
// 컨트롤 핸들 연결
m_btnOK.SubclassDlgItem(IDOK, this);
m_btnCancel.SubclassDlgItem(IDCANCEL, this);
return TRUE;
}
DECLARE_MESSAGE_MAP()
afx_msg void OnBnClickedOk()
{
// 확인 버튼 처리
MessageBox(_T("확인!"));
CDialog::OnOK();
}
};
BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
ON_BN_CLICKED(IDOK, OnBnClickedOk)
END_MESSAGE_MAP()
6.2 에디트 (CEdit)
class CMyDialog : public CDialog
{
public:
CEdit m_edit;
protected:
afx_msg void OnBnClickedGetText()
{
CString str;
m_edit.GetWindowText(str);
MessageBox(str);
}
afx_msg void OnBnClickedSetText()
{
m_edit.SetWindowText(_T("새 텍스트"));
}
DECLARE_MESSAGE_MAP()
};
BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
ON_BN_CLICKED(IDC_BTN_GET, OnBnClickedGetText)
ON_BN_CLICKED(IDC_BTN_SET, OnBnClickedSetText)
END_MESSAGE_MAP()
6.3 리스트 컨트롤 (CListCtrl)
class CMyDialog : public CDialog
{
public:
CListCtrl m_list;
protected:
virtual BOOL OnInitDialog()
{
CDialog::OnInitDialog();
// 리포트 뷰 설정
m_list.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
// 컬럼 추가
m_list.InsertColumn(0, _T("이름"), LVCFMT_LEFT, 150);
m_list.InsertColumn(1, _T("나이"), LVCFMT_LEFT, 80);
m_list.InsertColumn(2, _T("직업"), LVCFMT_LEFT, 150);
// 항목 추가
int index = m_list.InsertItem(0, _T("홍길동"));
m_list.SetItemText(index, 1, _T("30"));
m_list.SetItemText(index, 2, _T("개발자"));
index = m_list.InsertItem(1, _T("김철수"));
m_list.SetItemText(index, 1, _T("25"));
m_list.SetItemText(index, 2, _T("디자이너"));
return TRUE;
}
DECLARE_MESSAGE_MAP()
};
7. 대화상자 (CDialog)
7.1 모달 대화상자
// MyDialog.h
class CMyDialog : public CDialog
{
public:
CMyDialog(CWnd* pParent = NULL);
enum { IDD = IDD_MYDIALOG };
CString m_strName;
int m_nAge;
protected:
virtual void DoDataExchange(CDataExchange* pDX);
virtual BOOL OnInitDialog();
virtual void OnOK();
DECLARE_MESSAGE_MAP()
};
// MyDialog.cpp
CMyDialog::CMyDialog(CWnd* pParent)
: CDialog(IDD_MYDIALOG, pParent)
, m_strName(_T(""))
, m_nAge(0)
{
}
void CMyDialog::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Text(pDX, IDC_EDIT_NAME, m_strName);
DDX_Text(pDX, IDC_EDIT_AGE, m_nAge);
}
BOOL CMyDialog::OnInitDialog()
{
CDialog::OnInitDialog();
// 초기화
m_strName = _T("홍길동");
m_nAge = 30;
UpdateData(FALSE); // 변수 → 컨트롤
return TRUE;
}
void CMyDialog::OnOK()
{
UpdateData(TRUE); // 컨트롤 → 변수
if (m_strName.IsEmpty()) {
MessageBox(_T("이름을 입력하세요!"));
return;
}
CDialog::OnOK();
}
BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
END_MESSAGE_MAP()
// 호출
void CMainFrame::OnShowDialog()
{
CMyDialog dlg;
if (dlg.DoModal() == IDOK) {
CString str;
str.Format(_T("이름: %s\n나이: %d"), dlg.m_strName, dlg.m_nAge);
MessageBox(str);
}
}
8. 실전 예제: 간단한 메모장
// MyDoc.h
class CMyDoc : public CDocument
{
DECLARE_DYNCREATE(CMyDoc)
protected:
CString m_strContent;
public:
CString GetContent() const { return m_strContent; }
void SetContent(const CString& str) { m_strContent = str; SetModifiedFlag(); }
virtual BOOL OnNewDocument();
virtual void Serialize(CArchive& ar);
protected:
DECLARE_MESSAGE_MAP()
};
IMPLEMENT_DYNCREATE(CMyDoc, CDocument)
BEGIN_MESSAGE_MAP(CMyDoc, CDocument)
END_MESSAGE_MAP()
BOOL CMyDoc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;
m_strContent.Empty();
return TRUE;
}
void CMyDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring()) {
ar << m_strContent;
} else {
ar >> m_strContent;
}
}
// MyView.h
class CMyView : public CEditView
{
DECLARE_DYNCREATE(CMyView)
protected:
CMyDoc* GetDocument() const;
public:
virtual void OnInitialUpdate();
protected:
DECLARE_MESSAGE_MAP()
afx_msg void OnEditCut();
afx_msg void OnEditCopy();
afx_msg void OnEditPaste();
afx_msg void OnUpdateEditCut(CCmdUI* pCmdUI);
};
IMPLEMENT_DYNCREATE(CMyView, CEditView)
BEGIN_MESSAGE_MAP(CMyView, CEditView)
ON_COMMAND(ID_EDIT_CUT, OnEditCut)
ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)
ON_UPDATE_COMMAND_UI(ID_EDIT_CUT, OnUpdateEditCut)
END_MESSAGE_MAP()
CMyDoc* CMyView::GetDocument() const
{
return (CMyDoc*)m_pDocument;
}
void CMyView::OnInitialUpdate()
{
CEditView::OnInitialUpdate();
CMyDoc* pDoc = GetDocument();
SetWindowText(pDoc->GetContent());
}
void CMyView::OnEditCut()
{
Cut();
}
void CMyView::OnEditCopy()
{
Copy();
}
void CMyView::OnEditPaste()
{
Paste();
}
void CMyView::OnUpdateEditCut(CCmdUI* pCmdUI)
{
long nStart, nEnd;
GetEditCtrl().GetSel(nStart, nEnd);
pCmdUI->Enable(nStart != nEnd);
}
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- Windows API 기초 | 메시지 루프와 윈도우 프로시저 완벽 가이드
- Windows API 컨트롤 | 버튼·에디트·리스트박스 완벽 가이드
- Windows API 다이얼로그 | 대화상자와 공용 컨트롤 완벽 가이드
이 글이 도움이 되셨나요? MFC 기초를 마스터하는 데 도움이 되었기를 바랍니다!
Windows API & MFC 시리즈를 모두 완료했습니다! 🎉