시리즈:중급 프로그래밍 시리즈/C++/MFC/따라하기: 두 판 사이의 차이

잔글 (Ruin 사용자가 쉽게 배우는 프로그래밍 입문/C++/따라하기 문서를 중급 프로그래밍 시리즈/C++/따라하기 문서로 옮겼습니다: 등급을 나누어서 옮깁니다..)
(차이 없음)

2015년 12월 4일 (금) 00:26 판

틀:토막글 이 문서에서는 예제를 따라하며 C++을 배운다. 묻지도 따지지도 않고 따라하기 때문에 어느새 C++에 익숙해진 자신을 발견할 수 있을 것이다.

시작하기전에

프로그램 다운로드 및 설치

일단, 이 문서를 보는 위키러들이 가장 눈에 쉽게 보이는 결과를 만드는 것이 주 목적이기 때문에, 윈도우즈에서 작업한다.

프로그램은 Studio Community 2013을 사용한다. 링크된 페이지에서 프로그램을 다운받고 다음, 다음을 연타하며 설치하면 된다. 설치가 끝난 후, Microsoft 계정으로 인증하자.

프로그램 하나 만드는 김에 도스창에 나오는 프로그램 말고 그래픽 창이 뜨는 프로그램도 만들 것이다. 여기에서는 그래픽 프로그래밍을 위해 DirectX를 사용한다. 다운로드 링크는 [1] 역시나 다음 버튼을 계속 누르고 기본 설정으로 설치하면 된다.

설명은 영문판을 기준으로 한다. 한글판을 안깔아서 작성자가 귀찮다고 한다

슬라이딩 퍼즐

많은 사람들이 15-퍼즐을 알 것이다. 이 프로그램은 이를 기초로 하여 발전시켜 나갈 것이다.

기초: 15-퍼즐을 만들어보자

프로젝트 생성

Visual Studio 실행.png

Visual Studio를 실행한 후 화면이다. 화면의 배경이 어두운건 커스텀 설정을 바꿨기 때문이니 신경쓰지 않아도 된다. 이 화면상에서, 메뉴의 File-New-Project를 순서대로 누르거나 Ctrl+Shift+N을 누르자.

Visual Studio 새 프로젝트.png

왼쪽의 Templates에서 Visual C++을 선택한다.

Visual Studio Cpp 선택.png

Visual C++의 하위 항목 중 MFC를 선택한다.

Visual Studio MFC 선택.png

오른쪽에서 MFC Application을 선택하고, Name에 Sliding Puzzle라고 적은 뒤 Ok를 누른다.

MFC 마법사1.png

Next를 누른다.

MFC 마법사2.png

Application type을 Single Document, Project style을 MFC standard로 하고 Finish를 누른다.

예제 프로젝트 생성 완료.png

프로젝트가 생성되면 Solution Explorer에 뭔가 많이 떠있을 것이다.

프로젝트 설정

소스코드를 직접 타이핑하자

위키미디어 공용의 이미지비트맵 이미지로 변환해서 사용한다. 방금 전에 생성한 프로젝트의 경로(따로 설정을 하지 않았다면 내문서/Visual Studio 2013/Project/Sliding Puzzle/Sliding Puzzle)에 비트맵 이미지를 넣는다.

리소스를 프로젝트에 포함시키기

쉽게 배우는 CPP 리소스창 추가 전.png

오른쪽에 있는 이 창이 보일것이다. 밑의 탭에는 Solution Explorer, Team Explorer, Class Explorer 세 개가 있다. 이제 여기에 하나를 더 추가할 것이다.

쉽게 배우는 CPP 리소스창 위치.png

위 처럼 클릭하거나 Ctrl+Shift+E를 사용하여 Resource Explorer를 보이게 한다. 그럼 오른쪽에 Resouce Explorer 탭이 자동으로 선택되어 있을 것이다.

쉽게 배우는 CPP 리소스 추가 메뉴.png

위 그림처럼 클릭한다.

쉽게 배우는 CPP 비트맵 선택.png

리소스 추가 창이 뜨는데, 여기에서 Bitmap을 선택하고 Import 버튼을 누른다.

쉽게 배우는 CPP 임포트 비트맵.png

앞에서 미리 넣어 두었던 비트맵 파일을 선택한다.

쉽게 배우는 CPP 리소스 추가 완료.png

프로그램에서 사용할 비트맵 이미지를 정상적으로 추가하였다.

이제, 약간의 클릭과 타이핑만 하시면 됩니다
편집: stdafx.h

쉽게 배우는 CPP GDIplus사용준비.png

Solution Explorer에서 Header Files 항목을 열고, stdafx.h를 더블클릭한다.

쉽게 배우는 CPP Stdafx h.png

다음과 같은 파일이 열릴 것이다. 이 파일의 끝에 다음을 추가한다.

#include <gdiplus.h>
#pragma comment(lib, "gdiplus")
편집: Sliding Puzzle.cpp

쉽게 배우는 CPP Gdiplus 사용 준비2.png

오른쪽 탭에서 Solution Explorer을 누르고, Source Files 항목을 열어서 Sliding Puzzle.cpp를 더블클릭한다.

쉽게 배우는 CPP Sliding Puzzle cpp.png

Sliding Puzzle.cpp가 열리게 된다. 소스코드를 보여주는 부분의 바로 위를 보자.

쉽게 배우는 CPP 함수찾기1.png

콤보박스(버튼을 누르면 리스트가 나와서 선택할 수 있는 박스)를 위 그림처럼 선택한다.

쉽게 배우는 CPP 함수찾기2.png

그 옆의 콤보 박스도 위 그림처럼 선택한다.

쉽게 배우는 CPP 찾은함수.png

소스코드의 해당 함수가 위치한 곳으로 이동하게 된다.

BOOL CSlidingPuzzleApp::InitInstance() 위의 CSlidingPuzzleApp theApp; 부분 밑에 다음을 추가한다.

ULONG_PTR gdiplusToken;

BOOL CSlidingPuzzleApp::InitInstance() 아래 부분에서 CWinApp::InitInstance();와 // Initialize OLE libraries 사이에 다음을 추가한다.

Gdiplus::GdiplusStartupInput gdiplusStartupInput;
if (Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr) != Gdiplus::Ok){
	AfxMessageBox(_T("ERROR: Failed to initialize GDI+ library"));
	return FALSE;
}

int CSlidingPuzzleApp::ExitInstance() 아래 부분에서 AfxOleTerm(FALSE);와 return CWinApp::ExitInstance(); 사이에 다음을 추가한다.

Gdiplus::GdiplusShutdown(gdiplusToken);
편집: Sliding PuzzleView.h

쉽게 배우는 CPP Sliding PuzzleView h.png

Solution Explorer에서 Header Files 항목을 열고 Sliding PuzleView.h를 더블클릭해서 열어서

//Operations
public:

다음 줄에 타이핑한다.

int state[4][4];
Gdiplus::Rect board{ 100, 50, 206, 206 };
Gdiplus::Image *pimage;
Gdiplus::Bitmap *pmembitmap;
bool isClicked = false;
bool isMoving = false;
CPoint loc0{ 3, 3 };
void movePiece(const CPoint &loc);
편집: Sliding PuzzleView.cpp

쉽게 배우는 CPP 클래스 뷰 선택.png

오른쪽 탭에서 Class View를 누르면 나오는 화면에서 Sliding Puzzle 항목을 열고, CSlidingPuzzleView 항목을 선택한다.

쉽게 배우는 CPP 속성에서 WM PAINT 메시지 처리 함수 추가.png

Properties 창에 있는 아이콘들 중 오른쪽에서 3번째 아이콘을 클릭한 후 스크롤을 내려 WM_PAINT를 찾는다. WM_PAINT의 오른쪽 칸의 화살표가 있는 흰색 박스(빈칸 옆의 흰색 박스)를 누른다. 누르면 나오는 <Add> OnPaint를 누른다. 같은 방법으로, WM_KEYDOWN, WM_LBUTTONDOWN, WM_LBUTTONUP의 처리함수(OnKeyDown, OnLButtonDown, OnLButtonUp)을 추가한다.

쉽게 배우는 CPP Sliding PuzzleView cpp.png

Solution Explorer에서 Source Files 항목을 열고 Sliding PuzzleView.cpp를 더블클릭해서 연다.

  1. include "Sliding PuzzleView.h" 다음 줄에 추가한다.
#include <chrono>

using namespace std::chrono;

Sliding Puzzle.cpp를 편집할 때처럼 텍스트를 직접 편집하는 부분 위의 콤보박스(이하 콤보박스)의 2, 3번째 항목을 CSlidingPuzzleView, CSlidingPuzzleView()으로 선택한 후 CSlidingPuzzleView::CSlidingPuzzleView() 안에 다음과 같이 입력한다.

Gdiplus::Bitmap *pbitmap = Gdiplus::Bitmap::FromResource(
	AfxGetInstanceHandle(), (WCHAR*)MAKEINTRESOURCE(IDB_BITMAP1));
pimage = dynamic_cast<Gdiplus::Image*>(pbitmap);

for (int i = 0; i < 4; ++i){
	for (int j = 0; j < 4; ++j){
		state[i][j] = i * 4 + j + 1;
	}
}
state[3][3] = 0;

pmembitmap = ::new Gdiplus::Bitmap{ board.Width, board.Height };
Gdiplus::Graphics memgraphics(pmembitmap);
memgraphics.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality);

Gdiplus::SolidBrush BlackBrush{ Gdiplus::Color(32, 0, 0, 0) };
memgraphics.FillRectangle(&BlackBrush,
	Gdiplus::Rect{ 0, 0, board.Width, board.Height });

const Gdiplus::REAL tmpW = static_cast<Gdiplus::REAL>(pimage->GetWidth()),
	tmpH = static_cast<Gdiplus::REAL>(pimage->GetHeight());

for (int i = 0; i < 4; ++i){
	for (int j = 0; j < 4; ++j){
		if (i * j == 9) continue;
		memgraphics.DrawImage(pimage,
			Gdiplus::RectF(static_cast<Gdiplus::REAL>(52) * i,
			static_cast<Gdiplus::REAL>(52) * j,
			static_cast<Gdiplus::REAL>(50), static_cast<Gdiplus::REAL>(50)),
			tmpW / board.Width * 52 * (state[i][j] / 4),
			tmpH / board.Height * 52 * (state[i][j] % 4),
			tmpW / board.Width * 50, tmpH / board.Height * 50,
			Gdiplus::UnitPixel);
	}
}

콤보박스(이하 콤보박스)의 2, 3번째 항목을 CSlidingPuzzleView, OnPaint()으로 선택한 후 CPaintDC dc(this); 다음 줄에 입력한다.

Gdiplus::Graphics graphics(dc.GetSafeHdc());
graphics.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality);

graphics.DrawImage(pmembitmap, board.X, board.Y);

콤보박스(이하 콤보박스)의 2, 3번째 항목을 CSlidingPuzzleView, OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)으로 선택한 후 CView::OnKeyDown(nChar, nRepCnt, nFlags); 이전 줄에 입력한다.

switch (nChar)
{
case VK_LEFT:
	if (!isMoving && loc0.x < 3) movePiece(CPoint{ loc0.x + 1, loc0.y });
	break;
case VK_RIGHT:
	if (!isMoving && loc0.x > 0) movePiece(CPoint{ loc0.x - 1, loc0.y });
	break;
case VK_UP:
	if (!isMoving && loc0.y < 3) movePiece(CPoint{ loc0.x, loc0.y + 1 });
	break;
case VK_DOWN:
	if (!isMoving && loc0.y > 0) movePiece(CPoint{ loc0.x, loc0.y - 1 });
	break;
}

콤보박스(이하 콤보박스)의 2, 3번째 항목을 CSlidingPuzzleView, OnLButtonDown(UINT nFlags, CPoint point)으로 선택한 후 CView::OnLButtonDown(nFlags, point); 이전 줄에 입력한다.

isClicked = true;

콤보박스(이하 콤보박스)의 2, 3번째 항목을 CSlidingPuzzleView, OnLButtonUp(UINT nFlags, CPoint point)으로 선택한 후 CView::OnLButtonUp(nFlags, point); 이전 줄에 입력한다.

if (isClicked && !isMoving){
	auto locInclude = [](int value, int measure){
		if ((value - measure) / 52 >= 0 && (value - measure) / 52 <= 3)
			return (value - measure) / 52;
		else return -1;
	};
	auto abs = [](int value){
		if (value < 0) return -value;
		return value;
	};

	isClicked = false;

	CPoint loc{ locInclude(point.x, board.X), locInclude(point.y, board.Y) };
	if (loc.x >= 0 && loc.y >= 0 &&
		abs(loc.x - loc0.x) + abs(loc.y - loc0.y) == 1 && !isMoving)
		movePiece(loc);
}

파일의 맨 끝 줄에서 개행하고 다음과 같이 입력한다.

void CSlidingPuzzleView::movePiece(const CPoint& loc)
{
	isMoving = true;

	Gdiplus::Graphics memgraphics(pmembitmap);
	memgraphics.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality);

	Gdiplus::SolidBrush WhiteBrush{ Gdiplus::Color::White };
	Gdiplus::SolidBrush BlackBrush{ Gdiplus::Color(32, 0, 0, 0) };

	const Gdiplus::REAL tmpW = static_cast<Gdiplus::REAL>(pimage->GetWidth()),
		tmpH = static_cast<Gdiplus::REAL>(pimage->GetHeight()),
		tmp2_5 = static_cast<Gdiplus::REAL>(2.5);

	const int n = 13;
	int m = 4;
	Gdiplus::RectF moverect{ static_cast<Gdiplus::REAL>(52) * loc.x,
		static_cast<Gdiplus::REAL>(52) * loc.y,
		static_cast<Gdiplus::REAL>(50),
		static_cast<Gdiplus::REAL>(50) };
	while (m != 0){
		system_clock::time_point tp = system_clock::now();
		while (duration_cast<milliseconds>(system_clock::now() - tp)
			<= static_cast<milliseconds>(60));
		--m;

		memgraphics.FillRectangle(&WhiteBrush,
			Gdiplus::RectF{ moverect.X - (2 * (loc.x - loc0.x)) - tmp2_5,
			moverect.Y - (2 * (loc.y - loc0.y)) - tmp2_5,
			moverect.Width + (2 * (loc.x - loc0.x)) + tmp2_5,
			moverect.Height + (2 * (loc.y - loc0.y)) + tmp2_5 });
		memgraphics.FillRectangle(&BlackBrush,
			Gdiplus::RectF{ moverect.X - (2 * (loc.x - loc0.x)) - tmp2_5,
			moverect.Y - (2 * (loc.y - loc0.y)) - tmp2_5,
			moverect.Width + (2 * (loc.x - loc0.x)) + tmp2_5,
			moverect.Height + (2 * (loc.y - loc0.y)) + tmp2_5 });
		moverect.X -= (n * (loc.x - loc0.x));
		moverect.Y -= (n * (loc.y - loc0.y));

		memgraphics.DrawImage(pimage,
			moverect,
			tmpW / board.Width * 52 * (state[loc.x][loc.y] / 4),
			tmpH / board.Height * 52 * (state[loc.x][loc.y] % 4),
			tmpW / board.Width * 50, tmpH / board.Height * 50, Gdiplus::UnitPixel);

		RedrawWindow();
	}

	state[loc0.x][loc0.y] ^= state[loc.x][loc.y];
	state[loc.x][loc.y] ^= state[loc0.x][loc0.y];
	state[loc0.x][loc0.y] ^= state[loc.x][loc.y];
	loc0 = loc;

	isMoving = false;
}

대망의 프로젝트 빌드 및 실행

쉽게 배우는 CPP 빌드.png

메뉴에서 위와 같이 누르거나 Ctrl+Shift+B를 누른다. 그리고 기다린다.

쉽게 배우는 CPP 아웃풋.png

텍스트 편집부의 아래에 위치한 Output 창에 Builed: 1 succeeded가 뜨면 빌드가 성공한 것이다.

쉽게 배우는 CPP 실행.png

메뉴에서 위와 같이 누르거나 Ctrl+F5를 누른다.

쉽게 배우는 CPP 실행화면.png

잘 실행된다. 그림이 없는 회색의 큰 사각형 주위의 사각형을 클릭하거나 키보드의 방향키를 누르면 조각이 이동한다.

스톱워치 기능 넣기

분할 확장: [math]\displaystyle{ M \times N }[/math]으로!

이미지 변경: 입맛대로 이미지 바꿔서 하자

프로그램 분석: C++ 프로그램의 구조