심화반 예제 (아날로그 시계)

이번에 만들어 본 예제는 아날로그 시계입니다.

C++ 언어와 MFC를 이용해 만들어 보았습니다.

아날로그 시계를 프로그래밍 하기 위해 진행한 설계 과정입니다.

1. 시계 외곽 원 그리기 

이 부분은 매우 쉽습니다. CDC 클래스의 Ellipse() 함수를 호출하면 원을 그려줍니다.

다만, Ellipse 함수는 사각형의 왼쪽 위점과 오른쪽 아래점을 지정하면 그 사각형에 내접하는 원을 그려주므로 뷰 클래스의 크기를 얻어와 그 사각형에 내접하는 원을 그리면 끝입니다.

윈도우 기본 좌표계는 왼쪽 위 좌표가 0, 0 이며 X좌표는 오른쪽으로 가면 커지고, Y 좌표는 특이하게 아래쪽으로 가면 커집니다. 눈에 보이는 화면 좌표에서 음수를 없애서 편리하게 좌표를 계산하도록 만들어 줍니다.



만약, 뷰의 크기가 변함에 따라 시계의 크기를 변경하고 싶다면 가로, 세로 축 중 더 작은 값을 알아내 작은 값을 원의 지름으로 적용하면 화면에 맞는 시계의 외곽 원을 그려낼 수 있습니다.


2. 시간, 분 마크 적용하기

시계의 시간, 분 선을 그리기 위해서는 삼각함수가 필요합니다.
원의 외곽선 좌표를 찾아내 시계의 중심점으로 부터 선을 그어야 하기 때문입니다.



시계의 중심점 좌표와 (anchorPt) 시계의 반지름 (radius), 각도를 알고 있다면 다음과 같이 외곽선의 좌표를 구할 수 있습니다.

// 각도를 라디안으로 변환 (각도 곱하기 파이 나누기 180)
double radian = deg * PI / 180.0;
// sin (라디안) 곱하기 반지름으로 X좌표 구하기
double dx = sin(radian)*radius;
// cos (라디안) 곱하기 반지름으로 Y좌표 구하기
double dy = cos(radian)*radius;
// 원의 외곽선 X 좌표
anchorPt.x + dx
// 원의 외곽선 Y 좌표
anchorPt.y + dy

시간, 분 마크는 각도는 360도를 6으로 나누어 총 60번 짧은 직선을 그려주면 됩니다. 매 5회 마다는 굵은 선으로 표시해 1시 부터 12시까지 따로 표시하면 되겠죠.


3. 현재 시간 알아내기

현재 시간을 알아내는 방법은 다양합니다. 로컬(현지)시간을 알아내는 방법은 아래와 같이 사용하면 됩니다. 물론 이 방법 말고도 다양한 방법이 있습니다.

// CTime 클래스를 이용해 현지 시간 가져오기
CTime time = CTime::GetCurrentTime();

이 프로젝트에서는 세계시간을 구현하기 위해 세계 협정시를 이용해 시차를 더하고 빼는 방법을 적용하였으며, UTC(세계 협정시, 영국 그리니치 천문대를 기준) 시간을 알아내기 위해 아래 방법을 사용하였습니다.

// UTC 0 기준의 시간얻기
SYSTEMTIME utctime;
::GetSystemTime(&utctime);

얻어진 UTC 0 기준의 시간은 다양한 세계시계를 구현하기 위해 다음과 같은 메뉴를 구성해 UTC -12 부터 UTC + 14 까지 세계 시간을 볼 수 있도록 만들어 보았습니다.



4. 초침, 분침, 시침 그리기

이제 시계의 기본 구성 (원, 테두리)과 시간도 알아내었다면 시침, 초침, 분침을 그리기 위해 위에서 설명한 삼각함수가 또 필요합니다.

시, 분, 초침은 현재 시간에 맞는 각 침의 각도를 계산한 후, 삼각함수를 이용해 시계의 중심점으로 부터 각도의 변화에 따른 원 외곽선 좌표를 알아내어 직선을 그리는 방식입니다.

먼저 초침을 그리기 위해 위에서 구한 현재시간은 초에 6을 곱합니다. (360도를 60초로 구현해야 하므로)

// 초침 각도 구하기
deg = sec * 6.0;

즉 30초 초침을 그리기 위해 30 x 6 = 180도 에 초침 선을 그리기 위함입니다.

분침 그리기 또한, 360도를 60분으로 시계에서 표현하므로 초침과 그리는 원리는 같지만, 초침이 변화함에 따라 분침의 각도가 미세하게 변하므로 이를 위해 아래와 같이 코드로 구현하였습니다.

// 분침 각도 구하기 (초침의 위치를 분침 각도에 적용)
deg = (min + sec / 60.0) * 6.0;

시침은 360도의 각도가 12시간으로 시계에서 표현되므로 시간 x 30도로 각도를 구해야 합니다. 다만 분침의 값이 시침의 각도에 반영되어야 하므로 아래와 같이 코드로 구현합니다.

// 시침 각도 구하기
deg = (((int)hour % (int)12.0) + (min / 60.0)) * 30.0;

hour % 12는 아날로그 시계를 12시간을 기준으로 (24시간 표현 X) 표시되기 때문에 현재의 시간을 12로 나눈 나머지를 구하는 부분이며, min 나누기 60은 분의 현재값을 시침의 각도에 반영하기 위함입니다.

시침, 분침, 초침을 현재시간을 이용해 각도를 찾아낸 뒤 시계의 중심점에서 선을 그려준 모습입니다.




5. 마무리

삼각함수를 이용해 간단히 아날로그 시계를 구현해 보았습니다.

세계 시간이 모두 구현되어 있으며, 첨부된 소스코드에 보면 시간 서버에 접속해 인터넷 시간을 가져오는 방법도 Socket을 이용해 구현해 보았습니다.

네트워크 타임 서버에 접속해 시간을 읽어오기 위해 NTP(Network Time Protol) 를 이용해 구현하였습니다.

이 과정을 간략히 과정을 요약해 본다면 아래와 같습니다.

1. UDP (비연결) Socket 생성 (winsock Ver. 2.2 이용)

2. Time Server로 NTP 패킷 보내기 (123번 포트 이용)

3. Time Server에서 NTP 패킷 수신 (시간 정보 포함)

4. Socket 종료

5. 현재 컴퓨터의 시간을 바꾸기위한 SetLocalTime() 함수 호출, 단 프로세스의 권한 상승 필요

HANDLE h_token;
TOKEN_PRIVILEGES tp;
// 현재 프로세스의 권한과 관련된 정보를 변경하기 위해 토큰정보를 연다.
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &h_token))
{
// 권한과 관련된 정보 접근에 실패함..
return ;
}
// 현재 프로세스가 SE_TIME_ZONE_NAME 권한을 사용할수 있도록 설정한다.
LookupPrivilegeValue(NULL, SE_TIME_ZONE_NAME, &tp.Privileges[0].Luid);
tp.PrivilegeCount = 1;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

// 지정한 값으로 권한을 조정한다.
AdjustTokenPrivileges(h_token, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, (PDWORD)0);
if (GetLastError() != ERROR_SUCCESS)
{
// 권한 조정에 실패한 경우...
return;
}

6. 실행파일 또한 UAC 권한 상승 (관리자 권한, 프로젝트 속성-링커-매니페스트파일)



컴퓨터 시간은 컴퓨터의 시간을 바꾸면 조작이 가능하나, 인터넷 시간을 이용해 출퇴근, 근태 시스템에 적용한다면 시간 조작의 불가능한 시계 프로그램이 구현 가능합니다.


  • 개발환경 : Microsoft Visual Studio 2015, Windows 10 Pro 64bit
  • 개발언어 : C++, MFC
  • 핵심 적용 기술, 환경 
  1. Device Context, GDI, Double Buffering (깜빡임방지)
  2. SDI (Single Document Interface)
  3. C++ 11 std::thread 
  4. 삼각함수 (시계 구현)
  5. Network Time Protocol
  • 첨부파일 
  1. 설치파일 (Installshield로 제작된 설치파일, 클릭) 
  2. 소스코드 (C++ 소스코드, rar 압축파일형태, 클릭)
  3. 구글아이디로 로그인하면 다운로드 가능합니다.

댓글

이 블로그의 인기 게시물

Qt Designer 설치하기

C++ 예제 (소켓 서버, 이미지, 파일전송)