메뉴 닫기

MFC CWnd 클래스 완벽 가이드: 윈도우 생성부터 메시지 처리까지


MFC CWnd 클래스 완벽 가이드: 윈도우 생성부터 메시지 처리까지

📌 MFC 입문자라면 꼭 알아야 할 핵심 클래스 CWnd를 쉽게 설명합니다

C++ 기반 윈도우 프로그램을 만들고 싶은데, 도대체 어디서부터 시작해야 할지 막막하셨나요?
MFC를 처음 접한 분들이라면 CWnd 클래스라는 이름부터 어렵게 느껴지실 수 있습니다.
하지만 실제로는 MFC에서 윈도우를 생성하고 메시지를 처리하는 데 있어 가장 기본이 되는 매우 중요한 클래스입니다.
이 글에서는 CWnd가 어떤 역할을 하는지, 왜 중요한지를 차근차근 설명드릴게요.
특히 실전 예제를 통해 초보자도 쉽게 이해할 수 있도록 구성했으니 끝까지 읽어보시면 큰 도움이 되실 거예요.

MFC는 Microsoft에서 제공하는 오래된 프레임워크지만, 여전히 많은 레거시 시스템과 기업 환경에서 널리 사용되고 있습니다.
그 중심에는 바로 CWnd가 있습니다.
이번 글에서는 CWnd가 어떤 구조로 되어 있는지, 어떤 기능을 제공하는지, 그리고 실전에서 어떻게 활용되는지를 하나씩 정리해드립니다.
아직도 MFC가 어렵게 느껴지신다면, 이 포스팅이 실마리가 될 수 있습니다.







🔗 CWnd 클래스란?

MFC의 핵심 클래스 중 하나인 CWnd는 모든 윈도우(창)의 기반이 되는 클래스입니다.
CWnd는 MFC 프레임워크에서 사용자 인터페이스를 담당하며, 버튼, 리스트 박스, 대화 상자 같은 다양한 윈도우 컨트롤들이 이 클래스를 상속받아 동작합니다.

보다 정확히 말하자면, CWnd는 윈도우 핸들(HWND)을 래핑(wrapping)하는 클래스입니다.
이를 통해 개발자는 직접 Win32 API를 다루는 번거로움 없이 MFC만으로도 윈도우를 생성하고 메시지를 처리할 수 있습니다.

📌 CWnd가 제공하는 핵심 기능

  • 🛠️Create 함수를 통한 윈도우 생성
  • 📨OnPaint, OnClick 등 메시지 처리 함수 제공
  • 🧱다른 컨트롤(CButton, CEdit 등)의 기반 클래스 역할
  • 🔧윈도우 위치, 크기, 스타일 등을 제어하는 다양한 멤버 함수

핵심 구조는 어떻게 되어 있나요?

CWnd는 내부적으로 HWND 멤버를 포함하고 있으며, 이는 Win32 API의 창 핸들과 연결되어 있습니다.
또한 메시지 맵(Message Map)을 통해 윈도우 메시지를 멤버 함수로 연결하는 구조를 가지고 있어 이벤트 기반 프로그래밍이 가능합니다.

CODE BLOCK
// 메시지 맵 예시
BEGIN_MESSAGE_MAP(CMyWnd, CWnd)
    ON_WM_PAINT()
    ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()

이 구조 덕분에, 개발자는 필요한 메시지에만 집중하여 해당 핸들러 함수를 작성하면 됩니다.
복잡한 메시지 루프를 직접 작성할 필요가 없기 때문에 생산성도 높아지죠.


🛠️ 윈도우 생성과 초기화 방식

CWnd 클래스는 MFC에서 직접 윈도우를 생성할 수 있도록 다양한 기능을 제공합니다.
가장 기본적인 방식은 Create 함수를 사용하는 것인데요,
이는 해당 클래스가 어떤 종류의 윈도우를 생성하느냐에 따라 다양한 오버로드 버전을 제공합니다.

예를 들어, 일반 윈도우를 만들 때는 CreateEx → Create → PreCreateWindow 순으로 내부적으로 호출됩니다.
반면 대화 상자(Dialog Based)의 경우에는 DoModal 또는 CreateIndirect를 사용하게 됩니다.
MFC는 이런 과정을 모두 캡슐화하여, 개발자가 복잡한 WinAPI 호출 없이도 손쉽게 UI 요소를 구성할 수 있게 해줍니다.

📌 기본 CWnd 윈도우 생성 예제

CODE BLOCK
CMyWnd* pWnd = new CMyWnd();
pWnd->Create(NULL, _T("My Window"), WS_OVERLAPPEDWINDOW, CRect(100,100,400,300), pParentWnd, 0);
pWnd->ShowWindow(SW_SHOW);

위 코드를 보면 CWnd를 상속한 클래스에서 직접 객체를 생성하고,
Create 함수를 호출하여 윈도우를 생성한 후,
ShowWindow로 화면에 표시하는 방식입니다.

PreCreateWindow는 언제 활용하나요?

윈도우가 생성되기 전 단계에서 스타일이나 클래스 속성을 수정하고 싶을 때는 PreCreateWindow 함수를 오버라이딩합니다.
이를 통해 개발자는 보다 유연하게 커스터마이징이 가능해지며, 예를 들어 창의 테두리 스타일, 배경 색상, 메뉴 속성 등을 지정할 수 있습니다.

💡 TIP: PreCreateWindow에서 설정한 스타일은 이후 변경이 어려우므로,
초기 윈도우 생성 전에 반드시 필요한 속성들을 조정해두는 것이 좋습니다.

이처럼 MFC는 윈도우 생성 과정을 고도로 추상화하여,
복잡한 코드 없이도 유연한 인터페이스를 구현할 수 있게 도와줍니다.
처음에는 구조가 다소 복잡해 보일 수 있지만,
직접 작성해보며 흐름을 이해하면 점차 익숙해질 수 있습니다.







⚙️ 메시지 처리 메커니즘 이해

MFC에서 가장 핵심적인 개념 중 하나가 바로 메시지 처리입니다.
윈도우 프로그램은 이벤트 기반 구조를 가지기 때문에, 사용자의 입력이나 시스템 이벤트를 처리하는 것이 매우 중요하죠.
CWnd는 이러한 메시지를 받아 처리하는 구조를 가지고 있으며, 이를 위해 메시지 맵(Message Map)이라는 독특한 방식을 사용합니다.

메시지 맵은 일종의 매핑 테이블로, 특정 메시지가 들어왔을 때 어떤 함수로 연결할지를 정의하는 구조입니다.
이 덕분에 CWnd를 상속한 클래스는 자신의 메시지를 직접 핸들링할 수 있으며, 다른 컴포넌트와 충돌 없이 독립적인 동작이 가능합니다.

📌 메시지 맵의 기본 구조

CODE BLOCK
BEGIN_MESSAGE_MAP(CMyWnd, CWnd)
    ON_WM_PAINT()
    ON_WM_LBUTTONDOWN()
    ON_WM_KEYDOWN()
END_MESSAGE_MAP()

위 구조는 CMyWnd 클래스가 WM_PAINT, WM_LBUTTONDOWN, WM_KEYDOWN 같은 메시지를 처리하겠다고 선언하는 방식입니다.
각 매크로는 실제로 내부적으로 메시지 ID와 해당 함수 포인터를 연결하는 역할을 합니다.

핸들러 함수는 어떻게 작성하나요?

핸들러 함수는 일반적인 멤버 함수로 작성하며, 보통 afx_msg 키워드를 붙여 선언합니다.
실제로는 afx_msg는 단지 MFC 코드 생성 도구를 위한 힌트일 뿐이며, 컴파일러에게 직접적인 영향을 주진 않습니다.

CODE BLOCK
void CMyWnd::OnPaint()
{
    CPaintDC dc(this);
    dc.TextOutW(10, 10, _T("Hello MFC"));
}

이렇게 작성한 함수는 메시지가 도착했을 때 자동으로 호출되며,
기본적인 UI 출력이나 사용자 입력 반응을 처리하는 데 활용됩니다.

💎 핵심 포인트:
모든 MFC 클래스가 메시지 맵을 사용하는 것은 아닙니다.
CWinThread, CDocument 등은 메시지 맵을 사용하지 않으며, CWnd를 직접 상속한 클래스에만 적용됩니다.

MFC의 메시지 처리 구조는 복잡한 듯 보이지만, 실제로는 잘 구조화된 방식으로 초보자도 금방 익숙해질 수 있습니다.
핵심은 메시지 맵의 흐름과 역할을 이해하고, 자주 사용되는 메시지들을 손에 익히는 것입니다.


🔌 GDI를 이용한 그리기 구현

MFC에서 그래픽 요소를 화면에 출력하고 싶다면, GDI(Graphic Device Interface)를 활용해야 합니다.
CWnd는 GDI의 대부분 기능을 사용할 수 있도록 래핑되어 있어, 복잡한 저수준 API 없이도 다양한 그리기 작업이 가능합니다.

주로 사용하는 클래스는 CDC이며, OnPaint 함수에서 CPaintDC를 통해 쉽게 생성할 수 있습니다.
이를 통해 선(Line), 사각형(Rectangle), 텍스트(Text), 이미지(Bitmap) 등을 자유롭게 출력할 수 있습니다.

📌 텍스트와 도형 출력 예제

CODE BLOCK
void CMyWnd::OnPaint()
{
    CPaintDC dc(this);

    // 텍스트 출력
    dc.TextOutW(50, 30, _T("MFC GDI 예제입니다."));

    // 선 그리기
    dc.MoveTo(40, 60);
    dc.LineTo(200, 60);

    // 사각형 그리기
    dc.Rectangle(40, 80, 200, 150);
}

위 코드는 텍스트와 선, 사각형을 그리는 기본적인 GDI 예제입니다.
MoveToLineTo를 사용하면 직선을 그릴 수 있고, Rectangle 함수로 도형을 출력할 수 있습니다.

브러시와 펜은 어떻게 사용할까요?

좀 더 복잡한 도형을 그리거나, 스타일을 변경하고 싶다면 CBrushCPen을 사용할 수 있습니다.
이 객체들을 생성한 후 SelectObject로 적용하면 선 두께나 색상, 채우기 스타일 등을 다양하게 조절할 수 있죠.

💡 TIP: GDI 자원은 수동으로 해제하지 않으면 누수가 발생할 수 있습니다.
Create로 만든 객체는 반드시 DeleteObject를 호출해 해제하세요.

💎 핵심 포인트:
OnPaint 함수는 자주 호출되므로, 복잡한 연산은 피하고 최대한 가볍게 처리하는 것이 성능 향상에 유리합니다.

MFC의 GDI 활용은 실시간 차트, 그래픽 툴, 커스터마이징 UI 등에 자주 사용됩니다.
기초부터 차근히 익히면 나만의 도구를 만드는 데에도 큰 도움이 됩니다.







💡 실전 예제: 사용자 정의 윈도우 만들기

앞서 배운 CWnd의 생성, 메시지 처리, 그리기 기능을 종합하여 실제 사용자 정의 윈도우 클래스를 만들어보겠습니다.
직접 클래스를 상속받고, 윈도우를 생성한 후, 사용자 메시지를 처리하고 텍스트를 출력하는 방식으로 구성됩니다.

📌 사용자 정의 클래스 구현 예제

CODE BLOCK
// 사용자 정의 클래스 선언
class CMyCustomWnd : public CWnd
{
public:
    afx_msg void OnPaint();
    DECLARE_MESSAGE_MAP()
};

// 메시지 맵 설정
BEGIN_MESSAGE_MAP(CMyCustomWnd, CWnd)
    ON_WM_PAINT()
END_MESSAGE_MAP()

// 그리기 함수 정의
void CMyCustomWnd::OnPaint()
{
    CPaintDC dc(this);
    dc.TextOutW(20, 20, _T("나만의 사용자 윈도우!"));
}

이 코드는 CWnd를 상속받은 CMyCustomWnd 클래스를 만들고,
OnPaint를 통해 텍스트를 출력하는 간단한 예제입니다.
이 클래스를 메인 프레임에서 생성하고 보여주면 실행됩니다.

프레임에서 윈도우 생성하기

CODE BLOCK
CMyCustomWnd* pWnd = new CMyCustomWnd();
pWnd->Create(NULL, _T("Custom Window"), WS_OVERLAPPEDWINDOW, CRect(100, 100, 400, 300), pParentWnd, 0);
pWnd->ShowWindow(SW_SHOW);

이제 하나의 윈도우가 완성되었습니다.
이 구조를 기반으로 버튼 추가, 사용자 입력 처리, 데이터 출력 등 다양한 기능을 확장해 나갈 수 있습니다.

💎 핵심 포인트:
사용자 정의 CWnd 클래스는 메시지와 그리기 방식에 대한 완전한 제어권을 갖게 됩니다.
복잡한 커스터마이징이 필요한 경우 매우 유용하게 활용됩니다.

MFC의 장점은 이처럼 객체지향적으로 구성된 구조 덕분에 유지보수와 확장성이 뛰어나다는 데 있습니다.
처음에는 다소 복잡하게 느껴질 수 있지만, 직접 구현해보면 의외로 쉽게 다가올 수 있습니다.


자주 묻는 질문 (FAQ)

CWnd는 반드시 상속해서 사용해야 하나요?
대부분의 경우 CWnd를 직접 상속하여 사용자 정의 윈도우 클래스를 생성하는 것이 일반적입니다.
하지만 간단한 테스트나 제어용으로 CWnd 인스턴스를 바로 생성하여 사용할 수도 있습니다.
CWnd와 CDialog의 차이는 무엇인가요?
CWnd는 모든 윈도우의 기반 클래스이고, CDialog는 대화 상자를 위한 파생 클래스입니다.
대화 상자는 보통 사용자 입력을 받는 폼 형식에 자주 사용됩니다.
메시지 맵 없이 메시지를 처리할 수 있나요?
기본적으로는 메시지 맵을 통해 처리하는 것이 원칙이지만,
메시지 루프를 직접 오버라이드하거나, API 수준에서 직접 처리하는 방법도 가능합니다.
다만 MFC 철학에 맞지 않기 때문에 권장되지 않습니다.
OnPaint 외에 그리기에 사용되는 함수가 있나요?
OnDraw, OnEraseBkgnd 같은 함수들도 사용됩니다.
특히 View 기반 클래스에서는 OnDraw가 많이 활용됩니다.
윈도우가 깜빡이거나 느릴 때 대처법은?
더블 버퍼링을 구현하거나, 복잡한 연산을 OnPaint 바깥에서 처리하는 것이 좋습니다.
그리기 연산을 최소화하는 것이 핵심입니다.
CWnd 객체를 삭제하면 자동으로 윈도우도 제거되나요?
CWnd 객체를 delete하면 내부 HWND도 함께 소멸됩니다.
단, DestroyWindow를 먼저 호출하는 것이 안정적입니다.
모든 컨트롤이 CWnd를 상속하나요?
예, CButton, CEdit, CListBox 등 대부분의 MFC 컨트롤 클래스는 CWnd를 직접 또는 간접적으로 상속합니다.
CWnd에서 커스텀 마우스 이벤트를 처리할 수 있나요?
네, ON_WM_MOUSEMOVE, ON_WM_LBUTTONDOWN 등 다양한 마우스 메시지를 핸들링할 수 있으며,
사용자 정의 처리를 위한 조건 분기도 자유롭게 구현할 수 있습니다.



🧩 CWnd를 제대로 이해하면 MFC가 쉬워집니다

이번 글에서는 MFC 프로그래밍의 핵심 클래스인 CWnd에 대해 알아보았습니다.
CWnd는 윈도우 생성, 메시지 처리, GDI 그리기 등 모든 MFC 응용 프로그램의 중심이 되는 클래스입니다.
기초적인 구조부터 시작해 실제 사용자 정의 윈도우 구현까지 살펴보면서,
단순히 이론적인 설명에 그치지 않고 실습 중심으로 구성해 이해를 도왔습니다.

CWnd는 초보자에게는 다소 어려운 개념일 수 있지만,
그 개념을 잡고 나면 MFC 프레임워크 전체를 이해하는 데 큰 도움이 됩니다.
메시지 맵 구조, GDI를 이용한 그리기, 그리고 클래스 확장 방식 등은 MFC의 구조적인 특징을 반영하고 있으며,
앞으로 다양한 UI 구성과 윈도우 처리 로직에 직접 활용할 수 있는 중요한 기반이 됩니다.

이제 CWnd에 대한 자신감을 가지고 MFC의 다른 고급 기능에도 도전해보세요.
실무에서도 여전히 널리 쓰이는 프레임워크인 만큼, 익혀두면 다양한 프로젝트에서 유용하게 활용될 수 있습니다.


🏷️ 관련 태그:MFC, CWnd, 메시지맵, GDI, C++, 윈도우프로그래밍, MFC기초, 사용자정의윈도우, CDialog, WinAPI