저번 포스팅에서 갑자스럽게 윈도우 프로그래밍에 꽂혀서 코딩을 해보았는데 개념이 생각이 나지 않아
다시 되세김 하고자 포스팅을 시작해 볼까 합니다.
MFC의 기본적인 클레스대한 이해를 돕고자한 포스팅이며 개인적인 지식이며, 잘 못된 내용이 포함되어 있을 수 있습니다.
기본적인 클레스
CWinApp - 앱 자체를 의미 합니다.
#include "resource.h" // 주 기호입니다.
// CHelloWorldApp:
// 이 클래스의 구현에 대해서는 HelloWorld.cpp을(를) 참조하세요.
//
class CHelloWorldApp : public CWinAppEx
{
public:
CHelloWorldApp() noexcept;
...
}
해더 파일을 살펴 보면 CWinApp 이라는 클레스를 상속 받아 만들어졌음을 알 수 있습니다.
자제적으로 UI/UX를 표시하는 기능은 없으며, 프로그램의 초기화, 종료, 실행 등의 기능이 캡슐화 되어 있습니다.
모든 앱에는 하나의 WinApp만을 상속 받아 작성되어야 합니다.
주요적인 코드를 보면 메인프레임을 생성하고 Document Template를 통해 Document, MainFrame 및 뷰 사이를 연결하는 역활을 합니다.
InitContextMenuManager();
InitKeyboardManager();
InitTooltipManager();
CMFCToolTipInfo ttParams;
ttParams.m_bVislManagerTheme = TRUE;
theApp.GetTooltipManager()->SetTooltipParams(AFX_TOOLTIP_TYPE_ALL,
RUNTIME_CLASS(CMFCToolTipCtrl), &ttParams);
// 애플리케이션의 문서 템플릿을 등록합니다. 문서 템플릿은
// 문서, 프레임 창 및 뷰 사이의 연결 역할을 합니다.
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CHelloWorldDoc),
RUNTIME_CLASS(CMainFrame), // 주 SDI 프레임 창입니다.
RUNTIME_CLASS(ListViewSample));
if (!pDocTemplate)
return FALSE;
AddDocTemplate(pDocTemplate);
// 표준 셸 명령, DDE, 파일 열기에 대한 명령줄을 구문 분석합니다.
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// 명령줄에 지정된 명령을 디스패치합니다.
// 응용 프로그램이 /RegServer, /Register, /Unregserver 또는 /Unregister로 시작된 경우 FALSE를 반환합니다.
if (!ProcessShellCommand(cmdInfo))
return FALSE;
// 창 하나만 초기화되었으므로 이를 표시하고 업데이트합니다.
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
// 타이틀 수정
m_pMainWnd->SetWindowTextW(L"Hello World");
CFrameWnd - CMainFrame
MFC를 이용하여 작성한 코드는 기본적으로 프레임 윈도우를 두고 거기에 차일드 윈도우들을 붙이는 형식입니다.
화면상에 보이는 부분은 대부분 프레임 윈도상에 구현된다고 보면 됩니다.
// MainFrm.h: CMainFrame 클래스의 인터페이스
//
#pragma once
class CMainFrame : public CFrameWndEx
{
protected: // serialization에서만 만들어집니다.
CMainFrame() noexcept;
DECLARE_DYNCREATE(CMainFrame)
...
}
CMainFrame의 클레스의 선언부를 보면 CFrameWnd를 상속 받아 구현된것을 볼 수 있습니다.
클레스 위자드에서 메서드를 보시면 OnCreate, OnToolbarCreateNew, OnViewCustomize 등과 같이 메시지 핸들러 함수가 등록되어 있는 것을 볼 수 있습니다.
대표적인 메시지로 WM_CREATE라는 함수가 발생하였을때 OnCreate가 실행되는 것입니다.
이 메시지는 생성될때 발생하는 메시지 입니다.
이 외에도 키보드 이벤트가 발생할때 WM_COMMAND, 뷰나 프로그램이 종료할때 WM_DESTROY, 화면에 무엇인가 그려질째 WM_PAINT등의 다양한 메시지가 존재합니다.
메시지 처리는 Win32API의 메시지 처리를 보시면 좀더 수월하게 이해 할 수 있으리라 생각합니다.
HINSTANCE hinst;
HWND hwndMain;
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
{
MSG msg;
BOOL bRet;
WNDCLASS wc;
UNREFERENCED_PARAMETER(lpszCmdLine);
// Register the window class for the main window.
if (!hPrevInstance)
{
wc.style = 0;
wc.lpfnWndProc = (WNDPROC) WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon((HINSTANCE) NULL,
IDI_APPLICATION);
wc.hCursor = LoadCursor((HINSTANCE) NULL,
IDC_ARROW);
wc.hbrBackground = GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = "MainMenu";
wc.lpszClassName = "MainWndClass";
if (!RegisterClass(&wc))
return FALSE;
}
hinst = hInstance; // save instance handle
// Create the main window.
hwndMain = CreateWindow("MainWndClass", "Sample",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, (HWND) NULL,
(HMENU) NULL, hinst, (LPVOID) NULL);
// If the main window cannot be created, terminate
// the application.
if (!hwndMain)
return FALSE;
// Show the window and paint its contents.
ShowWindow(hwndMain, nCmdShow);
UpdateWindow(hwndMain);
// Start the message loop.
while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// Return the exit code to the system.
return msg.wParam;
}
MFC에서는 메시지가 발생할때 특정 기능을 추가하기 위해서는 가상함수 중 PreTranslateMessage를 통해 메시지를 후킹할 수 있으며, 다른 방법으로는 가상함수 중 WindowProc을 통해서도 가능합니다.
프레임 윈도우가 하는 역활은 툴바, 스테이터스바, 메뉴, 뷰 등과 같이 메시지가 발생할때 처리하는 역활을 합니다.
CWnd - 모든 윈도우는 CWnd상속 받아 생성된다.
CMainFrame도 CFrameWnd를 상속받아 생성된거 같지만 끝까지 따라가보면 CWnd를 상속 받아 생성 된것을 알 수 있습니다.
따라서 CWnd 객체에 대해서 자세히 이해할 필요가 있습니다. 물론 더 상위에 대한 클레스에 대해 이해를 한다면 좋겠지만 현재 수준에서 상위 클래스를 이해하기도 어려울 뿐더라 아직은 윈도우에 대한 이해가 더 중요하므로 이부분을 중점적으로 이해 하는게 좋을 것으로 생각됩니다.
위에서 설명했듯이 Win32API 또한 윈도우 프로그래밍이기 때문에 둘 다 다뤄보면 일맥 상통하는 부분을 많이 찾을 수 있을 것입니다. 두개의 공통된 부분을 보면 많은 이해력이 생길 수 있으며, Win32코드로 MFC를 채울때도 존재합니다.
불가능하진 않더라도 꼭 ListBox만 고집해야 하는 상황이 아니라면 구지 사용할 이유가 없을듯 합니다.
멀티 컬럼을 위해서는 ListView를 사용하는게 정신건강에도 좋을듯 합니다.
이번 포스팅에서는 ListView를 사용하는 법을 같이 진행해 봅시다.
ListView를 사용하기 위한 CFormview를 기본클래스로 클래스를 하나 생성하도록 하겠습니다.
InitInstance 함수의 항목 중 RUNTIME_CLASS를 추가한 클래스로 변경합니다.
// 애플리케이션의 문서 템플릿을 등록합니다. 문서 템플릿은
// 문서, 프레임 창 및 뷰 사이의 연결 역할을 합니다.
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CHelloWorldDoc),
RUNTIME_CLASS(CMainFrame), // 주 SDI 프레임 창입니다.
RUNTIME_CLASS(ListViewSample)); // 해당 CHelloWorldView => ListViewSample
ListViewSample에 빨간 줄이 들어 온다면 다음 항목에 추가한 해더 파일을 추가해 줍니다.
// HelloWorld.cpp: 애플리케이션에 대한 클래스 동작을 정의합니다.
//
#include "pch.h"
#include "framework.h"
#include "afxwinappex.h"
#include "afxdialogex.h"
#include "HelloWorld.h"
#include "MainFrm.h"
#include "HelloWorldDoc.h"
#include "HelloWorldView.h"
#include "ListViewSample.h" // 해당 부분 추가
해당 파일을 수정하는 이유를 알기 위해서는 MFC의 구조를 조금 이해 하셔야 편리한거 같습니다.
CWinApp -> CDocument -> CFrameWnd -> CView, CToolBar, CStatusBar 형식으로 포함 하도록 되어 있습니다.
프로그램이 실행되면 전체적인 것을 포함하는 것은 CWinApp 이라고 생각하시면됩니다.
한 응용프로그램을 관장하며, 초기화, 실행, 종료를 캡슐화 합니다.
CWinApp 아래에 도큐먼트가 포함되고 토큐먼트에 툴바, 뷰, 스테이터스 등이 포함된다고 이해 하고 있습니다.
툴바나 스테이스터스바, 메뉴 등의 위치는 다들 알고 있으실 테니 표시는 생략했습니다.
// 애플리케이션의 문서 템플릿을 등록합니다. 문서 템플릿은
// 문서, 프레임 창 및 뷰 사이의 연결 역할을 합니다.
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CHelloWorldDoc),
RUNTIME_CLASS(CMainFrame), // 주 SDI 프레임 창입니다.
RUNTIME_CLASS(ListViewSample)); // 해당 CHelloWorldView => ListViewSample
처음 수정한 코드입니다. 해당 코드를 수정한 다음 리소스 뷰를 통해 추가한 뷰를 열어 봅니다.
위와 같은 폼이 생성되었을 것입니다.
해당 폼에 리스트 뷰를 추가 하고 컨트롤 변수를 추가 해 줍니다.
m_ctrlListView라는 컨트롤 변수를 추가 했습니다.
그리드 형식으로 표시하길 원하기 때문에 리스트 컨트롤의 속성에서 리포트 방식으로 변경합니다.
솔루션 탐색기를 통해 ListViewSample.cpp 로 돌아 온 후 데이터를 입력하기 위한 코딩을 삽입합니다.
코드를 봤더니 추가할 적당한 함수를 찾지 못해 추가해 주기로 하겠습니다.
CTRL + SHIFT + X 단축키를 눌러 클래스 위자드를 실행 한 후 가상함수 탭에서 OnInitialUpdate 함수를 찾아 추가해 줍니다.
추가 하신 후 코드편집을 누르시면 해당 위치로 이동하니 이용하시면 편리합니다.
CListCtrl의 함수는 컬럼 해더 부분과 데이터 부분으로 나뉘어 집니다.
그래서 컬럼 해더를 따로 추가 해 주셔야 됩니다.
void ListViewSample::OnInitialUpdate()
{
CFormView::OnInitialUpdate();
// TODO: 여기에 특수화된 코드를 추가 및/또는 기본 클래스를 호출합니다.
// 컬럼 추가
m_ctrlListView.InsertColumn(0, L"컬럼1", LVCFMT_LEFT, 100, -1);
m_ctrlListView.InsertColumn(1, L"컬럼2", LVCFMT_LEFT, 100, -1);
m_ctrlListView.InsertColumn(2, L"컬럼3", LVCFMT_LEFT, 100, -1);
// 컬럼 삭제
m_ctrlListView.DeleteColumn(2);
// 데이터 추가
m_ctrlListView.InsertItem(0, L"데이터 1");
m_ctrlListView.SetItem(0, 1, LVIF_TEXT, L"데이터 2", 0, 0, 0, NULL);
m_ctrlListView.InsertItem(1, L"데이터 1");
m_ctrlListView.SetItem(1, 1, LVIF_TEXT, L"데이터 2", 0, 0, 0, NULL);
}