5.타이머와 윈도우 컨트롤

본장에서는 타이머를 메시지를 이용하여 이벤트를 만드는 방법과 다양한 윈도우 형태를 만드는 방법을 설명합니다. 윈도우에서는 일정시간을 설정하고 그시간이 되면 타이머 메시지를 발생하여 이타이머를 이용하여 여러 멀티 테스킹 및 일정시간에 반복되는 프로그래밍 할수 있도록 되어 있습니다.

지금까지는 윈도우 시스템메뉴와 아이콘,최대크기,종료 버튼이 있는 기본적인 형태의 윈도우만 사용했습니다. 본장을 통해서 다양한 스타일의 윈도우 출력에 대해서 공부하게 될것입니다. 또한 원형 형태 윈도우 타이틀 바가 없이 단순하게 외곽선만 있는 윈도우에 대해서도 본장에서 설명합니다.

타이머 메시지란

타이머란 일정시간이 경과 되면 알려주는 시계의 기능을한다고 보시면 됩니다. 예를 들어 □1분이 지나면 나에게 알려다오□ 라고 했을 경우 타이머는 1분이 지난후에 □1분이 되었습니다□ 라고 알려줍니다. 윈도우에서 타이머는 타이머를 종료하기 이전까지 계속적으로 알려줍니다. □1분이 지나면 나에게 알려다오□라고 설정하면 설정한 후에 1분이 경과되면 타이머 메시지를 발생하고 또 다시 1분이 경과 되면 타이머 메시지를 발생하고... 즉 특정 시간을 타이머로 설정하였다면 설정한후부터 그시간 간격으로 타이머 메시지를 발생합니다. 타이머를 설정하는 함수는 SetTimer입니다.

UINT SetTimer(

HWND hWnd, // 윈도우 핸들

UINT nIDEvent, // 타이머 번호

UINT uElapse, // 설정된 시간 간격

TIMERPROC lpTimerFunc // 타이머 메시지가 발생되었을 때 실행되는 함수

);

hWnd란 윈도우 핸들이며 nIDEvent란 타이머 번호입니다. 타이머 번호란 시간을 설정하는 타이머의 번호입니다. 윈도우에서는 원하는대로 타이머를 사용할 수 있습니다. 예를 들어서 어떤 타이머는 1분마다 타이머 메시지를 발생시키고 어떤 타이머는 10분마다 타이머 메시지를 발생시킨다면 두 개의 타이머가 다르게 동작되어 야 합니다. 이때 1분마다 타이머 메시지를 발생시키는 것은 1번으로 10분마다 타이머 메시지를 발생하는 타이머를 2번으로 이렇게 번호를 설정하여 구동할 수가 있습니다. uElapse는 설정된 시간 간격입니다. 기본단위는 msec입니다. 1분마다라고 할경우에는 60000 이 설정될수 있을 것입니다. lpTimerFunc란 타이머 메시지가 발생 되었을 때 수행되는 함수 프로시저입니다.

예를 하나들어서 5초마다 타이머 메시지를 발생하는 타이머를 1번으로 하여 타이머를 가동하고자 한다면 다음과 같이 할수 있습니다.

SetTimer(hwnd,1,5000,NULL);

위와 같이 하였을 경우 5초마다 WM_TIMER이라는 메시지가 들어옵니다.

case  WM_TIMER:

//5초마다 수행해야할 내용기록

break;

만일 5초마다 타이머 메시지를 발생하는 타이머를 1번으로 1초마다 메시지를 발생하는 타이머를 2번으로 하였을 경우 다음과 같이 설정할수 있습니다.

SetTimer(hwnd,1,5000,NULL);//5초짜리 1번 타이머 설정

SetTimer(hwnd,2,1000,NULL);//1초짜리 2번 타이머 설정

위와 같이 하였을 경우 1번 타이머가 메시지를 보낼 때 와 2번 타이머가 메시지를 보낼 때 두 개 모두 똑같은 WM_TIMER메세지를 발생합니다. 따라서 어떤 타이머로부터 메시지가 발생되었는가를 확인할 필요성이 있습니다. wParam 에는 메지시와 함께 설정된 타이머 번호가 설정됩니다.  다음과 같은 방법으로 해당 타이머에 대응하는 내용을 분리하여 수행할수 있습니다.

case WM_TIMER:

if(wParam==1)

{

//1번 타이머로부터 메시지가 발생되었을 경우

}

else if(wParam==2)

{

//2번 타이머로부터 메시지가 발생되었을 경우

}

break;

타이머를 너무 믿지 마십시오!

예를 들어선 SetTimer(hwnd,1,200,NULL) 하면 0.2초마다 계속적으로 타이머가 발생될것입니다. 0.2초마다 해당되는 내용이 수행될 때 이것이 꼭 0.2초마다 한번씩 정확하게 수행된다는 보장이 없습니다. 윈도우에서는 SetTimer(hwnd,1,200,NULL) 에 의해서 정확하게 0.2초마다 메시지를 보냅니다. 그러나 메시지를 받는 어플리케이션 측에서 그메시지를 받지 못할 경우가 있습니다. 그렇게 되면 이메시지는 잃어 버리게 됩니다. 메시지큐에는 WM_TIMER이라는 메시지가 도착하였을 때 어플리케이션측에서 어떤 다른 작업을 하고 있기 때문에 WM_TIMER라는 메시지를 가져 오지 못하고 있고 그리고 0.2초라는 시간이 흐른다음 WM_TIMER이라는 메시지가 들어왔을경우에는 이전 WM_TIMER가 무시됩니다. 어플리케이션에 작업 로드가 많이걸리지 않을경우에는 이런 문제점이 없으나 작업 로드가 많이 걸릴 경우에는 이런 문제점이 발생할수 있습니다. 쉽게말해서 for(;;)루프나 while루프로 반복작업이 복잡하게 발생되는 상황에서는 WM_TIMER 메시지를 놓칠수도 있기 때문입니다. 윈도우에서 메시지처리 프로그램은 간결하게 만들어야 합니다. 얼마나 간결하게 만드는가에 프로그램에 기술적 차이가 있으며 이것은 바로 알고리듬의 구현문제가 되는 것입니다. 똑같은 프로그램을 어떤사람은 100줄에 어떤사람은 5줄에 구현할수 있다면 5줄에 구현하는 사람이 더욱 실력이 있는것입니다. 그렇기에 필자는 윈도우 프로그램에서 알고리듬이 중요하다고 강조를 많이하고 있습니다. 저만 그런 것이 아닙니다. Charles Petzold의 프로그래밍 Windows95 에서도 □돼지가 되지말라고□ 당부를 했습니다.

프로시저로 넘기기

한 개의 타이머가 아닌 여러개의 타이머를 사용했을 경우 위의 방법을 이용한다면 다음과 같은 switch문을 이용하게 될것입니다.

case WM_TIMER:

if(wParam==1)

{

//1번 타이머로부터 메시지가 발생되었을 경우

}

else if(wParam==2)

{

//2번 타이머로부터 메시지가 발생되었을 경우

}

else if(wParam==3)

{

//3번 타이머로부터 메시지가 발생되었을 경우

}

:

break;

타이머 메시지안에 switch문을 이용하여 다소 복잡하고 switch문이란 연산처리에서 수행속도가 늦게 처리되는 구문이므로 작은 시간 간격으로 위와 같이 하였을 경우 타이머를 잃어 버릴수 있으며 또한 프로그램 모양새도 다소 복잡한 것이 보기 좋지 않을 것입니다. 이런 방법을 해소하기 위해서 SetTiemr의 4번째 인자에 함수를 등록하여 WM_TIMER가 발생되었을 때 설정된 프로시저 함수를 수행할수 있도록 할수 있습니다.

다음은 프로시저를 사용한 예입니다.

SetTimer(hwnd,1,200,TimeProc);

위와 같이 한다면 0.2초후에는 TimerProc의 함수가 수행됩니다. 이함수는 다음과 같은 형태로 만들어야 합니다.

VOID CALLBACK TimeProc(HWND hwnd,UINT iMsg,UINT wParam,DWORD lParam)

{

//0.2초 후에 발생되는 내용을 이곳에 기록

}

즉 형은 VOID CALLBACK이며 인자은 윈도우 핸들과 메시지 그리고 wParam과 lParam으로 됩니다. 자세히 보면 지금까지 사용한 WndProc 과 같은 형태입니다.

이렇게 프로시저를 사용하면 잇점이 많습니다. 위에 언급하였듯이 윈도우 프로그램은 각 메시지마다 프로그램 내용이 간결해야 한다고 설명했습니다. 그러나 실지로 어플리케이션 프로그램을 작성할 경우 간결하게 작성할수 없는 것이 대부분입니다. 필자의 경우 영상처리 프로그램을 제작할 경우 for루프가 3번정도 겹쳐서 처리되는 경우도 허다합니다. 이런 내용들이 많은데 어떻게 간결하게 처리할까요?

바로 콜백 함수를 이용하는 것입니다. 뒷부분에서 콜백을 이용하는 많은 부분들이 예제로 다루어 질것이고 본장에서는 타이머에 대해서만 설명을 합니다.

0.2초라면 매우 빠른 처리입니다. 이처리를 WM_TIMER메시지 안에 있으면 잃어 버릴수 있기 때문에 WM_TIMER라는 메시지가 오면 바로 TimeProc에 모든 작업을 맞기는 것입니다. 그리고 바로 다른 메시지를 받기위해 기다리는 것입니다.

이렇게 함으로써 매우 빠르게 타이머 이벤트를 처리할수 있습니다. WndProc 프로시저가 동시적으로 여러 메시지가 발생될때도 정확하게 수행하듯이 프로시저함수는 동시적으로 수행되는 내용을 따로다로 처리할수 있다는 장점이 있습니다. Visual C++에서 SDK sample를 보시게 되면 많은 내용들이 콜백 함수로 분리되어 있습니다.(이렇게 분리되어 있기 때문에 소스 분석이 어렵다는 느낌도 있습니다)

이이유는 모듈 수행이 간결하게 되어서 메시지처리 가 원할하게 이루어질수 있도록 하기 위해서입니다.

타이머 메시지 예제 ExTimer

Extimer는 두 개의 타이머를 가동합니다. 1번 타이머는 0.5초마다 2번 타이머는 2초마다 가동되도록 하였습니다. WM_PAINT메시지에서는 램덤 수를 이용하여 임의적인 색을 만들고 이색으로 박스를 그리고 또한 원을 그리도록 하였습니다.

case WM_CREATE :

//타이머 설정

SetTimer(hwnd,1,500,NULL);

SetTimer(hwnd,2,2000,NULL);

return 0 ;

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

//램덤 함수를 이용하여 r,g,b값을 설정

r=rand()%256;

g=rand()%256;

b=rand()%256;

//rgb값에 의해서 브러쉬와 펜을 만든다.

hBrush=CreateSolidBrush(RGB(r,g,b));

hPen=CreatePen(PS_SOLID,1,RGB(r,g,b));

//브러쉬와 펜을 설정

oldBrush=SelectObject(hdc,hBrush);

oldPen=SelectObject(hdc,hPen);

//박스와 우너을 그린다.

Rectangle(hdc,10,10,300,300);

Ellipse(hdc,320,320,420,420);

//이전 펜과 브러쉬설정

SelectObject(hdc,oldBrush);

SelectObject(hdc,oldPen);

//펜과 브러쉬 삭제

DeleteObject(hBrush);

DeleteObject(hPen);

EndPaint (hwnd, &ps) ;

return 0 ;

1번 타이머가 발생되면 화면에서 박스 영역만 무효화시켜서 다시 출력하도록 합니다. 이렇게 되면 박스의 색만 다시 바뀌게 될것입니다.

2번 타이머가 발생되면 화면에서 원 영역만 무효화 시킵니다. 다음과 같은 방법으로 코딩을 합니다.

case WM_TIMER:

switch(wParam)

{

case 1:

//박스 영역 rgn을 만든다.

rgn=CreateRectRgn(10,10,300,300);

//박스 영역 무효화

InvalidateRgn(hwnd,rgn,FALSE);

//rgn 삭제

DeleteObject(rgn);

break;

case 2:

//원 영역 rgn을 만든다.

rgn=CreateRectRgn(320,320,420,420);

//원영역 무효화

InvalidateRgn(hwnd,rgn,FALSE);

//rgn삭제

DeleteObject(rgn);

break;

}

break;

위와 같이 하게 되면 1번 타이머가 발생될때마다 박스의 색은 임의적인 색으로 바뀌게 되고 2번 타이머가 발생될때는 원의 색이 바뀌게 될것입니다.

본예는 타이머를 사용하는 방법을 이해하는 예제입니다. 소스는 다음과 같습니다.

(프로그램 소스)

//타이머 예제

//ExTimer.c

#include <windows.h>

#include <stdlib.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

static char szAppName[] = "ExTimer" ;

HWND        hwnd ;

MSG         msg ;

WNDCLASSEX  wndclass ;

wndclass.cbSize        = sizeof (wndclass) ;

wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc   = WndProc ;

wndclass.cbClsExtra    = 0 ;

wndclass.cbWndExtra    = 0 ;

wndclass.hInstance     = hInstance ;

wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName  = NULL ;

wndclass.lpszClassName = szAppName ;

wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

RegisterClassEx (&wndclass) ;

hwnd = CreateWindow (szAppName,

"타이머 예제:ExTimer",

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

NULL,

hInstance,

NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

{

HDC         hdc ;

PAINTSTRUCT ps ;

HBRUSH hBrush,oldBrush;

HPEN   hPen,oldPen;

BYTE r,g,b;

HRGN rgn;

switch (iMsg)

{

case WM_CREATE :

//타이머 설정

SetTimer(hwnd,1,500,NULL);

SetTimer(hwnd,2,2000,NULL);

return 0 ;

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

//램덤 함수를 이용하여 r,g,b값을 설정

r=rand()%256;

g=rand()%256;

b=rand()%256;

//rgb값에 의해서 브러쉬와 펜을 만든다.

hBrush=CreateSolidBrush(RGB(r,g,b));

hPen=CreatePen(PS_SOLID,1,RGB(r,g,b));

//브러쉬와 펜을 설정

oldBrush=SelectObject(hdc,hBrush);

oldPen=SelectObject(hdc,hPen);

//박스와 우너을 그린다.

Rectangle(hdc,10,10,300,300);

Ellipse(hdc,320,320,420,420);

//이전 펜과 브러쉬설정

SelectObject(hdc,oldBrush);

SelectObject(hdc,oldPen);

//펜과 브러쉬 삭제

DeleteObject(hBrush);

DeleteObject(hPen);

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_TIMER:

switch(wParam)

{

case 1:

//박스 영역 rgn을 만든다.

rgn=CreateRectRgn(10,10,300,300);

//박스 영역 무효화

InvalidateRgn(hwnd,rgn,FALSE);

//rgn 삭제

DeleteObject(rgn);

break;

case 2:

//원 영역 rgn을 만든다.

rgn=CreateRectRgn(320,320,420,420);

//원영역 무효화

InvalidateRgn(hwnd,rgn,FALSE);

//rgn삭제

DeleteObject(rgn);

break;

}

break;

case WM_DESTROY :

KillTimer(hwnd,1);

KillTimer(hwnd,2);

PostQuitMessage (0) ;

return 0 ;

}

return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

}

(프로그램 소스끝)

그림 1은 ExTimer출력 결과입니다.

(그림 1) ExTimer 출력 결과

프로시저를 이용한 타이머 메시지 예제 ExTimer2

ExTimer2는 화면 (0,0)좌표에 현재 시간을 출력하면서 2초마다 사각형을 임의적인 색으로 바꾸어 출력하는 예제입니다. 현재 시간을 출력하는 부분은 프로시저 함수를 이용하였습니다. 먼저 다음과 같이 프로시저 함수를 정의하고

VOID CALLBACK TimeProc(HWND hwnd,UINT iMsg,UINT wParam,DWORD                    lParam);

SetTimer함수를 가동하였습니다.

//0.1초마다 TimerProc가 가동되도록 타이머 설정

SetTimer(hwnd,CLOCKTIMER,100,(TIMERPROC)TimeProc);

현재 시간을 출력하는 TimerProc는 다음과 같이 제작합니다.

//현재 시간을 화면에 출력하는 프로시저

VOID CALLBACK TimeProc(HWND hwnd,UINT iMsg,UINT wParam,DWORD lParam)

{

time_t curtime;

struct tm cur;

char curdate[80];

HDC         hdc ;

time(&curtime);//시스템 시간을 얻는다.

cur=*localtime(&curtime);//시스템 시간을 로컬 타임존으로 바꾼다.	wsprintf(curdate,"%02d-%02d-%02d:%02d:%02d:%02d",cur.tm_year,cur.tm_mon+1,	cur.tm_mday,cur.tm_hour,cur.tm_min,cur.tm_sec);

hdc=GetDC(hwnd);

TextOut(hdc,0,0,curdate,strlen(curdate));

ReleaseDC(hwnd,hdc);

}

time_t는 타이머 형입니다. time함수에 이타이머 형의 포인터를 넘겨주면 시스템 시간을 얻을수 있습니다. 이타이머 형은 long형입니다. 이형으로부터 연월일 시분의 형태를 tm구조체로 전환시켜야 합니다. 이때 사용하는 함수가 localtime입니다.

ExTimer2의 프로그램 소스는 다음과 같습니다.

(프로그램 소스)

//타이머 예제

//ExTimer2.c

#include <windows.h>

#include <stdlib.h>

#include <time.h>

#define CLOCKTIMER 1

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

VOID CALLBACK TimeProc(HWND hwnd,UINT iMsg,UINT wParam,DWORD lParam);

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

static char szAppName[] = "ExTimer2" ;

HWND        hwnd ;

MSG         msg ;

WNDCLASSEX  wndclass ;

wndclass.cbSize        = sizeof (wndclass) ;

wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc   = WndProc ;

wndclass.cbClsExtra    = 0 ;

wndclass.cbWndExtra    = 0 ;

wndclass.hInstance     = hInstance ;

wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName  = NULL ;

wndclass.lpszClassName = szAppName ;

wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

RegisterClassEx (&wndclass) ;

hwnd = CreateWindow (szAppName,

"타이머 예제:ExTimer2",

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

NULL,

hInstance,

NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

{

HDC         hdc ;

PAINTSTRUCT ps ;

HBRUSH hBrush,oldBrush;

HPEN   hPen,oldPen;

BYTE r,g,b;

HRGN rgn;

switch (iMsg)

{

case WM_CREATE :

//0.1초마다 TimerProc가 가동되도록 타이머 설정

SetTimer(hwnd,CLOCKTIMER,100,(TIMERPROC)TimeProc);

//WM_TIMER메시지에 의한 내용이 수행되도록 설정

SetTimer(hwnd,2,2000,NULL);

return 0 ;

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

r=rand()%256;

g=rand()%256;

b=rand()%256;

hBrush=CreateSolidBrush(RGB(r,g,b));

hPen=CreatePen(PS_SOLID,1,RGB(r,g,b));

oldBrush=SelectObject(hdc,hBrush);

oldPen=SelectObject(hdc,hPen);

Rectangle(hdc,20,20,300,300);

SelectObject(hdc,oldBrush);

SelectObject(hdc,oldPen);

DeleteObject(hBrush);

DeleteObject(hPen);

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_TIMER:

rgn=CreateRectRgn(20,20,300,300);

InvalidateRgn(hwnd,rgn,FALSE);

DeleteObject(rgn);

break;

case WM_DESTROY :

KillTimer(hwnd,CLOCKTIMER);

KillTimer(hwnd,2);

PostQuitMessage (0) ;

return 0 ;

}

return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

}

//현재 시간을 화면에 출력하는 프로시저

VOID CALLBACK TimeProc(HWND hwnd,UINT iMsg,UINT wParam,DWORD lParam)

{

time_t curtime;

struct tm cur;

char curdate[80];

HDC         hdc ;

time(&curtime);//시스템 시간을 얻는다.

cur=*localtime(&curtime);//시스템 시간을 로컬 타임존으로 바꾼다.

wsprintf(curdate,"%02d-%02d-%02d:%02d:%02d:%02d",

cur.tm_year,cur.tm_mon+1,	cur.tm_mday,cur.tm_hour,cur.tm_min,cur.tm_sec);

hdc=GetDC(hwnd);

TextOut(hdc,0,0,curdate,strlen(curdate));

ReleaseDC(hwnd,hdc);

}

(프로그램 소스끝)

그림 2는 ExTimer2의 출력 결과입니다.

(그림 2) ExTimer2 출력 결과

다양한 윈도우 스타일

지금까지 사용한 윈도우는 스타일는 WS_OVERLAPPEDWINDOW입니다. WinMain에서 CreateWindow 함수를 보시면 스타일을 알수가 있을것입니다. WS_OVERLAPPEDWINDOW는 실제로 다음과 같은 형의 조합니다.

WS_OVERLAPPED    |    WS_CAPTION     |   WS_SYSMENU

WS_THICKFRAME    |   WS_MINIMIZEBOX  |  WS_MAXIMIZEBOX

위의 의미는 타이틀바가 있는 윈도우에 시스템 메뉴가 있으며 두꺼운 외곽선을 가지고 있고 축소버튼과 전체 화면 버튼을 가지고 있는 윈도우 형태입니다. WS_OVERLAPPED는 타이틀바만 있는 가장 기본적인 윈도우 형태입니다. WS_OVERLAPPEDWINDOW를 사용하지 않고 위의 6개를 OR연산을 하여 윈도우 스타일을 설정하여도 같은 형태의 윈도우가 나타납니다. WS_CAPTION은 설정하지 않아도 같은 형태의 윈도우가 만들어 집니다. 이유는 WS_OVERLAPPED라는 형이 타이틀 바를 가지고 있는 기본형으로 설정되어 있으며 WS_SYSMENU등이 설정하기 위해서는 타이틀바가 필수적으로 설정되기 때문입니다. WS_CAPTION은 타이틀 바가 없는 윈도우에서 타이틀 바를 넣기 위해서 사용하는 윈도우 스타일입니다. 표1은 윈도우 스타일 리스트입니다. 표1 외에도 여러 다양한 윈도우 스타일이 있습니다. 이스타일에 대해서는 다음장의 차일드 윈도우 만들기에서 자세하게 설명할것입니다. 예를 들어서 축소버튼만 활성화 하고 시스템 메뉴가 있으며 확대버튼이 없게 하기 위해서는 다음과 같이 할수 있습니다.

hwnd = CreateWindow (szAppName,

"윈도우스타일예제:ExWinStyle",

WS_OVERLAPPED| WS_SYSMENU | WS_MINIMIZEBOX,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

NULL,

hInstance,

NULL) ;

위의 CreateWindow의 해석은 기본윈도우에 시스템 메뉴를 가지고 있으며 축소버튼을 나타낸 형태입니다. 위의 내용대로이면 확대버튼이 나타나지 않아야 하겠지만 그렇게 되지는 않습니다. WS_MINIMIZEBOX나 WS_MAXIMIZEBOX 단 한 개를 설정하여도 확대 축소버튼이 동시에 나타납니다. 다만 설정하지 않은 버튼은 비활성화 되어 있습니다.

윈도우중에 타이틀 바가 없는 즉 외곽선만 있는 윈도우는 어떻게 설정할까요? 보통 WS_POPUP를 이용합니다.

WS_VISIBLE| WS_POPUP

위와 같이 하면 외곽이 없고 타이틀바가 없는 그냥 박스 형태의 윈도우가 나타납니다. 이때는 CW_USEDEFAULT를 사용할수 없으며 위치를 직접 설정해 주어야

(표1)윈도우 스타일 리스트

스타일	설정값(16진수)	내용
WS_BORDER	0x00800000L	윈도우의 외곽 경계선이 있는 형태
WS_CAPTION	0x00C00000L	윈도우 타이틀바를 가지고 있는 형태 WS_BORDER를 포함하며 WS_DLGFRAME 스타일과 함께 사용할수 없다.
WS_CHILD	0x40000000L	자식윈도우 스타일 자신이 혼자서 독자적으로 뜨는 형태( WS_POPUP)가 아니라 부모윈도우 안에 포함되는 형태. WS_POPUP와 함께 사용할수 없다.
WS_DISABLED	0x08000000L	비활성화 된 윈도우 형태 버튼같은 윈도우에서 회색형태가 나타나는 것은 이스타일이 설정되어 있기 때문이다.
WS_DLGFRAME	0x00400000L	외곽두께가 두꺼운 대화상자 형태의 윈도우 스타일
WS_HSCROLL	0x00100000L	수평 스크롤 바가 붙은 윈도우
WS_VSCROLL	0x00200000L	수직 스크롤 바가 붙은 윈도우
WS_SYSMENU	0x00080000L	시스템 메뉴가 붙은 윈도우
WS_THICKFRAME	0x00040000L	두꺼운 외각경계건을 가지고 있는 윈도우 스타일
WS_MINIMIZEBOX	0x00020000L	축소 버튼이 설정되어 있는 윈도우 스타일
WS_MAXIMIZEBOX	0x00010000L	확대 버튼이 설정되어 있느 윈도우 스타일
WS_VISIBLE	0x10000000L	화면에 보이는 윈도우
WS_POPUP	0x80000000L	자신이 독자적으로 화면에 나타나는 형태,타이틀 바를 가지고 있지 않다.
WS_OVERLAPPED	0x00000000L	타이틀 바를 가지고 있는 기본윈도우
WS_OVERLAPPEDWINDOW		표준 윈도우 스타일

합니다. 그렇지 않으면 화면에 아무것도 나오지 않게 될것입니다. 만일 그냥 외곽선을 보이고자 한다면 위의 스타일에 WS_BODER를 두꺼운 경계선을 설정하고자 한다면 WS_THICKFRAME를 추가하면 됩니다.

WS_VISIBLE| WS_POPUP | WS_BORDER //경계선만 있는 형태

WS_VISIBLE| WS_POPUP | WS_THICKFRAME //두꺼운 경계선 형태

다양한 윈도우 스타일 예제 ExWinStyle

ExWinStyle는 처음에는 WS_OVERLAPPEDWINDOW형태로 설정되었다가 3초마다 한번씩 윈도우 스타일을 바꾸는 예제입니다. 윈도우 스타일을 바꾸기 위해서는 윈도우에 설정된 스타일을 바꾸어야 합니다. 윈도우에 설정된 스타일을 바꿀때는 GetWindowLong를 사용합니다. 또한 윈도우에 현재 어떤 스타일이 있는 가를 알고자 할경우에는 SetWindowLong를 사용합니다. 이두함수의 형태는 다음과 같습니다.

LONG SetWindowLong(

HWND hWnd, // 윈도우 핸들

int nIndex, // 설정할 값의 색인 번호

LONG dwNewLong // 설정될 새로운 값

);

LONG GetWindowLong(

HWND hWnd, // 윈도우 핸들

int nIndex // 얻고자 하는 값의 색인 번호

);

실제 GetWindowLong과 SetWindowLong는 윈도우 스타일 뿐만 아니라 CreateWindow함수와 함께 설정된 인자들의 값등 윈도우에 설정된 여러 정보를 얻고 설정할수 있습니다. 이 인자들중에서 특정 인자를 설정하고자 할 때 nIndex에 값을 대입하여 줍니다. nIndex는 다음과 같이 정의되어 있습니다.

GWL_STYLE : 윈도우 스타일

GWL_WNDPROC : 윈도우와 연결된  메시지프로시저 함수

GWL_HINSTANCE : 인스턴트 핸들

GWL_HWNDPAREN :부모 윈도우 핸들

GWL_ID : 자식윈도우 일 경우 설정된 ID

만일 윈도우 스타일을 얻고자 한다면 다음과 같이 할수 있습니다.

DWORD style

style=GetWindowLong(hwnd,GWL_STYLE);

같은 방법으로 새로운 스타일을 윈도우에 설정하고자 한다면 다음과 같이 할수 있습니다.

style=WS_OVERLAPPED | WS_CAPTION | WS_THICKFRAME

SetWindowLong(hwnd,GWL_STYLE,style);

위와 같이하면 윈도우에 스타일은 바뀌나 형태가 바뀌지 않습니다. 형태를 바뀌게 하기 위해서는 윈도우 자체를 다시 그려주어야 합니다. 윈도우를 그리는 것과 윈도우안에 클라이언트영역을 그리는것과는 다릅니다. 윈도우 안에 클라이언트영역을 그리고자 할경우에는 InvalidateRgn을 사용할수 있으나 윈도우를 그리고자 할경우에는 먼저 윈도우를 감추고 난 뒤 스타일을 바꾸고 그리고 윈도우를 다시 보여주어야 합니다. 윈도우를 감추거나 보여줄때는 ShowWindow라는 함수를 사용하면 됩니다.

ShowWindow(hwnd,SW_HIDE);//윈도우를 감추고

SetWindowLong(hwnd,GWL_STYLE,style);

ShowWindow(hwnd,SW_SHOW);//윈도우를 다시 보여준다.

(프로그램 소스)

//윈도우 스타일 예제

//ExWinStyle.c

#include <windows.h>

#include <stdlib.h>

#define CLOCKTIMER 1

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

VOID CALLBACK TimeProc(HWND hwnd,UINT iMsg,UINT wParam,DWORD lParam);

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

static char szAppName[] = "ExWinStyle" ;

HWND        hwnd ;

MSG         msg ;

WNDCLASSEX  wndclass ;

wndclass.cbSize        = sizeof (wndclass) ;

wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc   = WndProc ;

wndclass.cbClsExtra    = 0 ;

wndclass.cbWndExtra    = 0 ;

wndclass.hInstance     = hInstance ;

wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName  = NULL ;

wndclass.lpszClassName = szAppName ;

wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

RegisterClassEx (&wndclass) ;

hwnd = CreateWindow (szAppName,

"윈도우스타일예제:ExWinStyle",

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

NULL,

hInstance,

NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

{

HDC         hdc ;

PAINTSTRUCT ps ;

HBRUSH hBrush,oldBrush;

HPEN   hPen,oldPen;

BYTE r,g,b;

HRGN rgn;

switch (iMsg)

{

case WM_CREATE :

SetTimer(hwnd,CLOCKTIMER,3000,(TIMERPROC)TimeProc);

SetTimer(hwnd,2,2000,NULL);

return 0 ;

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

r=rand()%256;

g=rand()%256;

b=rand()%256;

hBrush=CreateSolidBrush(RGB(r,g,b));

hPen=CreatePen(PS_SOLID,1,RGB(r,g,b));

oldBrush=SelectObject(hdc,hBrush);

oldPen=SelectObject(hdc,hPen);

Rectangle(hdc,20,20,300,300);

SelectObject(hdc,oldBrush);

SelectObject(hdc,oldPen);

DeleteObject(hBrush);

DeleteObject(hPen);

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_TIMER:

rgn=CreateRectRgn(20,20,300,300);

InvalidateRgn(hwnd,rgn,FALSE);

DeleteObject(rgn);

break;

case WM_DESTROY :

KillTimer(hwnd,CLOCKTIMER);

KillTimer(hwnd,2);

PostQuitMessage (0) ;

return 0 ;

}

return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

}

int pos=0;

VOID CALLBACK TimeProc(HWND hwnd,UINT iMsg,UINT wParam,DWORD lParam)

{

DWORD style;

switch(pos)

{

case 0:

style=WS_OVERLAPPED | WS_CAPTION |                                           WS_THICKFRAME |WS_SYSMENU;

break;

case 1:

style=WS_OVERLAPPED | WS_SYSMENU                                               |WS_MAXIMIZEBOX;

break;

case 2:

style= WS_OVERLAPPED | WS_SYSMENU                                         |WS_MAXIMIZEBOX | WS_MINIMIZEBOX;

break;

case 3:

style=WS_VISIBLE | WS_BORDER | WS_POPUP;

break;

}

//pos를 증가시키가다 4가되면 다시 0으로 돌아온다.

//결국 pos는 0,1,2,3을 반복하게 된다.

pos++;

if(pos==4)

pos=0;

ShowWindow(hwnd,SW_HIDE);//윈도우를 감추고

SetWindowLong(hwnd,GWL_STYLE,style);

ShowWindow(hwnd,SW_SHOW);//윈도우를 다시 보여준다.

}

(프로그램 소스끝)

그림 3은 ExWinStyle출력 결과입니다.

(그림 3) ExWinStyle 출력 결과

윈도우 크기 변경하기

지금까지 제작한 프로그램은 CW_USEDEFAULT를 사용하여 시스템에서 임의의 크기로 설정하는 윈도우를 만들어 사용하였습니다. 실제 어플리케이션 프로그램을 제작한다면 때로는 윈도우 크기를 일정하게 만들 필요가 있으며 또한 사용중에 윈도우의 크기를 변환해야할 필요성이 있을경우가 있습니다. 윈도우 크기를 변경할 때 사용하는 함수가 MoveWindow 함수입니다.

BOOL MoveWindow(

HWND hWnd, // 윈도우 핸들

int X, // 윈도우 X좌표

int Y, // 윈도우 Y좌표

int nWidth, // 가로크기

int nHeight, // 세로 크기

BOOL bRepaint // 화면을 재출력할 때 사용하는 플러그

);

bRepaint는 윈도우가 바뀌었을 때  화면을 재출력할것인가를 결정하는 플러그입니다. FALSE이면 화면을 재출력하지 않고 TRUE이면 화면을 재출력합니다. MoveWindow는 주로 WM_POPUP형태에서 사용되는 함수입니다. WM_POPUP형태란 자신의 윈도우가 독자적으로 윈도우 시스템 위에서 움직일수 있는 형태의 윈도우를 이야기 합니다. 반대로 WM_CHILD형태는 자신이 독자적으로 움직이지 않고 자신의 부모윈도우의 특정 위치에서 움직이는 형태를 이야기 합니다.  WM_CHILD형태의 윈도우에서는 주로 SetWindowPos함수를 사용합니다. 이함수는 현재의 윈도우가 부모윈도우를 기준으로 어느 부분에 위치할것인가를 결정해주는 함수입니다. 물론 둘다 WM_POPUP 나 WM_CHILD형태 가능합니다. 다만 어떤 함수가 어떤의미에서 더 편리한가를 말씀드리는 것입니다.

MoveWindow의 좌표는 스크린을 기준으로 하는 디바이스 좌표입니다. 예를 들어서 (0,0)이라고 하였을 경우에는 스크린의 좌측 상단을 의미하는 것입니다. MoveWindow 함수를 실행시키면 이동되는 윈도우에서는 WM_MOVE와 WM_SIZE가 발생됩니다. WM_MOVE와 함께 들어오는 인자중에서 LOWORD(lParam)은 x좌표를 HIWORD(lParam) 은 y좌표 값이 들어 옵니다.

만일 사용자가 특정 위치로 윈도우를 이동하였을 경우 이이동 위치가 어플리케이션 프로그램에서는 적장하지 않을 경우 WM_MOVE메세지에서 현재의 위치를 받고 이것을 다시 다른 위치로 이동시킬수도 있습니다.

case WM_MOVE:

xPos=LOWORD(lParam);

yPos=HIWORD(lParam);

if(xPos와 yPos가 적절한 위치가 아니면 )

{

MoveWindow함수를 이용하여 다른 위치로 이동;

}

MoveWindow함수는 WM_MOVE와 함께 WM_SIZE또한 메시지를 발생됩니다.

WM_SIZE의 LOWORD(lParam)은 가로 크기와 HIWORD(lParam)은 세로크기가 리턴됩니다. 어플리케이션 프로그램에서 윈도우의 크기가 사용자에의해서 너무 커지거나 작아졌을 때 프로그램이 부적절 하게 구동될수도 있는 경우도 있습니다. 예를 들어서 그림을 출력하는 프로그램에서 그림의 크기가 (100,100)인데 사용자가 크기를 조절하여 (10,10)정도의 크기로 윈도우를 축소하거나 또는 (200,200)정도로 크게 만들 경우 보기에 적당하지 않을수 도 있습니다. 이럴 경우 WM_SIZE를 이용하여 새로운 크기로 만들 수 있습니다.

case WM_SIZE:

xSize=LOWORD(lParam);

ySize=HIWORD(lParam);

if(xSize와 ySize의 크기가 적당하지 않으면)

{

MoveWindow함수를 이용하여 새로운 크기로 전환한다.

}

(잔소리)

윈도우에서 hwnd인자가 들어있는 함수들은 메시지와 연결되는 것이 대부분이라고 생각하시면 됩니다. MoveWindow에 hwnd인자가 들어갑니다. 이것이 WM_MOVE와 WM_SIZE라는 메시지를 발생하는 듯이  윈도우 핸들을 이용한 특정함수는 대부분이 꼭 이 함수에 연결되는 메시지를 가지고 있다는 것입니다. 여기에서 기억해야 할 것은 검증을 할수 있다는 것입니다. 즉 사용자에 의한 어떤 입력에의해서 hwnd를 이용한 함수가 발생되었다면 가능하다면 그 함수와 연계된 메시지에서 처리가 제대로 되었는가를 확인하는 것이 좋습니다. 다음과 같은 형식이 될것입니다.

윈도우함수(hwnd,인자들)//특정 함수 값 실행

case 윈도우함수와 연계된 메시지:

수행된 윈도우 함수를 검증한다.

break;

위와 같은 방식은 시스템이 매우 안정적으로 돌아갑니다. 즉 한번 실행하고 그 실행을 검증하기 때문에 오류를 방지할수 있기 때문입니다. 이제부터 함수를 배우면 이함수와 연계된 메시지가 어떤 것들인가도 함께 기억하는 습관을 들였으면 합니다.

(잔소리 끝)

윈도우 크기 변경하기

스크롤 윈도우

원형윈도우

윈도우 크기 변경하기 예제 ExWinSize

ExWinSize는 윈도우 크기를 변경하는 예제입니다. 이 예제는 ExWinStyle에서 TimeProc 수행될 때 램덤 함수를 이용하여 임의의 x 와 y좌표를 설정하고 이좌표로 윈도우를 이동시키는 예제입니다. ExWinSize는 3초간격으로 윈도우 스타일이 바뀌어 지면서 모니터에 이곳 저곳으로 이동하면서 나타나는 형태입니다. 이렇게 떠 다니는 윈도우는 사실 많은 곳에서 사용되지 않으나 윈도우를 이동하는 방법을 설명하기 위한 재미있는 프로그램이 될것입니다.

(프로그램 소스)

//윈도우 이동형태 예제

//ExWinSize.c

#include <windows.h>

#include <stdlib.h>

#define CLOCKTIMER 1

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

VOID CALLBACK TimeProc(HWND hwnd,UINT iMsg,UINT wParam,DWORD lParam);

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

static char szAppName[] = "ExTimer2" ;

HWND        hwnd ;

MSG         msg ;

WNDCLASSEX  wndclass ;

wndclass.cbSize        = sizeof (wndclass) ;

wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc   = WndProc ;

wndclass.cbClsExtra    = 0 ;

wndclass.cbWndExtra    = 0 ;

wndclass.hInstance     = hInstance ;

wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName  = NULL ;

wndclass.lpszClassName = szAppName ;

wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

RegisterClassEx (&wndclass) ;

hwnd = CreateWindow (szAppName,

"윈도우크기 변경예제:ExWinSize",

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

NULL,

hInstance,

NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

{

HDC         hdc ;

PAINTSTRUCT ps ;

HBRUSH hBrush,oldBrush;

HPEN   hPen,oldPen;

BYTE r,g,b;

HRGN rgn;

switch (iMsg)

{

case WM_CREATE :

SetTimer(hwnd,CLOCKTIMER,3000,(TIMERPROC)TimeProc);

SetTimer(hwnd,2,2000,NULL);

return 0 ;

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

r=rand()%256;

g=rand()%256;

b=rand()%256;

hBrush=CreateSolidBrush(RGB(r,g,b));

hPen=CreatePen(PS_SOLID,1,RGB(r,g,b));

oldBrush=SelectObject(hdc,hBrush);

oldPen=SelectObject(hdc,hPen);

Rectangle(hdc,20,20,300,300);

SelectObject(hdc,oldBrush);

SelectObject(hdc,oldPen);

DeleteObject(hBrush);

DeleteObject(hPen);

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_TIMER:

rgn=CreateRectRgn(20,20,300,300);

InvalidateRgn(hwnd,rgn,FALSE);

DeleteObject(rgn);

break;

case WM_DESTROY :

KillTimer(hwnd,CLOCKTIMER);

KillTimer(hwnd,2);

PostQuitMessage (0) ;

return 0 ;

}

return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

}

int pos=0;

VOID CALLBACK TimeProc(HWND hwnd,UINT iMsg,UINT wParam,DWORD lParam)

{

DWORD style;

int x,y;

switch(pos)

{

case 0:

style=WS_OVERLAPPED | WS_CAPTION |                                           WS_THICKFRAME |WS_SYSMENU;

break;

case 1:

style=WS_OVERLAPPED | WS_SYSMENU                                           |WS_MAXIMIZEBOX;

break;

case 2:

style= WS_OVERLAPPED | WS_SYSMENU                                          |WS_MAXIMIZEBOX | WS_MINIMIZEBOX;

break;

case 3:

style=WS_VISIBLE | WS_BORDER | WS_POPUP;

break;

}

pos++;

if(pos==4)

pos=0;

//임의의 x와 y좌표를 만든다.

x=rand()%900;

y=rand()%500;

ShowWindow(hwnd,SW_HIDE);

//윈도우를 이동시킨다.

MoveWindow(hwnd,x,y,300,300,FALSE);

SetWindowLong(hwnd,GWL_STYLE,style);

ShowWindow(hwnd,SW_SHOW);

}

(프로그램 소스)

(그림 4) ExWinSize 예제 출력 결과

스크롤 윈도우 컨트롤

현재 화면에 나타나는 정보가 윈도우크기보다 클 경우 일반적인 프로그램은 윈도우 우측이나 상단부에 스크롤 바를 두어서 스크롤 바가 이동될때마다 출력되는 부분이 다르게 나타나게 설정됩니다. 예를 든다면 그림크기가 500x500인데 화면의 크기는 현재 200x200이라고 가정했을 때 나머지 부분은 감추어져 있으며 우측이나 하단부에 스크롤 바가 붙어 있어 그림의 감추어진 부분을 볼수 있게 해줍니다.

윈도우에 스크롤 바를 설정하는 것은 CreateWindow에서 간단하게 WM_VSCROLL 이나 WM_HSCROLL를 설정하여 주면됩니다.

WS_OVERLAPPEDWINDOW | WS_VSCROLL : 수직 스크롤 바

WS_OVERLAPPEDWINDOW | WS_HSCROLL : 수평 스크롤 바

WS_OVERLAPPEDWINDOW | WM_VSCROLL | WS_HSCROLL :  수직 수평 스크롤바

윈도우를 만들 때 스크롤 바가 설정된 형태로 나오게 되면 필수적으로 스크롤 바의 구간을 설정해 주어야 합니다. 스크롤 바에 구간을 설정해 주어야 한다는 의미는 스크롤바의 버튼이 한포인트 이동할때마다 스크롤바 전체에서 어느 위치에 설정되는가를 결정해 주기 때문입니다. 예를 들어 수직스크롤바가 크기가 100일 경우 전체구간을 10으로 설정하여 주었다면 한포인트 이동할때마다 10씩 스크롤 버튼이 상하로 움직이게 될것입니다. 이렇게 스크롤바의 구간을 설정해주는 함수가 SetScrollRange입니다.

BOOL SetScrollRange(

HWND hWnd, // 윈도우 핸들

int nBar, // 수직인가 수평이가 하는 플러그

int nMinPos, // 최소값

int nMaxPos, // 최대값

BOOL bRedraw // 화면을 다시 그릴것인가를 설정하는 플러그

);

예를 들어서 최소값이 0 최대값이 100인 구간의 수직 스크롤 바를 만들고자 한다면 위함수를 이용하여 다음과 같이 할수 있습니다.

SetScrollRange(hwnd,SB_VERT,0,100,FALSE);

SB_VERT 는 수직 스크롤 바에 해당하는 플러그이며 SB_HORZ 수평 스크롤 바에 해당하는 플러그입니다.  SetScrollRange함수를 실행하면 현재 윈도우에 설정된 스크롤 바의 크기를 계산하여 스크롤 버튼이 한포인트 이동할때마다 버튼의 위치가 어디에 설정될것인가를 자동적으로 계산해 줍니다.

구간을 설정한후에 우리가 임의로 스크롤 버튼의 위치를 이동시키고 싶을 경우에는 SetScrollPos함수를 사용합니다.

int SetScrollPos(

HWND hWnd, //스크롤 바 윈도우

int nBar, // 수직인가 수평인가를 결정하는 플러그

int nPos, // 스크롤 버튼의 위치

BOOL bRedraw // 화면 재출력 플러그

);

만약 스크롤 바에서 현재 위치를 10의 위치로 이동할 경우 SetScrollPos를 사용할 경우 다음과 같이 할수 있습니다.

SetScrollPos(hwnd,SB_VERT,10,TRUE);

스크롤 버튼의 위치가 이동되었다 해서 클라이언트 영역의 위치가 바뀌는 것은 절대 아닙니다. 스크롤 바의 위치를 변경시켰다면 그만큼 화면영역도 이동하여 다시 출력해주어야 합니다. 그렇지 않을 경우 스크롤 바는 스크롤 바대로 움직이고 이에 해당하는 화면 출력은 스크롤 바와 연결되지 않을 테니까요. 예를 들어 좌측 마우스 버튼을 클릭하였을 경우 스크롤 버튼의 위치를 10으로 이동하였다면 이에 해당한 화면 출력이 갱신되어야 합니다.

case WM_LBUTTONDOWN:

SetScrollPos(hwnd,SB_VERT,10,TRUE);

InvalidateRgn(hwnd,NULL,TRUE);//화면출력을 다시한다.

break;

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

//10위치만큼 이동한 내용을 다시 보여준다.

EndPaint (hwnd, &ps) ;

return 0 ;

프로그램에서 사용자가 임으로 스크롤 버튼을 클릭하여 드래깅하거나 또는 스크롤 바의 특정 부분을 클릭하여 페이지 이동을 할경우가 있습니다. 이럴 경우 이에 해당하는 WM_VSCROLL이나 WM_HSCROLL이 발생됩니다. WM_VSCROLL은 수직 스크롤바에서 마우스 버튼을 이용하여 클릭하였을경우이고 WM_HSCROLL은 수평 스크롤 바에서 이벤트를 발생하였을때입니다.

이메시지와 함께 LOWORD(wParam) 의 값은 스크롤 바에서 어떤 이벤트가 발생되었느가에 대한 값이 설정됩니다. 표 2는 이값에 대한 리스트입니다.

(표2) 스크롤 바에서 LOWORD(wParam)의 리스트

LOWORD(wParam)	내용
SB_BOTTOM	스크를 위치 최하단부 이동
SB_ENDSCROLL	스크롤 작업이 끝남을 알림
SB_LINEDOWN	한칸 아래로
SB_LINEUP	한칸 위로
SB_PAGEDOWN	한페이지 아래로
SB_PAGEUP	한페이지 위로
SB_THUMBPOSITION	스크롤 버튼을 눌러서 드래깅을 하엿을 때 드래깅이 종료 되었음
SB_THUMBTRACK	드래깅이 현재 동작중임
SB_TOP	스크롤 바의 최상단

WM_VSCROLL이나 WM_HSCROLL이 발생되었을 때 HIWORD(wParam)에는  스크롤 위치를  lParam에는 스크롤 바 윈도우 핸들이 넘겨집니다.

표2의 값과 함께 스크롤 이벤트가 발생되었을 때 스크롤 바의 위치와 함께 화면을 변경시켜주어야 합니다. 기본적인 방법을 예를 든다면 다음과 같을 것입니다.

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

//pos에 해당하는 만큼 화면을 이동하여 출력한다.

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_VSCROLL:

switch(LOWORD(wParam))

{

case SB_TOP://최상단

pos=0;

break;

case SB_BOTTOM://최하단

pos=99;

break;

case SB_LINEUP://한라인 위로

pos-=1;

if(pos<0)

pos=0;

break;

case SB_LINEDOWN://한라인 아래로

pos+=1;

if(pos==100)

pos=99;

break;

case SB_PAGEUP://페이지 업

pos-=10;

if(pos<0)

pos=0;

break;

case SB_PAGEDOWN://페이지 다운

pos+=10;

if(pos>=100)

pos=99;

break;

case SB_THUMBTRACK://현재 드래깅중

pos=HIWORD(wParam);

break;

}

//해당 포인터만큼 포스를 이동

SetScrollPos(hwnd,SB_VERT,pos,TRUE);

//화면을 다시 출력한다.

InvalidateRgn(hwnd,NULL,TRUE);

break;

위의 예는 전체 구간이 100이라고 가정을 하고 스크롤 바를 움직였을때의 내용을 설정한것입니다. 라인단위로 이동할 경우 pos의 값이 증감이 이루어 지는데 이 증감이 0-100 사이에 있도록 하며 페이지단위로 이동할경우에도 같은 방법으로 이루어지도록 하였습니다.  SB_THUMBTRACK 은 현재 스크롤 버튼을 클릭하여 이동할 때 발생되는 메시지이며 SB_THUMBPOSITION 드래깅이 종료 되었을 때 의 메시지입니다. 위의 예는 SB_THUMBTRACK을 사용하였습니다. 이것을 사용하면 드래깅하는 순간마다 화면 영역이 바뀌게 됩니다. 매우 크용량의 데이터를 출력할경우에는 SB_THUMBTRACK 보다 SB_THUMBPOSITION 이 좋습니다. 그이유는 SB_THUMBPOSITION 는 드래깅하는 순간에는 아무런 변화가 없다가 드래깅이 종료 된 순간에 변화를 가져오기 때문에 화면 변화가 한번에 간단히 이루어 질수 있기 때문입니다.

스크롤 윈도우 컨트롤 예제 ScrollWnd

ScrollWnd는 ScrollWnd.c 의 내용을 화면에 출력하는 예제입니다. 스크롤 바를 움직일 때 마다 ScrollWnd.c의 내용이 스크롤 되면서 화면에 나타납니다.

//스크롤 윈도우 컨트롤

//ScrollWnd.c

#include <windows.h>

#include <stdio.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

static char szAppName[] = "ScrollWnd" ;

HWND        hwnd ;

MSG         msg ;

WNDCLASSEX  wndclass ;

wndclass.cbSize        = sizeof (wndclass) ;

wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc   = WndProc ;

wndclass.cbClsExtra    = 0 ;

wndclass.cbWndExtra    = 0 ;

wndclass.hInstance     = hInstance ;

wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName  = NULL ;

wndclass.lpszClassName = szAppName ;

wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

RegisterClassEx (&wndclass) ;

hwnd = CreateWindow (szAppName,

"스크롤 윈도우:ScrollWnd",

WS_OVERLAPPEDWINDOW | WS_VSCROLL,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

CW_USEDEFAULT,

NULL,

NULL,

hInstance,

NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

char Buff[100][200];

int totalline;

int pos=0;

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

{

HDC         hdc ;

PAINTSTRUCT ps ;

FILE *out;

int i;

switch (iMsg)

{

case WM_CREATE :

//ScrollWnd.c를 열고

out=fopen("ScrollWnd.c","r");

if(out==NULL)

return 0;

//100라인만 Buff에 저장한다.

for(i=0;i<100;i++)

{

if(fgets(Buff[i],200,out)==NULL)

break;

Buff[i][strlen(Buff[i])-1]=0x00;

}

totalline=i;

fclose(out);

//윈도우 스크롤 구간및 위치 설정

SetScrollRange(hwnd,SB_VERT,0,totalline,FALSE);

SetScrollPos(hwnd,SB_VERT,pos,TRUE);

return 0 ;

case WM_SIZE:

SetScrollRange(hwnd,SB_VERT,0,totalline,FALSE);

SetScrollPos(hwnd,SB_VERT,pos,TRUE);

InvalidateRgn(hwnd,NULL,TRUE);

break;

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

//스크롤 pos가 시작점 totalline이 마지막 점으로 화면 출력

for(i=pos;i<totalline;i++)

TabbedTextOut(hdc,0,(i-pos)*20,Buff[i],strlen(Buff[i]),1,NULL,0);

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_VSCROLL:

switch(LOWORD(wParam))

{

case SB_TOP://최상단

pos=0;

break;

case SB_BOTTOM://최하단

pos=totalline-1;

break;

case SB_LINEUP://한라인 위로

pos-=1;

if(pos<0)

pos=0;

break;

case SB_LINEDOWN://한라인 아래로

pos+=1;

if(pos==totalline)

pos=totalline-1;

break;

case SB_PAGEUP://페이지 업

pos-=10;

if(pos<0)

pos=0;

break;

case SB_PAGEDOWN://페이지 다운

pos+=10;

if(pos>=totalline)

pos=totalline-1;

break;

case SB_THUMBTRACK://현재 드래깅중

pos=HIWORD(wParam);

break;

}

//해당 포인터만큼 포스를 이동

SetScrollPos(hwnd,SB_VERT,pos,TRUE);

UpdateWindow(hwnd);

//화면을 다시 출력한다.

InvalidateRgn(hwnd,NULL,TRUE);

break;

case WM_DESTROY :

PostQuitMessage (0) ;

return 0 ;

}

return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

}

(프로그램  소스끝)

WM_ PAINT에서 szBuff의 배열에 있는 데이터를 pos부터 마지막까지 윈도우에 출력하도록 하였습니다. 처음 프로그램이 시작되면 pos는 0이되고 스크롤 바에서 버튼이 이동되면 pos에 변화가 생깁니다. 이렇게 변화가 생기면 바로 화면영역을 재출력하여 출력내용을 변경시키는 것입니다.

그림 5는 ScrollWnd의 출력결과입니다.

(그림 5) ScrollWnd

원형윈도우

원형 윈도우란 그림 6과같이 타이틀 바도 없으며 단순히 윈도우가 동그란 형태로 출력하는 것을 말합니다.

시계를 화면에 출력하거나 또는 계임을 할 때 좀더 흥미있게 하기위해서 윈도우형태를 변경하여 사용할 때 또는 CD플레이어등을 만들 때 윈도우라는 느낌을 주지않고 신선한 느낌을 주기 위해서 많이 사용합니다.

(그림 6)ExClock 출력 결과

윈형윈도우를 만드는 방법의 의외로 간단합니다. 윈도우 스타일을 단순하게 타이틀바도 없고 외곽선이 없는 스타일로 설정합니다.

WS_POPUP | WS_VISIBLE

위와 같은 스타일을 설정할 경우 CreateWindow에서 CW_USEDEFAULT 사용하시면 화면에 윈도우가 출력되지 않습니다. 위와 같은 스타일을 만들고 난후에는 꼭 위치를 정해 주셔야 화면에 출력됩니다. 이렇게 하면 사각형의 아무것도 없는 박스만 나타나게 됩니다. 이 박스를 원으로 변경시킬 때 사용하는 함수가 SetWindowRgn입니다.

int SetWindowRgn(

HWND hWnd, // 윈도우 핸들

HRGN hRgn, //설정할 영역

BOOL bRedraw // 화면 재출력 플러그

);

SetWindowRgn함수의 두 번째 인자 hRgn의 영역을 원형으로 만들어야 합니다. 이때 HRGN을 원형으로 만드는함수가 CreateRoundRectRgn입니다.

CreateRoundRectRgn은 사실 원이 아니라 박스형태의 양쪽 모서리를 둥그렇게 만드는 형태입니다. GDI에서 RoundRect를 보셨다면 어떤 형태인 것을 아실수 있을것이니다. 이 Round박스를 원형에 가깝게 하기 위해서는 가로크기와 새로크기를 기준으로 모서리를 원으로 하면 됩니다. 화면에 100,100 크기의 원형에 가까운 윈도우를 만들고자 한다면 다음과 같이 할수 있습니다.

rgn=CreateRoundRectRgn(0,0,100,100);

SetWindowRgn(hwnd,rgn,FALSE);

원형윈도우 예제 ExClock

ExClock는 모니터 스크린 우측 상단부에 원형 시계를 출력하는 예제입니다. SetWindowRgn함수를 제외한 모든 부분은 지금까지 공부한 내용들입니다.

(프로그램 소스)

//원형 시계

//ExClock.c

#include <windows.h>

#include <stdlib.h>

#include <time.h>

#define CLOCKTIMER 1

#define CLOCKSIZE 100

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

VOID CALLBACK TimeProc(HWND hwnd,UINT iMsg,UINT wParam,DWORD lParam);

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

static char szAppName[] = "ExClock" ;

HWND        hwnd ;

MSG         msg ;

WNDCLASSEX  wndclass ;

int cx,x;

wndclass.cbSize        = sizeof (wndclass) ;

wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc   = WndProc ;

wndclass.cbClsExtra    = 0 ;

wndclass.cbWndExtra    = 0 ;

wndclass.hInstance     = hInstance ;

wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName  = NULL ;

wndclass.lpszClassName = szAppName ;

wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

RegisterClassEx (&wndclass) ;

//스크린의 가로 크기를 얻는다.

cx=GetSystemMetrics(SM_CXFULLSCREEN);

x=cx-CLOCKSIZE;

hwnd = CreateWindow (szAppName,

"원형시계 예제:ExClock",

WS_POPUP | WS_VISIBLE ,

x,

0,

CLOCKSIZE,

CLOCKSIZE,

NULL,

NULL,

hInstance,

NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

{

HDC         hdc ;

PAINTSTRUCT ps ;

HBRUSH hBrush,oldBrush;

HPEN   hPen,oldPen;

HRGN rgn;

switch (iMsg)

{

case WM_CREATE :

SetTimer(hwnd,CLOCKTIMER,100,(TIMERPROC)TimeProc);

rgn=CreateRoundRectRgn(0,0,CLOCKSIZE,CLOCKSIZE,CLOCKSIZE,CLOCKSIZE);

SetWindowRgn(hwnd,rgn,FALSE);

return 0 ;

case WM_PAINT :

hdc = BeginPaint (hwnd, &ps) ;

hBrush=CreateSolidBrush(RGB(0,0,200));

hPen=CreatePen(PS_SOLID,3,RGB(200,200,91));

oldBrush=SelectObject(hdc,hBrush);

oldPen=SelectObject(hdc,hPen);

RoundRect(hdc,0,0,CLOCKSIZE,CLOCKSIZE,CLOCKSIZE,CLOCKSIZE);

SelectObject(hdc,CreateSolidBrush(RGB(0,0,0)));

RoundRect(hdc,15,30,85,60,20,20);

SelectObject(hdc,oldBrush);

SelectObject(hdc,oldPen);

DeleteObject(hBrush);

DeleteObject(hPen);

EndPaint (hwnd, &ps) ;

return 0 ;

case WM_DESTROY :

KillTimer(hwnd,CLOCKTIMER);

PostQuitMessage (0) ;

return 0 ;

}

return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

}

VOID CALLBACK TimeProc(HWND hwnd,UINT iMsg,UINT wParam,DWORD lParam)

{

time_t curtime;

struct tm cur;

char curdate[80];

HDC         hdc ;

time(&curtime);

cur=*localtime(&curtime);

wsprintf(curdate,"%02d:%02d:%02d",cur.tm_hour,cur.tm_min,cur.tm_sec);

hdc=GetDC(hwnd);

SetBkColor(hdc,RGB(0,0,0));

SetTextColor(hdc,RGB(255,255,0));

TextOut(hdc,20,37,curdate,strlen(curdate));

ReleaseDC(hwnd,hdc);

}

(프로그램 소스끝)