MFC
[TOC]
MFC 설치
- Visual Studio 설치 시 MFC를 선택하지 않으면 MFC 프로젝트를 생성할 수 없다.
- Visual Studio 설치 파일을 이용해 추가 설치해야한다.
- 설치파일이 있어야하고 없으면 다시 MS에서 다운 받는다.
- 새 프로젝트 대화상자에 “Visual Studio 설치 관리자 열기”를 클릭해도 된다.
- C++를 사용한 데스크톱 개발
- x86 및 x64용 Visual C++ ATL
- x86 및 x64용 Visual C++ MFC
MFC 프로그램의 종류
SDI(Single Document Interface)
- 윈도우 프래임 안에 하나의 윈도우만 생성
- CDocument, CView
MDI(Multi Document Interface)
- 윈도우 프래임 안에 여러 윈도우가 생성됨
대화상자 기반
- SDI, MDI와 다르게 CDialog, CWinApp으로 구성된다.
MFC 기본 생성 클래스
App
- 응용 프로그램 클래스
- 프로그램 전체에 대한 설정
- 프레임 윈도우 생성
- 메시지 루프 제공
MainFrame
- 메인(프레임) 윈도우 클래스 정의
- 윈도우에서의 작업을 정의
- 분할 및 View 관리
View
- 실제 화면 출력을 담당
Doc
메시지 맵
-
이벤트 발생시 호출될 콜백함수를 정의
-
BEGIN_MESSAGE_MAP, END_MESSAGE_MAP 블록 안에 정의
-
툴에 의해 자동으로 생성됨
MFC 작동 구조
- Win32 코드를 내부에 감추고 메시지 발생 시 메시지 맵에 등록된 메서드를 호출한다.
- …MFC.cpp
- theApp
- 어플리케이션 실행 시 호출될 메서드들이 정의되어있다.
- 이 메서드들에 할 일을 작성한다.
- InitInstance
- 각종 초기화 작업
- 메인 윈도우 객체 생성
- theApp
- CMainFrame::On~()
- MFC 프레임워크에서 어플 실행시 메시지루프에서 Run() 함수를 실행하여 발생된 메시지에 따라 On~으로 시작하는 메서드를 호출해준다.
대화상자 기반
- DoDataExchange()
- 컨트롤과 변수를 바인딩 시켜주는 코드가 들어있다.
- 바인딩만으론 안되고 UpdateData(true); 함수를 호출해야한다.
- DDX_ (DoDataeXchange)
- DDX_Text
- Edit Control 객체를 CString 변수와 연결
- DDX_Control
- Control과 변수를 연결
- DDX_IPAddress
- IP Address를 입력받도록 설계된 UI를 위한 함수
- DDX_DateTimeCtrl
- CDataTimeCtrl과 연동을 위해 사용
- DDX_Text
- DDV_ (Dialog Data Validation)
- DDV_MinMax
- 최대, 최솟값 지정
- DDV_MaxChar
- 최대 글자수 지정
- DDV_MinMax
- OnInitDialog()
- UpdateData()
- UpdateData(true);//컨트롤의 값을 변수로 전송
- UpdateData(false);//변수의 값을 컨트롤로 전송
디버그 메시지 출력
- TRACE(“text”, p1, p2, …);
컨트롤에 이벤트 추가
- 리소스 편집기에서 컨트롤에 우클릭하여 이벤트를 추가할 수 있다.
- 이벤트를 추가하면 해당되는 콜백함수 코드가 생성된다.
리소스 편집기에서 탭 순서보기
- ctrl + d
라디오 컨트롤
- 그룹으로 지정해야함
- 그룹은 탭번호 우선순위가 가장 높은(숫자 낮은) 라디오 버튼만 True로 지정하면 됨
- 그룹 True한 라디오 버튼에서 int형 변수를 추가
- 선택된 라디오 버튼 순서에 맞는 값이 변수에 적용됨
리스트 컨트롤
- hList.AddString()
- hList.DeleteString()
스크롤바
- 스크롤바 설정값은 코드에서 작성한다.
- 스크롤 관련 이벤트는 스크롤바 컨트롤에 콜백하수를 지정하지 않고 부모 윈도우의 WM_HSCROLL, WM_VSCROLL 메시지를 이용해야한다.
//스크롤바 최소,최대값 지정
//hVScrollBar.SetScrollRange(0, 100);
//스크롤바 정보 한번에 설정
SCROLLINFO sScrollInfo;
sScrollInfo.cbSize = sizeof(sScrollInfo);
sScrollInfo.fMask = SIF_ALL;
sScrollInfo.nMin = 0;
sScrollInfo.nMax = 100;
sScrollInfo.nPage = 10;
sScrollInfo.nTrackPos = 0;
sScrollInfo.nPos = 50;
hVScrollBar.SetScrollInfo(&sScrollInfo);
void CWinMFCDialogBasedDlg::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) {
// TODO: 여기에 메시지 처리기 코드를 추가 및/또는 기본값을 호출합니다.
if (pScrollBar == &hVScrollBar) {
SCROLLINFO sScrollInfo;
pScrollBar->GetScrollInfo(&sScrollInfo);
switch (nSBCode) {
case SB_PAGEUP:
{
sScrollInfo.nPos -= sScrollInfo.nPage;
break;
}
case SB_PAGEDOWN:
{
sScrollInfo.nPos += sScrollInfo.nPage;
break;
}
case SB_LINEUP://화살표클릭
{
sScrollInfo.nPos -= sScrollInfo.nPage/10;
break;
}
case SB_LINEDOWN://화살표클릭
{
sScrollInfo.nPos += sScrollInfo.nPage/10;
break;
}
case SB_THUMBPOSITION://트랙 움직이고 나서
case SB_THUMBTRACK://트랙 움직이는 동안
{
sScrollInfo.nPos = sScrollInfo.nTrackPos;
break;
}
default:
break;
}
hVScrollBar.SetScrollPos(sScrollInfo.nPos);
}
CDialogEx::OnVScroll(nSBCode, nPos, pScrollBar);
}
MFC에서 DC 사용
- CDC 클래스를 사용
CDC *dc1 = CWnd::GetDC();
CDC *dc2 = this->GetDC();
CDC *dc3 = GetDC();
CClientDC dc(this);
//세 변수 모두 다른 주소값을 갖는다. GetDC요청시 새로 생성하여 넘겨주는 듯.
공용 컨트롤
- MFC에서 여러 응용프로그램에서 공통으로 빈번히 사용하는 컨트롤을 모아서 공용 컨트롤로 제공
- VS2013 부터 일반 컨트롤과 동일한 취급하도록 통합됨
- 사용법
- CommCtrl.h 파일 추가
- ComCtrl32.lib 라이브러리를 프로젝트에 링크
- 윈도우 생성 위치에 InitCommonControls() 함수를 실행하여 COMCTRL.DLL을 메모리에 로드
- MFC 프로젝트 생성 시 자동으로 코드에 포함됨
- 종류
- List Control
- Spin Control
- Slider Control
- IP Address Control
- Progress Control
- Tab Control
- Month Calendar Control
- Data Time Control
- Tree Control
ListControl
//스타일설정
cListCtrl.SetExtendedStyle(
LVS_EX_FULLROWSELECT // 아이템 전체가 클릭되도록 한다.
| LVS_EX_GRIDLINES // 서브아이템 사이에 그리드 라인을 넣는다.
);
//컬럼삽입
cListCtrl.InsertColumn(0, "Column1", 0, 200);
cListCtrl.InsertColumn(1, "Column2", 0, 200);
pcListCtrl.InsertColumn(0, "Column1", 0, 200);
pcListCtrl.InsertColumn(1, "Column2", 0, 200);
//아이템삽입
LVITEMA item = {};
if (cListCtrl.GetItemCount() == 0) {
item.pszText = "0";
}
else {
item.pszText = "9";
}
item.mask = LVIF_TEXT;
item.iItem = 1;//아이템이 삽입될 위치
cListCtrl.InsertItem(&item);
cListCtrl.SetItemText(1, 1, "hi");
TreeControl
//TreeCtrl
HTREEITEM root = cTreeCtrl.InsertItem("root");
HTREEITEM sub = cTreeCtrl.InsertItem("sub", root);
cTreeCtrl.InsertItem("sub2", root);
cTreeCtrl.InsertItem("subsub", sub);
공용 대화상자
- 임의로 만드는게 아니라 MFC에서 제공하는 Common Dialog Classes
색상 대화상자
CColorDialog cColorDialog;
if (cColorDialog.DoModal() == IDOK) {
COLORREF color = cColorDialog.GetColor();
CString str;
str.Format("%u", color);
AfxMessageBox(str);
}
파일 대화상자와 MFC의 File
- https://yyman.tistory.com/515?category=571805
CFileDialog cFileDialog(true/*true:open, false:save*/, nullptr, nullptr, OFN_HIDEREADONLY, "filetype1|*.*|filetype2|*.exe|");
if (cFileDialog.DoModal() == IDOK) {
CString str;
str.Format("%s, %s, %s, %s\n", cFileDialog.GetFileName(), cFileDialog.GetFileTitle(), cFileDialog.GetFileExt(), cFileDialog.GetFolderPath());
TRACE(str);
CStdioFile cStdioFile;
if (cStdioFile.Open(cFileDialog.GetPathName(), CFile::modeRead | CFile::typeText)) {
cStdioFile.ReadString(str);
TRACE("%s\n", str);
cStdioFile.Close();
}
}
폰트 대화상자
CFontDialog cFontDialog;
if (cFontDialog.DoModal() == IDOK) {
CDC *dc1 = CWnd::GetDC();
CDC *dc2 = this->GetDC();
CDC *dc3 = GetDC();
CClientDC dc(this);
CFont cFont, *pcOldFont;
//Logical Font
//cFont.CreateFontIndirectA에 전달하면 이 값과 가장 비슷한 Physicial Font를
//Font Mapper이 찾아서 dc에 등록해준다.
LOGFONT sLOGFONT;
//방법1. LOGFONT 구조체를 사용, 좀 더 간단
cFontDialog.GetCurrentFont(&sLOGFONT);
cFont.CreateFontIndirectA(&sLOGFONT);
////방법2. 인자로 전달, 복잡하다
cFont.CreateFont(
12, // nHeight
0, // nWidth
0, // nEscapement
0, // nOrientation
FW_NORMAL, // nWeight
FALSE, // bItalic
FALSE, // bUnderline
0, // cStrikeOut
ANSI_CHARSET, // nCharSet
OUT_DEFAULT_PRECIS, // nOutPrecision
CLIP_DEFAULT_PRECIS, // nClipPrecision
DEFAULT_QUALITY, // nQuality
DEFAULT_PITCH | FF_SWISS, // nPitchAndFamily
cFontDialog.GetFaceName()); // lpszFacename
//방법3. 간단한 설정
cFont.CreatePointFont(120, "");
//폰트 적용
pcOldFont = dc.SelectObject(&cFont);
dc.TextOutA(0, 0, "hihi");
//이전 폰트로 돌림
dc.SelectObject(pcOldFont);
cFont.DeleteObject();
//이전 설정으로 그려진다.
dc.TextOutA(200, 0, "hoho");
}
MFC에 Direct3D 적용하기
MFC 프로젝트의 윈도우 기본 구조
- 왼쪽은 렌더링 뷰
- 오른쪽은 폼 뷰
MFC 프로젝트 생성
- 응용 프로그램 종류
- 단일 문서(SDI)
- 프로젝트 스타일
- MFC standard
- 복합 문서 지원
- 없음
- 사용자 인터페이스 기능
- 분할 창 - 선택
- 고급기능
- 공용 컨트롤 매니페스트 - 선택
- 나머지는 해제
- 유니코드 라이브러리 사용
- VS2017에는 이 옵션이 없다.
MFC 프로젝트 분할 창 설정
FormView 생성
- Dialog와 같이 컨트롤을 올려서 사용할 수있는 View
- 새 항목 추가 > MFC 클래스 > 기본 클래스 CFormView로 설정하여 생성하면 리소스와 클래스가 만들어짐
- 기본 클래스에 CFormView가 없을 경우
- CFormView를 상속받는 클래스를 수동 생성
- .h, .cpp 파일에 다음과 같은 매크로 추가
- .h : DECLARE_DYNCREATE(CMenuFormView)
- .cpp : IMPLEMENT_DYNCREATE(CMenuFormView, CFormView)
- 리소스에 IDD_FORMVIEW 다이얼로그를 생성하고 위에 생성한 클래스와 연결
- 리소스 뷰에서 폼뷰를 우클릭 > 클래스 마법사 > 클래스 이름에서 CFormView를 상속받는 클래스를 지정
- 위 작업을 하면 클래스 선언, 클래스 구현에 .h, .cpp 파일이 자동으로 등록됨
- CFormView를 상속받는 클래스를 수동 생성
//.h
#pragma once
#include <afxext.h>
class CMenuFormView :
public CFormView {
public:
CMenuFormView();
DECLARE_DYNCREATE(CMenuFormView)
~CMenuFormView();
};
//.cpp
#include "stdafx.h"
#include "CMenuFormView.h"
#include "resource.h"
IMPLEMENT_DYNCREATE(CMenuFormView, CFormView)
CMenuFormView::CMenuFormView():CFormView(IDD_FORMVIEW){
}
CMenuFormView::~CMenuFormView() {
}
CSplitterWnd 설정
메인프레임 클래스의 OnCreateClient() 함수에서 CSplitterWnd 및 View를 생성해준다.
- CSplitterWnd.Create()
- 동적 분할창 생성
- CSplitterWnd.CreateStatic()
- 정적 분할창 생성
- 뭐가 다른지 정리
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT /*lpcs*/,
CCreateContext* pContext) {
//CSplitterWnd 생성
if (!m_wndSplitter.CreateStatic(
this,//메인프레임
1,//행
2//열
)) {
return false;
}
//분할창 크기 설정
CRect cRect;
GetClientRect(&cRect);
int iClientWidth = cRect.right - cRect.left;
int iMenuFormViewWidth = iClientWidth / 6;
//분할창 생성 1
if (!m_wndSplitter.CreateView(0, 0,
RUNTIME_CLASS(CMy10D3DMFCView),
CSize(iClientWidth - iMenuFormViewWidth, 0), pContext
)) {
return false;
};
//분할창 생성 2
if (!m_wndSplitter.CreateView(0, 1,
RUNTIME_CLASS(CMenuFormView),
CSize(iMenuFormViewWidth, 0), pContext
)) {
return false;
};
}
분할 경계 프레임 고정하기
- 렌더링 뷰의 크기 변경은 Direct3D의 백버퍼 크기 변경의 주요 원인이기 때문에 막는 것이 좋다.
- CSpritterWnd를 상속받아 WM_NCHITTEST 메시지 처리를 재정의하여 크기 변경하지 못하게 한다.
//.h
class CSpiltterWndStatic : public CSplitterWnd {
public:
CSpiltterWndStatic();
~CSpiltterWndStatic();
protected:
afx_msg LRESULT OnNcHitTest(CPoint point);
DECLARE_MESSAGE_MAP();
};
//.cpp
BEGIN_MESSAGE_MAP(CSpiltterWndStatic, CSplitterWnd)
ON_WM_NCHITTEST()
END_MESSAGE_MAP()
CSpiltterWndStatic::CSpiltterWndStatic() {}
CSpiltterWndStatic::~CSpiltterWndStatic() {}
LRESULT CSpiltterWndStatic::OnNcHitTest(CPoint point) {
return HTNOWHERE;
}
MFC에서 무한 루프
- App OnIdle()에서 무한루프를 만들 수 있다.
- 여기서 View의 업데이트와 렌더를 계속 실행시켜줘야한다.
- App 클래스에서 View 인스턴스에 접근하려면 MainFrame을 거쳐야하는데 View 인스턴스를 저장하고 사용하면 된다.
- 전역 클래스 포인터 활용
- extern 활용
- 다른 소스파일(외부)에서 선언된 전역변수를 사용하겠다.
- 전역 변수가 외부에 있다는 것을 표시할 뿐, 전역 변수를 선언하지 않는다.
//App 클래스 파일에 전역 변수로 선언
CMy10D3DMFCView *g_pView;
//메인프레임(사용하는 곳)에서 extern 키워드로 g_pView를 지정
extern CMy10D3DMFCView *g_pView;
//OnCreateClient에서 생성된 View의 포인터를 저장
g_pView = (CMy10D3DMFCView *)m_wndSplitter.GetPane(0, 0);
MFC에 Direct3D 적용
Direct3D 라이브러리 등록
- 프로젝트 속성 > 링커 > 입력 > 추가 종속성
- ..\DirectX9\Lib\x86\dxguid.lib;..\DirectX9\Lib\x86\d3d9.lib;..\DirectX9\Lib\x86\d3dx9.lib;
View 클래스에 Direct3D 관련 클래스 변수 선언
//..View.h
CD3DFramework m_pD3dFramework;
CAppMesh m_cApp;
View 클래스에서 한번만 호출되는 곳에서 Direct3D 초기화 실행
- OnInitialUpdate() 함수에서 초기화
- 소멸 함수에서 해제
OnInitialUpdate() 함수 추가 방법
- VS 클래스뷰 창에서 View 클래스 클릭 > 속성 창에서 재정의 아이콘 클릭 > OnInitialUpdate 찾아 > Add OnInitialUpdate 클릭 > View 클래스 .cpp 파일에 콜백 함수 추가
- OnInitialUpdate()
//초기화
void CMy10D3DMFCView::OnInitialUpdate() {
CView::OnInitialUpdate();
// TODO: 여기에 특수화된 코드를 추가 및/또는 기본 클래스를 호출합니다.
m_pD3dFramework.Init(m_hWnd/*View의 Wnd핸들*/, &m_cApp, 1000 / 60);
}
//소멸
CMy10D3DMFCView::~CMy10D3DMFCView()
{
m_pD3dFramework.Release();
}
View에서 배경을 지우지 않게 설정
- View 클래스뷰 창에서 View 클래스 클릭 > 속성 창에서 메시지 아이콘 클릭 > WM_ERASEBKGND 찾아 > Add OnEraseBkgnd 클릭 > View 클래스 .cpp 파일에 메시지 콜백 함수 추가
- BOOL CMenuFormView::OnEraseBkgnd(CDC* pDC)
- 추가된 OnEraseBkgnd 함수 내부를 다음과 같이 변경하여 배경을 지우지 않도록 설정
BOOL CMenuFormView::OnEraseBkgnd(CDC* pDC) {
return false;
//return CFormView::OnEraseBkgnd(pDC);
}
App::OnIdle() 재정의로 무한루프 설정
- App::OnIdle() 함수
- Windows 메시지가 처리되지 않고 있는 경우 프레임워크는 CWinApp의 멤버 함수 OnIdle을 호출한다.
- 리턴값이 true이면 무한반복 실행
- 리턴값이 false이면 중지
App::OnIdle() 재정의하기
- 클래스뷰 창에서 App 클래스 클릭 > 속성 창에서 재정의 아이콘 클릭 > OnIdle 선택 > Add OnIdle 선택
- App 코드에 다음 콜백함수 추가
BOOL CMy10D3DMFCApp::OnIdle(LONG lCount) {
// TODO: 여기에 특수화된 코드를 추가 및/또는 기본 클래스를 호출합니다.
return CWinApp::OnIdle(lCount);
}
MFC 파일별 역할
- App
- Direct3D View의 전역 변수 선언
- OnIdle 함수에서 무한 루프 제공
- View
- Direct3D View
- Form View
- MainFrame
- 분할 창(SplitterWnd) 생성
- 분할된 각 창에 Direct3D 뷰, 폼뷰 생성 및 추가
- Direct3D View의 전역 변수에 생성된 Direct3D View의 포인터 할당
Direct3D 플리핑 시점 변경하여 FPS 높이기
- 기본 플리핑 FPS는 30~50 내외
- 플리핑 시점을 즉시로 변경하여 최대 FPS가 되도록 설정
D3DPRESENT_PARAMETERS sD3DParam;//Direct3D 디바이스 생성에 필요한 파라미터
sD3DParam.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;//즉시 플리핑
윈도우 타이틀 바꾸기
- 메인프레임 클래스의 PreCreateWindow(CREATESTRUCT &cs) 함수의 파라미터에 설정
- 기본 설정으로 실행하면 타이틀 바에 “제목 없음 - [프로젝트명]” 형식으로 나오는데 “제목 없음”을 제거할 수 있음
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) {
if (!CFrameWnd::PreCreateWindow(cs))
return FALSE;
cs.style &= ~FWS_ADDTOTITLE;
SetTitle(TEXT("MFC Multi View"));
return TRUE;
}
참고
- 스크롤바
- https://yyman.tistory.com/
- 폰트 대화상자
- DirectX 3D 게임 프로그래밍