본문 바로가기
C & C++/MFC Media

WM_PAINT 메세지에 대하여...

by izen8 2012. 10. 5.
반응형

 

생성된 윈도우가 다른 윈도우에 의하여 가려졌다가 다시 나타날 경우 가려졌던 부분이 지우개로 지운 것
처럼 지워지는 현상을 목격할 수 있습니다. 또는 윈도우의 크기를 리사이즈 시켰을 경우 윈도우 화면의
일부 또는 전체가 지워지는 현상을 목격할 수 있습니다. 이와 같이 생성된 윈도우의 가려졌던 부분을
무효화 영역(Invalid Region)이라 하고 우리는 무효화 영역이 생겼을 경우 이를 다시 복구시켜야 할
필요성을 인지할 수 있습니다.
 
무효화 영역(Invalid Region)의 복구 작업을 윈도우즈가 알아서 처리해 준다면 사용자 입장에서는 한결

 

프로그래밍에 대한 부담을 덜 느낄수 있겠지만 아쉽게도 윈도우즈는 무효화 영역(Invalid Region)의
복구에 대한 책임을 전적으로 사용자에게 맡기고 있습니다. 이는 무효화 영역이 생길 경우를 사용자가
미리 예측하여 이에 대한 복구 작업을 직접 수행해야 한다는 뜻입니다.
 
그렇다면 무효화 영역(Invalid Region)에 대한 복구 작업은 어떻게 처리할까요? 바로 WM_PAINT
메세지를 이용하여 무효화 영역에 대한 복구작업을 수행하게 됩니다. 무효화 영역이 생겼을 경우 윈도우즈는
WM_PAINT 메세지를 발생하여 무효화 영역을 처리하도록 하는 것입니다.
 
이번 교육자료에서는 WM_PAINT 메세지에 대해 자세히 알아보겠습니다.
 
 
 
1. WM_PAINT 메세지는 Flag성 메세지
 
보통 윈도우에 메시지가 발생하면 이 메세지는 윈도우 메시지 큐에 들어가게 되고, WndProc는
메세지 큐에 들어온 순서대로 메시지들을 처리하게 됩니다. 하지만 Flag성 메시지는 보통 메시지와는
다른 처리방법을 가지고 있습니다.
 
Flag성 메세지란 메세지가 발생하게 되면 처리되기 위해 메시지 큐에 들어가는 것이 아닌 처리되어야
함을 의미하는 Flag만 설정하는 메세지를 의미합니다. Flag성 메시지는 GetMessage 함수가 메시지
큐에 들어있는 메시지를 다 처리하고 난 후, Flag성 메세지들을 검사하게 되는데 Flag성 메세지의
Flag가 설정되어 있다면 바로 처리 되게 됩니다.
 
이와 같이 Flag성 메시지는 보통 메시지들과는 다른 처리 방법을 가지고 있고 보통 메시지들 보다
처리되는 우선순위가 가장 낮음을 알 수 있습니다.
 
WM_PAINT 메시지는 보통 메시지가 아닌 Flag성 메시지 입니다. 이는 다시 말해서 WM_PAINT
메시지는 처리를 위해 다른 무언가에 의해 발생 되어지는 메시지가 아닌 처리되어야 할 필요성, Flag
만 설정되고 메세지 큐안의 다른 메세지들 처리가 끝나면 처리되는 메세지 입니다.
 
Flag성 메세지에는 WM_PAINT 메세지 외 WM_TIMER 메세지가 있습니다. WM_TIMER 메세지는
사용자가 정의하는 시간 단위로 발생하는 메세지 입니다. 예를 들어 사용자가 SetTimer 함수를 통해
1000ms 단위로 WM_TIMER 메세지를 발생하도록 설정해 놓았다면 WM_TIMER는 1000ms 단위로
발생하여야 합니다. 하지만 실제적으로 발생주기를 체크해 보면 1000ms일 때도 있고 1003ms,
1002ms 등 다양한 시간차가 나는 것을 확인할 수 있습니다.
 
이는 WM_TIMER 메세지가 Flag성 메세지 이기 때문에 발생되어야 하는 1000ms 안에 만약 메세지
큐안에 처리해야 될 메세지가 많다면 이들 메세지가 다 처리되고 난 후 WM_TIMER 메세지가 처리
되기 때문에 시간차가 나게 되는 것입니다. 이 때문에 정확한 시간 타이머를 요구하는 프로그램에서
WM_TIMER 메세지를 쓰지 않도록 권장하고 있는 것입니다.
 
 
2. WM_PAINT 메세지 Parameter
 
1) wParam : 그리기 작업에 필요한 DC 핸들이 담겨 있습니다. 이 DC는 일부 공통 컨트롤에 의해
사용되는 DC 입니다. 일반적으로 wParam에 들어있는 DC 보다는 BeginPaint 함수를
통해 얻은 DC를 이용하는 것이 안전합니다.
 
 
3. WM_PAINT 메세지 발생시점
 
WM_PAINT 메세지는 무효화 영역(Invalid Region)이 생겼을 경우 이를 복구하기 위해 전달 되는 메세지
입니다. 즉 WM_PAINT 메세지의 발생시점은 무효화 영역이 생기는 시점으로 보시면 됩니다.
다음과 같은 경우입니다.
 
1) 윈도우가 처음 만들어져 사용자 눈에 보일 때
2) 윈도우가 다른 윈도우에 가려졌다가 환원될 때
3) 윈도우의 크기를 조절할 때
4) 윈도우의 위치가 이동되어 화면 밖으로 나갔다가 다시 나타날 때
5) 윈도우가 스크롤 될 때
 
이 외에도 WM_PAINT 메세지는 사용자에 의해 발생되는 경우도 있습니다. UpdateWindow 함수를
호출하여 WM_PAINT 메세지를 발생시키거나 InvalidateRect 함수를 사용하여 무효화 영역을 사용자가
지정하여 WM_PAINT 메세지를 처리 하도록 하는 경우 등이 있습니다.
 
UpdateWindow 함수와 InvalidateRect 함수에 대한 자세한 설명은 아래의 링크를 참고해 주시기 바랍니다.
 
* InvalidateRect 함수에 대하여 : http://www.tipssoft.com/bulletin/tb.php/FAQ/632
* UpdateWindow 함수에 대하여 : http://www.tipssoft.com/bulletin/tb.php/FAQ/633
 
 
4. WM_PAINT 메세지를 통한 무효화 영역(Invalid Region)의 복구 과정
 
앞에서 살펴본 무효화 영역(Invalid Region)은 WM_PAINT 메세지를 통해 어떻게 복구가 되는지 살펴보겠습니다.
 
 
위 그림과 같이 생성된 윈도우가 다른 윈도우에 가려져 무효화 영역(Invalid Region)이 생겼다고 하면 이를 복구 시켜
주기 위해 생성된 윈도우 전체를 다시 그려줘야 할까요? 만약 무효화 영역이 생길때 마다 윈도우 전체를 다시 그려주는
작업이 반복된다면 메모리 소모가 심해져 프로그램 전체에 과부하가 걸릴 수 있을 것입니다.
 
따라서 윈도우즈는 무효화 영역(Invalid Region)이 생겼을 경우 전체를 다시 그려주는 것이 아닌 무효화 영역만을 다시
그리도록 하고 있습니다. 예를 들어 목욕탕 안의 타일을 생각해 보겠습니다. 목욕탕의 타일 하나가 깨졌다면 목욕탕
안의 타일 전체를 바꾸는 것이 아닌 깨진 타일만 바꾸면 됩니다. 이와 같은 맥락으로 윈도우에 무효화 영역이 생겼다면
윈도우 전체를 다시 그려주는 것이 아닌 프로그램의 효율성을 위해 무효화 영역만을 다시 그려주게 됩니다.
 
위와 같이 무효화 영역(Invalid Region)이 생기면 윈도우즈는 이에 대한 복구를 위해 WM_PAINT 메세지의 Flag를 설정
하여 무효화 영역에 대한 처리를 하도록 합니다. 앞에서 살펴본 바와 같이 WM_PAINT 메세지는 Flag성 메세지로써
윈도우에 무효화 영역이 생겼다면 WM_PAINT 메세지의 Flag를 설정해 놓고, 현재 메세지 큐안의 처리해야 할 메세지들을
다 처리하고 나면 GetMessage 함수가 Flag성 메세지들을 살펴보게 되는데 이때 WM_PAINT 메세지의 처리 루틴이
이루어 지게 되는 것입니다. 이는 WM_PAINT 메세지는 다른 보통 메세지들 보다 처리 되는 우선순위가 제일 낮다는
점을 주목하셔야 합니다.
 
WM_PAINT 메세지 처리 루틴은 사용자가 직접 정의하도록 되어있습니다. 이는 사용자가 무효화 영역(Invalid Region)이
생길 경우를 미리 예측하여 이에 대한 무효화 영역의 복구 작업 루틴을 WM_PAINT 메세지 처리 루틴에서 정의해 주는
것입니다. 만약 WM_PAINT 메세지 처리 루틴을 사용자가 직접 정의하지 않는다면 DefWindowProc 함수가 이를 처리
하게 되는데 WM_PAINT 메세지 처리 루틴과의 차이점은 DefWindowProc 함수는 무효화 영역이 생기면 이를 유효화
해줄 뿐 다시 그려주는 작업은 하지 않는다는 점입니다.
 
다음 예제를 통해 WM_PAINT 메세지 처리 루틴을 구성하지 않았을 경우 어떻게 되는지 살펴보겠습니다.
 
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
// 마우스 왼쪽 버튼을 클릭했을 경우 메세지 처리 루틴입니다.
// 마우스 왼쪽 버튼을 클릭했을 경우 파란색 사각형을 그려주도록 합니다.
case WM_LBUTTONDOWN:
{
// 그리기 작업을 위한 DC를 GetDC 함수를 통해 얻어옵니다
HDC h_dc = GetDC(hWnd);
 
// 파란색 브러쉬를 설정해 주는 작업입니다.
HBRUSH bru;
bru = CreateSolidBrush(RGB(0,0,255));
SelectObject(h_dc, bru);

// 사각형을 그려줍니다.
Rectangle(h_dc, 10,10,200,200);
}
return FALSE;

case WM_DESTROY :
PostQuitMessage(0);
break;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
 
위의 소스는 마우스 왼쪽 버튼을 클릭 했을 경우 파란색의 사각형을 그려주는 예제입니다. 위의 소스에서
보시다 시피 WM_PAINT 메세지 루틴을 구성하지 않았습니다. 따라서 무효화 영역(Invalid Region)이 생겼을
경우 DefWindowProc 함수에서 무효화 영역에 대한 처리를 하게 됩니다.
 
 
하지만 DefWindowProc 함수는 무효화 영역을 유효화만 시켜줄 뿐 다시 그려주는 작업을 하지 않습니다.
따라서 다음 그림에서와 같이 윈도우의 크기를 줄여준 후 윈도우의 크기를 다시 늘린다면 줄어든 만큼의
무효화 영역(Invalid Region)은 다시 복구 되지 않음을 확인할 수 있습니다.
 
 
 
5. WM_PAINT 메세지 처리 루틴
 
WM_PAINT 메세지 처리 루틴을 소스를 통해 살펴 보겠습니다.
 
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_PAINT:
{
// 무효화 영역(Invalid Region)에 대한 정보를 담기 위한 PAINTSTRUCT 구조체 변수를 선언합니다
PAINTSTRUCT ps;
 
// BeginPaint 함수의 역할은 무효화 영역(Invalid Region)만을 그릴 수 있는 DC와
// 미리 선언한 PAINTSTRUCT 구조체 변수에 무효화 영역(Invalid Region)의 정보를
// 담아 주는 역할을 합니다.
// BeginPaint 함수의 호출을 시작으로 그리기 작업이 끝나면 EndPaint 함수를 호출하여
// 그리기 작업이 끝났음을 알려줍니다.
HDC h_dc = BeginPaint(hWnd, &ps);
 
// ..... 무효화 영역(Invalid Region)을 다시 그려주는 작업 ....
 
EndPaint(hWnd, &ps);
}
return FALSE;

}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
 
WM_PAINT 메세지 처리 루틴의 기본 골격은 위와 같이 WndProc 함수내 switch - case 문으로 처리됩니다.
WM_PAINT 메세지 처리 루틴에서 그리기 작업을 하기전에 이루어져야 할 두가지를 생각해보겠습니다.
 
1) 그리기 작업을 위한 DC를 얻어와야 한다
2) 무효화 영역(Invalid Region)만 다시 그려주기 위해 무효화 영역의 정보를 얻어와야 한다
 
"4. WM_PAINT 메세지를 통한 무효화 영역(Invalid Region)의 복구 과정" 에서 살펴본 바와 같이
무효화 영역(Invalid Region)이 생기면 이 부분만을 복구 시켜줘야 프로그램의 효율성을 높일 수 있음을 알 수
있었습니다. 따라서 WM_PAINT 메세지 처리 루틴에서 무효화 영역의 복구 작업을 시작하기 전에 무효화 영역에
대한 정보와 복구 작업을 위한 DC를 얻어오는 준비과정이 먼저 이루어 져야 합니다.
 
먼저 무효화 영역에 대한 정보를 담을 PAINTSTRUCT 구조체 변수를 선언합니다. 이 변수는 무효화 영역의
left, top, right, bottom 좌표값을 담을 변수 입니다.
 
PAINTSTRUCT ps;
 
그리고 실제적으로 무효화 영역(Invalid Region)의 정보를 담고 복구 작업을 위한 DC를 얻어와야 하는데 이는
다음과 같이 수행 됩니다.
 
HDC h_dc = BeginPaint(hWnd, &ps);
// ..... 무효화 영역(Invalid Region)을 다시 그려주는 작업 ....
EndPaint(hWnd, &ps);
 
BeginPaint 함수는 인자로 생성된 윈도우 핸들과 무효화 영역의 좌표값을 담을 PAINTSTRUCT 구조체 변수를 받아
함수 내부적으로 PAINTSTRUCT 구조체 변수에 무효화 영역의 좌표값을 담아주고 리턴값으로 복구 작업을 위한
DC를 리턴해 줍니다. BeginPaint 함수 호출이 끝나고 나면 무효화 영역의 복구 작업을 위한 준비과정이 끝났음을
의미하고 BenginPaint 함수 호출을 통해 얻은 DC와 무효화 영역의 정보가 담긴 PAINTSTRUCT 구조체 변수를
이용하여 무효화 영역의 복구작업을 수행합니다. 그리고 무효화 영역의 복구 작업이 끝나고 나면 EndPaint 함수
호출을 끝으로 무효화 영역의 복구 작업을 마치게 됩니다.
 
"4. WM_PAINT 메세지를 통한 무효화 영역(Invalid Region)의 복구 과정" 에서 마우스 왼쪽 버튼을 클릭
했을 경우 파란색 사각형을 그려주는 예제에서 WM_PAINT 메세지 처리 루틴을 추가시켜 보겠습니다. WM_PAINT
메세지 처리 루틴에서는 파란색 사각형이 그려진 영역이 무효화 영역(Invalid Region)으로 설정되었을 경우 파란색
사각형을 다시 그려주는 루틴을 구성하여 파란색 사각형이 지워지는 현상을 방지하도록 하겠습니다.
 
// 전역변수 flag는 마우스 왼쪽 버튼을 클릭했을 경우 그려지는 파란색 사각형이
// 무효화 되었을 경우를 위한 변수입니다. flag가 1일 경우 유효화 처리를 하도록 합니다.
int flag = 0;
 
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
// 마우스 왼쪽 버튼을 클릭했을 경우 메세지 처리 루틴입니다.
// 마우스 왼쪽 버튼을 클릭했을 경우 파란색 사각형을 그려주도록 합니다.
case WM_LBUTTONDOWN:
{
// 그리기 작업을 위한 DC를 GetDC 함수를 통해 얻어옵니다
HDC h_dc = GetDC(hWnd);
 
// 파란색 브러쉬를 설정해 주는 작업입니다.
HBRUSH bru;
bru = CreateSolidBrush(RGB(0,0,255));
SelectObject(h_dc, bru);

// 사각형을 그려줍니다.
Rectangle(h_dc, 10,10,200,200);
 
// 전역변수 flag을 1로 설정함으로써 그려진 파란색 사각형이 무효화 되었을 경우
// WM_PAINT 메세지에서 처리토록 합니다.
flag = 1;
}
return FALSE;
 
// WM_PAINT 메세지 처리 루틴입니다.
case WM_PAINT:
{
// 무효화 영역(Invalid Region)에 대한 정보를 담기 위한 PAINTSTRUCT 구조체 변수를 선언합니다
PAINTSTRUCT ps;
 
// BeginPaint 함수의 역할은 무효화 영역(Invalid Region)만을 그릴 수 있는 DC와
// 미리 선언한 PAINTSTRUCT 구조체 변수에 무효화 영역(Invalid Region)의 정보를
// 담아 주는 역할을 합니다.
// BeginPaint 함수의 호출을 시작으로 그리기 작업이 끝나면 EndPaint 함수를 호출하여
// 그리기 작업이 끝났음을 알려줍니다.
HDC h_dc = BeginPaint(hWnd, &ps);
 
// flag가 1로 설정되었다면 이는 파란색 사각형이 무효화 되었을 경우 처리하라는
// 의미이므로 파란색 사각형이 무효화 되었다면 파란색 사각형을 다시 그려주도록 합니다.
if(flag == 1)
{
 
// 파란색 브러쉬를 설정해 주는 작업입니다.
HBRUSH bru;
bru = CreateSolidBrush(RGB(0,0,255));
SelectObject(h_dc, bru);

// 사각형을 그려줍니다.
Rectangle(h_dc, 10,10,200,200);
}
 
EndPaint(hWnd, &ps);
}
return FALSE;

case WM_DESTROY :
PostQuitMessage(0);
break;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
 
위와 같이 WM_PAINT 메세지 처리 루틴을 구성하여 무효화 영역(Invalid Region)에 대한 처리를 해주면 무효화
영역에 대한 처리를 DefWindowProc 함수에서 하지 않고 WM_PAINT 메세지 처리 루틴에서 하게 됩니다. 위에서
구성한 바와 같이 WM_PAINT 메세지 처리 루틴 안에는 그려진 파란색 사각형이 무효화 되었을 경우에 대한 처리
루틴을 보실 수 있습니다.

 

//-------------------------------------------------------------------------------------------------//

 

윈도우의 일부분을 사용자가 변경시키기 위해서는 변경시키고자 하는 부분을 무효화(Invalid)시켜 다시
그려주어야 할 것입니다. 예를 들어 생성된 윈도우의 한쪽 모퉁이에 수시로 변경되는 그림을 배치 시키고
싶다면 그림을 변경시켜 주기 위해 윈도우 전체를 다시 그려주는 것이 아닌 변경되는 부분만을 무효화
시켜 다시 그려 주겠지요.
 
InvalidateRect 함수는 윈도우의 일부분을 무효화 시켜주는 함수입니다. 사용자가 InvalidateRect 함수를
통해 윈도우의 일부분을 무효화 시켜놓았다면 윈도우에 발생되는 WM_PAINT 메시지를 통해 무효화된
부분을 다시 그려주게 되는 것입니다.
 
이번 교육자료에서는 InvalidateRect 함수에 대해 알아보겠습니다.
 
 
 
1. InvalidateRect 함수 프로토 타입
 
InvalidateRect 함수의 프로토 타입은 아래와 같습니다.
 
BOOL InvalidateRect(
HWND hWnd,
CONST RECT *lpRect,
BOOL bErase
);
 
 
2. InvalidateRect 함수 Parameters
 
1) HWND hWnd
 
갱신되어야 할 영역을 지니고 있는 윈도우의 핸들을 의미합니다. 만약 이 값을 NULL로 지정한
다면 시스템 자체는 윈도우 전체를 무효화 영역(Invalid Region)으로 여기고 윈도우 전체를 다시
그리도록 합니다. 이를 위해 InvalidateRect 함수는 WM_ERASEBKGND, WM_NCPAINT 두가지
메시지를 해당 윈도우 프로시저에 전달하게 됩니다.
 
WM_ERAEBKGND 메시지는 해당 윈도우 Client 영역의 배경을 지워줌으로써 Client 영역을 갱신
하고, WM_NCPAINT 메시지는 해당 윈도우의 Client 영역 외의 부분(예, 캡션)을 갱신합니다.
결과적으로 이 두개의 메시지를 통해 해당 윈도우의 전체가 갱신되는 것입니다.
 
2) CONST RECT *lpRect
 
갱신되어야 할 영역의 정보를 담고 있는 RECT 구조체 변수의 포인터를 의미합니다. 사용자는
InvalidateRect 함수를 호출하기 전에 RECT 구조체 변수를 선언하고, 이 변수에 갱신되어야 할
영역의 정보를 담은후 InvalidateRect 함수의 인자로 싣어서 호출하여야 합니다.
 
3) BOOL bErase
 
무효화 영역(Invalid Region)을 갱신시켜줄 시 무효화 영역의 Background를 지워줘야 하는지에
대한 여부를 의미하는 변수입니다. 만약 TRUE값을 설정한다면 BeginPaint 함수가 호출 될 때
무효화 영역의 Background를 지워주는 작업이 이루어 지게 되고, FALSE 값을 설정한다면
무효화 영역의 Background는 그대로 남아있게 됩니다.
 
 
3. InvalidateRect 함수 리턴값
 
1) nonzero : 함수호출이 성공적으로 이루어 졌다면 0이 아닌값을 리턴합니다.
 
2) zero : 함수호출을 실패했다면 0을 리턴합니다.
 
 
4. InvalidateRect 함수와 WM_PAINT 메시지
 
InvalidateRect 함수를 통해 무효화 된 영역은 WM_PAINT 메시지를 통해 다시 그려지게 됩니다.
 
그렇다면 이는 'InvalidateRect 함수호출은 WM_PAINT 메시지를 발생시킨다'를 의미할까요?
 
보통 윈도우에 메시지가 발생하면 이 메세지는 윈도우 메시지 큐에 들어가게 되고, WndProc는
메세지 큐에 들어온 순서대로 메시지들을 처리하게 됩니다. 하지만 Flag성 메시지는 보통 메시지와는
다른 처리방법을 가지고 있습니다.
 
Flag성 메세지란 메세지가 발생하게 되면 처리되기 위해 메시지 큐에 들어가는 것이 아닌 처리되어야
함을 의미하는 Flag만 설정하는 메세지를 의미합니다. Flag성 메시지는 GetMessage 함수가 메시지
큐에 들어있는 메시지를 다 처리하고 난 후, Flag성 메세지들을 검사하게 되는데 Flag성 메세지의
Flag가 설정되어 있다면 바로 처리 되게 됩니다.
 
이와 같이 Flag성 메시지는 보통 메시지들과는 다른 처리 방법을 가지고 있고 보통 메시지들 보다
처리되는 우선순위가 가장 낮음을 알 수 있습니다.
 
WM_PAINT 메시지는 보통 메시지가 아닌 Flag성 메시지 입니다. 이는 다시 말해서 WM_PAINT
메시지는 처리를 위해 다른 무언가에 의해 발생 되어지는 메시지가 아닌 처리되어야 할 필요성, Flag
만 설정되고 메세지 큐안의 다른 메세지들 처리가 끝나면 처리되는 메세지 입니다.
 
Flag성 메세지에는 WM_PAINT 메세지 외 WM_TIMER 메세지가 있습니다. WM_TIMER 메세지는
사용자가 정의하는 시간 단위로 발생하는 메세지 입니다. 예를 들어 사용자가 SetTimer 함수를 통해
1000ms 단위로 WM_TIMER 메세지를 발생하도록 설정해 놓았다면 WM_TIMER는 1000ms 단위로
발생하여야 합니다. 하지만 실제적으로 발생주기를 체크해 보면 1000ms일 때도 있고 1003ms,
1002ms 등 다양한 시간차가 나는 것을 확인할 수 있습니다.
 
이는 WM_TIMER 메세지가 Flag성 메세지 이기 때문에 발생되어야 하는 1000ms 안에 만약 메세지
큐안에 처리해야 될 메세지가 많다면 이들 메세지가 다 처리되고 난 후 WM_TIMER 메세지가 처리
되기 때문에 시간차가 나게 되는 것입니다. 이 때문에 정확한 시간 타이머를 요구하는 프로그램에서
WM_TIMER 메세지를 쓰지 않도록 권장하고 있는 것입니다.
 
결과적으로 InvalidateRect 함수호출은 WM_PAINT 메시지를 발생시키지 않음을 알 수 있습니다.
InvalidateRect 함수는 무효화 영역만을 지정해 놓고 이 영역을 처리해 달라는 신호만을 설정하게
되는데 이것이 WM_PAINT 메세지의 Flag를 설정하는 것입니다.
 
즉, InvalidateRect 함수호출이 끝나고 바로 WM_PAINT 메세지 처리 루틴이 돌아가는 것이 아니고
WM_PAINT 메세지의 Flag만 설정해 놓고 WinMain 함수 내 GetMessage 함수가 메세지 큐에 들어
있는 메세지들을 다 처리하고 난 후 WM_PAINT 메세지의 Flag를 검사하여 이를 처리하도록 하는
것입니다.
 
다음 소스에서 프로그램 진행순서를 살펴 보겠습니다.
 
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_LBUTTONDOWN:
{
InvalidateRect(hWnd, NULL, FALSE);
// InvalidateRect 함수 호출후 작업... ---> 처리루틴 A
}
return FALSE;
 
case WM_PAINT:
{
// WM_PAINT 메세지 처리 루틴 내 작업... ---> 처리루틴 B
}
return FALSE;
 
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
 
위와 같이 마우스의 왼쪽 버튼을 눌렀을 경우 InvalidateRect 함수를 호출 합니다. InvalidateRect 함수
호출 후 윈도우의 메세지 큐안에 처리되어야 할 메세지들이 많이 있다고 가정하면 InvalidateRect 함수
호출을 통해 WM_PAINT 메세지의 Flag를 설정하게 되고, 메세지 큐안에는 이미 처리해야 할 메세지
들이 많이 들어있다고 가정하였기 때문에 바로 WM_PAINT 메세지를 처리할 수 없게 됩니다.
 
따라서 InvalidateRect 함수 호출 후 위의 처리루틴 A 과정이 처리되고 처리루틴 A 처리가 끝난 후
메세지 큐안의 메세지들이 다 처리 되어 WM_PAINT 메세지를 처리할 수 있는 상태가 되면
WM_PAINT메세지 처리루틴 처리루틴 B로 넘어가게 되는 것입니다.
 
 
5. InvalidateRect 함수 예제
 
InvalidateRect 함수를 이용한 간단한 예제를 만들어 보겠습니다.
 
두개의 Bitmap 이미지를 리소스로 등록시킨 후 마우스 왼쪽 버튼을 클릭시 등록된 Bitmap이 번갈아
가면서 출력되는 간단한 프로그램입니다.
 
동일한 크기의 Bitmap을 마우스 왼쪽 버튼을 클릭시 Bitmap을 번갈아 가며 출력하기 위해 윈도우
전체를 바꿔주는 것이 아닌 Bitmap이 출력되는 영역, 즉 Bitmap의 크기만큼을 InvalidateRect 함수를
통하여 갱신시키도록 합니다.
 
WinMain 함수는 생략하고 설명이 필요한 WndProc 함수내 메시지 처리 소스와 필요한 전역변수를
소개하겠습니다.
 
// 전역변수 MyBitmap[2]는 리소스로 등록한 Bitmap 두개를 담기 위한 변수입니다.
// 리소스에 등록한 Bitmap ID는 IDB_BITMAP1, IDB_BITMAP2 입니다.
// 두개의 Bitmap 의 사이즈는 동일합니다. 따라서 Bitmap을 바꾸기 위해
// Bitmap 크기만큼의 영역만을 갱신시켜주게 됩니다.
// Bitmap을 리소스로 등록시키는 과정은 생략하도록 하겠습니다.
HBITMAP MyBitmap[2];
// 전역변수 count는 등록한 Bitmap 두개를 마우스 왼쪽 버튼을 클릭시 번갈아 가며 출력하기
// flag 입니다. flag가 0일시 IDB_BITMAP1, flag가 1일시 IDB_BITMAP2를 출력시키게 됩니다.
int count;
// 무효화 영역(Invalid Region)의 정보를 담을 RECT 구조체 변수 입니다.
// 우리가 출력시킬 영역은 Bitmap의 크기이므로 RECT 구조체 변수에는 Bitmap의 크기가
// 담겨지게 됩니다.
RECT rc;
 
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
// WM_CREATE 메시지는 윈도우가 생성될때 발생되는 메시지 입니다.
// 이 메세지 처리 루틴에서 리소스에 등록한 Bitmap 두개를 로드 시키고
// 전역변수 들의 값을 초기화 시킵니다.
case WM_CREATE:
{
// 리소스에 등록한 Bitmap을 로드 시키는 과정입니다.
MyBitmap[0] = LoadBitmap((HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE), "IDB_BITMAP1");
MyBitmap[1] = LoadBitmap((HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE), "IDB_BITMAP2");
 
// 마우스 왼쪽 버튼을 클릭시 Bitmap을 번갈아 가며 출력시키기 위한 flag 입니다.
// 초기에 count를 0으로 초기화 하여 첫 화면에서는 IDB_BITMAP1을 출력하도록 합니다.
count = 0;
 
// 무효화 영역(Invalid Region)의 정보를 담는 과정입니다.
// Bitmap이 출력될 위치와 Bitmap의 크기가 담겨지게 됩니다.
// RECT 구조체 변수 rc의 멤버 left, top은 Bitmap이 출력될 위치
// right, bottom은 Bitmap의 크기가 담겨지게 됩니다.
rc.left = 0;
rc.top = 0;
// 로드 시킨 Bitmap의 크기를 RECT 구조체 변수 rc의 멤버 right, bottom에
// 담는 과정입니다.
BITMAP info;
GetObject(MyBitmap[0], sizeof(BITMAP), &info);

rc.right = info.bmWidth;
rc.bottom = info.bmHeight;
}
return FALSE;
 
// 마우스 왼쪽 버튼을 눌렀을때의 처리 루틴입니다.
case WM_LBUTTONDOWN:
{
// 마우스 왼쪽 버튼이 눌린다면 미리 정의해 놓은 무효화 영역의 정보를 담은
// RECT 구조체 변수를 InvalidateRect 함수를 통해 호출하여 무효화 시킵니다.
// 무효화 영역은 WM_PAINT 메시지 처리 루틴에서 다시 그려지게 될것입니다.
InvalidateRect(hWnd, &rc, FALSE);
}
return FALSE;
 
// WM_PAINT 메시지 처리 루틴을 통해 Bitmap을 그리는 과정과
// 무효화 영역(Invalid Region) 처리가 이루어집니다.
case WM_PAINT:
{
// BeginPaint 함수를 통해 그리기 작업이 이루어질 DC와 이에 대한 영역을
// PAINTSTRUCT 구조체 변수에 담아줍니다.
PAINTSTRUCT ps;
HDC h_dc = BeginPaint(hWnd, &ps);
 
// 여기서 부터 Bitmap을 윈도우에 출력시켜 주는 작업이 시작됩니다.
// Bitmap 출력에 대한 자세한 설명은 생략하도록 하겠습니다.
HDC memDC = CreateCompatibleDC(h_dc);
HBITMAP oldBitmap;
 
// 전역변수 count를 조사하여 0일 경우 IDB_BITMAP1을, 1일 경우 IDB_BITMAP2를
// 출력시켜주도록 하는 루틴입니다.
if(count == 1)
{
oldBitmap = (HBITMAP)SelectObject(memDC, MyBitmap[count]);
count = 0;
}
else
{
oldBitmap = (HBITMAP)SelectObject(memDC, MyBitmap[count]);
count = 1;
}
 
// 출력하고자 하는 Bitmap이 출력되는 과정입니다.
// left, top(0, 0) 위치에 Bitmap의 사이즈 크기(rc.right, rc.bottom) 출력시킵니다.
BitBlt(h_dc, 0,0, rc.right, rc.bottom, memDC, 0,0,SRCCOPY);
 
SelectObject(memDC, oldBitmap);
DeleteDC(memDC);
 
EndPaint(hWnd, &ps);
}
return FALSE;
}
 
 

 

//----------------------------------------------------------------------------------//

 

MFC에서 임의로 그림을 그린 경우 다른 윈도우로 해당 윈도우의 그림을 가렸다가 보이게하면 그림이
지워지는 현상이 발생할 때가 있습니다.
 
이와 같이 생성된 윈도우가 다른 윈도우에 의하여 가려져서 무효화 영역이 발생하면 WM_PAINT 메세지가
호출되고 WM_PAINT 메세지를 처리하는 메세지 핸들러에서 무효화 영역이 복구되어집니다. 다이얼로그
기반 대화상자에서 WM_PAINT 메세지는 OnPaint() 라는 메세지 함수에서 처리하게 됩니다.
 
WM_PAINT 메세지는 플래그성 메세지이기 때문에 메세지가 발생하여 WM_PAINT 관련 플래그에 1 로
설정되면 메세지 큐가 비어 있을 경우 WM_PAINT 메세지가 지속적으로 발생하게 됩니다. 이 행위는
WM_PAINT 관련 플래그가 0 으로 다시 변경되기 전까지 계속 반복됩니다. WM_PAINT 메세지에 대한
자세한 내용은 아래의 링크를 참고하시기 바랍니다.
 
WM_PAINT 메세지에 대하여 : http://www.tipssoft.com/bulletin/tb.php/FAQ/636
 
 
WM_PAINT 메세지를 처리해주는 OnPaint() 에는 무효화 영역을 복구하는 코드와 함께 WM_PAINT
메세지를 0 으로 변경해 주는 코드도 함께 존재하게됩니다. 막 생성한 다이얼로그 기반 대화상자의
OnPaint() 함수 기본 코드는 아래와 같습니다.
 
void 프로젝트명::OnPaint()
{
if (IsIconic()) {
CPaintDC dc(this); // device context for painting
 
SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);

} else {
CDialog::OnPaint();
}
}
 
위 함수에서 if 문은 프로그램이 최소화되었을 때 아이콘을 그려주는 루틴이고, else 문의 경우 프로그램이
최소화 상태가 아닌 경우에 사용자가 처리해 주어야 하는 무효화되었던 영역을 복구하는 루틴입니다.
 
if 문에 있는 CPaintDC 의 객체 파과자와 else 문에 있는 CDialog::OnPaint(); 는 1로 설정된 WM_PAINT
메세지는를 0 으로 다시 변경해줍니다.
 
종종 이런 특성을 잘 모르고 작업을 하면서 아래와 같이 else 문의 CDialog::OnPaint() 를 주석처리하고
CPaintDC가 아닌 CClientDC를 선언하여 작업을 하는 경우를 볼 수 있습니다.
 
void 프로젝트명::OnPaint()
{
if (IsIconic()) {
CPaintDC dc(this); // device context for painting
 
... 위 코드의 if 문과 동일 ( 중략 ) ...
} else {
// 클라이언트 DC를 선언한다.
CClientDC dc(this);
// 클라이언트 DC로 사각형을 그린다.
dc.Rectangle(10, 10, 100, 100);
// CDialog::OnPaint();
}
}
 
이렇게 작업을 하게되면 어떻게 될까요?
 
위의 else 문에 있는 CDialog::OnPaint(); 가 주석 처리되었기 때문에 WM_PAINT 메세지를 0 으로
변경하지 못하여 끊임 없이 OnPaint() 함수를 호출하게 되고, 그 결과 함수 안에 작성된 코드의 반복
처리로 인해 CPU 점유율이 크게 증가하게 됩니다. 아래의 그림은 다이얼로그 기반 대화상자에서
OnPaint() 부분에 위의 코드를 추가한 프로그램을 수행시켰을 때의 CPU 점유율입니다.
 
 
위와 같은 현상은 else 문에 CDialog::OnPaint(); 를 주석처리 한 상태에서 아무 작업을 하지 않고,
실행하여도 동일하게 발생합니다. OnPaint() 가 발생하므로 인해 반복적으로 다이얼로그를 그리는
작업을 기본적으로 수행하기때문입니다.
 
위와 같은 CPU 점유율을 증가시키는 현상 없이 정상적으로 OnPaint() 에서 무효화 영역에 대한 처리를
해주기 위해서는 CPaintDC 를 선언하여 사용하여야 합니다.
 
< 코드 1 >
void 프로젝트명::OnPaint()
{
if (IsIconic()) {
CPaintDC dc(this); // device context for painting
 
... 위 코드의 if 문과 동일 ( 중략 ) ...
} else {
// 페인트 DC를 선언한다.
CPaintDC dc(this);
// 클라이언트 DC로 사각형을 그린다.
dc.Rectangle(10, 10, 100, 100);
// CDialog::OnPaint();
}
}
 
<코드 1>과 같이 CPaintDC 객체를 생성하여 사용하면 해당 DC의 객체 파괴자에서 WM_PAINT 메세지를
0 으로 변경해주어서 CPU 점유율을 증가시키는 현상을 막을 수 있습니다. 또 CDialog::OnPaint() 는
CPaintDC의 객체 파괴자와 동일한 기능을 수행하기 때문에 중복 루틴을 방지하기 위하여 주석 처리하는
것이 좋습니다.
 
또 다른 코드의 중복성을 제거하기 위해서는 <코드 1><코드 2> 와 같이 CPaintDC 를 if 문 밖에서
선언하여 if 문과 else 문에서 함께 사용하도록 할 수 있습니다.
 
< 코드 2 >
void 프로젝트명::OnPaint()
{
CPaintDC dc(this); // device context for painting
if (IsIconic()) {

... 위 코드의 if 문과 동일 ( 중략 ) ...

} else {
 
// 클라이언트 DC로 사각형을 그린다.
dc.Rectangle(10, 10, 100, 100);

// CDialog::OnPaint();
}
}

 

반응형

댓글