본문으로 건너뛰기
Previous
Next
MFC 기초 | Microsoft Foundation Class 시작 가이드

MFC 기초 | Microsoft Foundation Class 시작 가이드

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가 여전히 중요한 이유:

  1. 레거시 시스템 - 2010년 이전 Windows 앱 대부분이 MFC 기반
  2. 빠른 개발 - Win32 API보다 훨씬 적은 코드로 구현
  3. 시스템 이해 - WPF, Qt 등 최신 프레임워크의 기초 개념
  4. 유지보수 - 오래된 기업 소프트웨어 유지보수에 필수

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 시리즈를 모두 완료했습니다! 🎉