코드/MFC

[MFC] 따라하기 04. 원이 그려지는 동작 만들기

야곰야곰+책벌레 2022. 3. 8. 11:28
728x90
반응형

1. 프로젝트 예제에 사용되는 내용

  • 원을 이루는 함수 구하기
  • ClientDC

2. CClientDC와 Ellipse()를 이용하여 원 그리기

Dialog Based 프로젝트를 만든 뒤, 버튼을 하나 추가하여 클릭으로 원을 그릴 수 있도록 해보자.

위와 같이 원을 그리는 것은 어렵지 않다. Button Click 이벤트에 다음과 같이 작성하면 간단하게 작성할 수 있다.

void CDrawCircleDlg::OnBnClickedDraw()
{
	CClientDC dc(this);
	dc.Ellipse(10, 10, 100, 100);
}

CCleintDC는 window에서 device context를 가져온다. Ellipse()는 원을 그리는 함수다.

OnInitDialog()에서는 CCleintDC을 이용해서 그림을 그릴 수 없다.
무효화된 뒤 다시 그리기 위해서는 WM_PAINT가 호출되어야 하는데, OnPaint()에 작성한 것이 아니기 때문에 1회 호출된 뒤, 무효화 영역에 갔다 오면 다시 그리지 않기 때문에 지워진다.
OnInitDialog()는 다이얼로그를 초기화하기 때문에 이벤트가 끝나면 다시 그리기를 하는데 CClientDC만으로는 다시 그릴 수 없기 때문이다.

3. 직선을 이용하여 원 그리기

원을 그리는 동작을 만들려면 원을 그리는 동작을 모두 쪼개야 한다. 원의 정의를 보면 한 점에서 동일 거리를 거리를 가지는 모든 점의 집합이다. 그렇기 때문에 다음과 같이 설명할 수 있다.

점 (x, y)의 위치는 반지름과 삼각함수를 이용하여 구할 수 있다.

θ의 크기를 0 ~ 2π 로 변경하면 원을 완성할 수 있다.

void CDrawCircleDlg::OnBnClickedDraw()
{
	CClientDC dc(this);
	
	double pi = 3.1415926535897932384626433832795; // PI
	double radius = 100; // 반지름
	int vertex = 365; // 꼭지점
	
	// 중심점은 반지름보다 (10, 10) 큰 위치
	double cen_x = radius + 10;
	double cen_y = radius + 10;

	// 0도 처리 : 0도 위치로 펜을 옮김
	double x = radius * cos(0.0) + cen_x;
	double y = radius * sin(0.0) + cen_y;
	dc.MoveTo(x, y);

	// 원 그리기
	for (int i = 0; i <= vertex; i++)
	{
		double radian = i * 2 * pi / vertex;
		double x = radius * cos(radian) + cen_x;
		double y = radius * sin(radian) + cen_y;
		dc.LineTo(x, y);
	}
}

왼쪽부터 vertex ( 4, 12, 356 )

꼭짓점의 개수를 많이 할수록 원에 가깝게 보이게 된다.

4. WM_TIMER 메시지를 이용하여 원을 그리는 모습 만들기

일정한 시간 간격으로 EVENT를 발생시키려면 WM_TIMER를 사용해야 한다. 클래스 뷰에서 다이얼로그를 선택 후 WM_TIMER 이벤트를 추가하자.

이벤트를 만들면 아래와 같은 함수가 생긴다.

BEGIN_MESSAGE_MAP(CDrawCircleDlg, CDialogEx)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDC_DRAW, &CDrawCircleDlg::OnBnClickedDraw)
	ON_WM_TIMER() // TIMER EVENT 추가됨
END_MESSAGE_MAP()

// EVENT가 발생하면 OnTimer함수가 호출됨.
void CDrawCircleDlg::OnTimer(UINT_PTR nIDEvent)
{
	// TODO: Add your message handler code here and/or call default

	CDialogEx::OnTimer(nIDEvent);
}

WM_TIMER는 SetTimer() 함수로 활성화시킬 수 있고, KillTimer() 함수를 사용해서 멈출 수 있다.

WM_TIMER를 Set 하게 되면 설정된 시간에 맞춰 OnTimer가 호출된다. 

매개 변수 UINT_PTR nIDEvent는 타이머 ID다.

SetTimer(0, 1000, NULL);   // 0번 타이머를 1000ms마다 OnTimer 함수를 호출한다.
SetTimer(1, 2000, OnTest); // 1벝 타이머를 2000ms마다 OnTest 함수를 호출한다.

KillTimer(0); // 0번 타이머 정지
KillTimer(1); // 1번 타이머 정지

위의 예를 쓰는 게 보편적이지만 아래와 같이 특별하게 사용할 수 있지만 해당 함수는 정해진 형식을 지켜야 한다. 여기서는 위의 예제를 사용하자.

위에서 사용했던 원 그리는 코드를 함수로 만들어 보자. 반지름, 꼭짓점을 매개변수로 만들자.

이때 Timer에 발생되는 카운트로 그림을 그려야 하므로 반복문을 제외하고 카운트 또한 매개변수로 받아오자.

void CDrawCircleDlg::drawCircle(int count, double radius, int vertex)
{
	CClientDC dc(this);
	const double pi = 3.1415926535897932384626433832795; // PI

	// 중심점은 반지름보다 (10, 10) 큰 위치
	double cen_x = radius + 10;
	double cen_y = radius + 10;
	double x, y, radian;

	// 시작 위치로 펜을 옮김
	radian = count * 2 * pi / vertex;
	x = radius * cos(radian) + cen_x;
	y = radius * sin(radian) + cen_y;
	dc.MoveTo(x, y);

	// 원 그리기
	radian = (count + 1) * 2 * pi / vertex;
	x = radius * cos(radian) + cen_x;
	y = radius * sin(radian) + cen_y;
	dc.LineTo(x, y);
}

여기서 중요한 점은 CClientDC를 함수 내에서 계속 선언하기 때문에 펜을 한상 시작 위치로 옮기는 작업이 필요하다. 펜을 옮기고 그리는 작업으로 바꾸면 위와 같은 코드를 만들 수 있다.

만들어진 함수를 OnTimer 함수 내에 적용해 보자.

void CDrawCircleDlg::OnTimer(UINT_PTR nIDEvent)
{
	double radius = 100; // 반지름
	int vertex = 365; // 꼭지점

	drawCircle(cntAngle++, radius, vertex);
	if (cntAngle > vertex)
		KillTimer(0);

	CDialogEx::OnTimer(nIDEvent);
}

cntAngle의 값은 항상 기억되어야 하므로 클래스의 멤버 변수로 선언했다. drawCircle() 함수를 호출하고 나면 1 증가하도록 했고 원을 그리고 나면 자동으로 타이머는 정지하도록 만들었다.

이제 시작 버튼의 내용을 수정하자.

void CDrawCircleDlg::OnBnClickedDraw()
{
	cntAngle = 0;
	SetTimer(0, 100, NULL);
}

cntAngle을 초기화시키고 타이머를 활성화시켜주기만 하면 원을 그리는 동작을 할 수 있다.

예제를 실행시키면 위와 같다.

따라하기 04. 원이 그려지는 동작 만들기.zip
0.13MB

728x90
반응형