자료 저장소

# 무효영역

무효영역이란 윈도우의 화면의 일부가 변경되어 다시 그려져야 할 부분을 말한다.
만약 윈도우 전체가 다 변경되는 경우라면 무효영역을 작업영역과 동일 하지만 윈도우의 일부분만 변경되었다면
변경된 부분만 무효영역이 된다. 운영체제는 최대한 좁은 면적만 무효영역으로 만들어 그리기에 소요되는 시간을
최대한 적게 소모하게 한다.

윈도우가 다시 그려져야 할 상황

① 윈도우가 처음 생성되었을 때
② 윈도우가 위치가 이동되었을 때
③ 윈도우의 크기가 변경되었을 때, 최대, 최소화 되었을 때
④ 다른 윈도우에 가려져 있다가 드러날 때, 즉 언커버될 때
⑤ 스크롤 될 때

화면을 다시 그리려면 InvalidateRect 함수로 작업영역을 무효화 해야 한다.

case WM_LBUTTONDOWN:
    bRect=!bRect;
    InvalidateRect(hWnd,NULL,TRUE);
    return 0;

BOOL InvalidateRect(HWND hWnd,CONST RECT *lpRect, BOOL bErase);

hWnd 윈도우의 lpRect 영역을 무효화 하여 다시 그리도록 하는 함수이다. 두번째 lpRect에 무효화할 영역을
지정하지 않고 NULL을 주면 작업영역 전체가 무효화 되어 깜박임이 심해진다.
이 부분을 수정하려면 화면의 변경이 일어나는 부분만 무효화 해야 하는데 다음과 같은 방식으로 수정하면된다.

RECT rt;
case WM_LBUTTONDOWN;
    bRect=!bRect;
    SetRect(&rt,10,10,200,200); // 변경이 일어나는 부분을 지정한다.
    InvalidataRect(hWnd,&rt,TRUE); // 지정된 부분만 무효화하고 다시 그린다.
    return 0;

주의 할 점

case WM_LBUTTONDOWN:
    bRect=!bRect;
    SendMessage(hWnd,WM_PAINT,0,0);
    return 0;

위 메시지는 절대로 제대로 작용되지 않는 예이다. Win32 그리기 시스템은 무효영역이 없으면 아예 그리기를
하지 않는다. 코드는 호출되지만 무효영역 바깥으로의 출력은 잘려 나간다.
무효영역의 바깥은 그리기에서 완전히 제외되는데 위 코드의 경우 WM_PAINT가 보내지기는 하지만 무효영역이
없기 때문에 그림은 그려지지 않는다. 그림을 바꾸고자 할 경우는 무효영역을 설정하여 윈도우즈가 WM_PAINT
메시지를 보내도록 해야 한다.

InvalidateRect 함수의 반대 함수는 다음과 같다.

BOOL ValidataRect(HWND hWnd,CONST RECT *lpRect);

함수의 이름이 의미하는 바대로 지정한 부분을유효화 한다. 즉 그리기 영역에서 제외 시킨다.
무효영역을 정교하게 계산하고자 할 때 일단 큰 영역을 무효화하고 제외되는 부분을 다시 유효화할 때 이 함수가
사용된다.

BOOL InvalidateRgn(HWND hWnd, HRGN hRgn, BOOL bErase);
BOOL ValidateRgn(HWND hWnd, HRGN hRgn);

위 두 함수는 리전을 무효화하고 유효화 하는 함수 이다. 리전은 곡선 및 분리된 영역을 말하는데 좀 더 복잡한
형태의 무효화를 할 수 있다.


# WM_PAINT 메시지가 보내지는 시점

WM_PAINT 메시지는 모든 메시지 중에서 우선 순위가 가장 낮으며 WM_PAINT 보다 순위가 낮은 메시지는
WM_TIMER 뿐이다. 윈도우즈가 응용프로그램 WM_PAINT 메시지를 보내는 경우는 두 가지 조건이 충족되었
을 때이다.

첫째, 메시지 큐에 대기중인 메시지가 없어야 한다.
사용자의 입력을 처리하는 일이 화면 그리기보다 더 중요하기 때문에 입력 처리상태에서는 그리기를 하지않는다.

둘째, 무효영역이 있어야 한다.
아무리 한가해도 다시 그려져야 할 부분이 없으면 WM_PAINT 메시지를 보낼 필요가 없다.
WM_PAINT를 큐에 넣는 주체는 운영체제 이며 응용 프로그램이 직접 WM_PAINT 메시지를 큐에 넣지 않는다.
응용 프로그램은 다만 무효영역을 만들 뿐이며 무효영역이 있으면 WM_PAINT가 메시지 큐에 자동으로 들어간다.


# WM_PAINT 통합

WM_PAINT 메시지는 메시지 큐에 결코 두 개 이상 들어가지 않는다. 윈도우즈는 두개의 WM_PAINT 메시지가
큐에 있으면 이 둘을 합쳐 하나로 만들되 두 메시지가 가진 무효영역의 합집합을 새로운 무효영역으로 설정한다.
분리된 두 무효영역을 두 번 나누어 그리는 것보다 합쳐서 한번에 그리는 것이 훨씬 더 빠르기 때문이다.
InvalidateRect를 호출한다고 해서 즉기 그리기를 하지 않으며 무효영역을 최대한 모아서 한꺼번에 그리므로
무효화를 여러번 나누어해도 무관하다. 불필요한 그리기를 한번이라도 줄여 운영체제의 전반적인 속도를 높이기
위해서인데 이런 메시지 통합은 WM_TIMER에도 똑같이 적용된다.


# UpdateWindow 함수

화면이 무효화 되는 시점과 실제고 그려지는 시점은 비동기적이다. 무효영역이 있더라도 다른 메시지가 큐에
있다면 WM_PAINT메시지는 대기중인 메시지가 처리되는 동안에는 보내지지 않는다.
이럴때 무효화 되는 즉시 화면을 다시 그리게 할 수 있는데 이 때 사용하는 함수가 UpdateWindow이다.
이 함수는 WM_PAINT를 메시지 큐를 거치지 않고 곧바로 WndProc으로 보내므로 다른 메시지보다 우선해서
처리하도록 한다.

BOOL UpdateWindow(HWND hWnd);
BOOL RedrawWindow(HWND hWnd, CONST RECT *lprcUpdate, HRGN hrgnUpdate, UINT flags);

아래 RedrawWindow함수는 그리기를 할 사각형이나 영역을 지정할 수 있고 플래그로 그리기 방법을 지정 할 수
있다는 점에서 좀 더 많은 기능을 가진다. 이 함수를 사용하면 비작업영역도 즉각 그리기를 할 수 있고 자식 윈도우
까지 한꺼번에 그릴 수 있다. 하지만 사용빈도는 높지 않다.


# 클리핑 영역

무효영역이란 다시 그려져야 할 부분이다. 그런데 운영체제가 실제 그리기에 사용하는 영역은 무효영역이 아니라
클리핑 영역이다. 클리핑 영역은 무효영역중에서도 화면에 보이는 가시 영역을 말한다.무효영역과가시 영역의
교집합
이 클리핑 영역이다. 대개의 경우 클리핑 영역은 무효영역과 일치하지만 그렇지 않은 경우도 있다.

윈도우의 일부가 화면 영역을 벗어나 있는 경우도 클리핑 영역이 무효영역이 보다 좁다. 운영체제는 무효영역중에
서도 최소한의 영역만을 그리기 위해, 또 다른 윈도우 영역을 침범하지 않기 위해 클리핑 영역을 사용한다.
한마디로 클리핑 영역을 그려져야 할 부분 중에서 그리기가 허가된 또한 진짜로 그릴 필요가 있는 영역이다.
무효영역을 운영체제에 의해 설정되거나 응용프로그램이 InvalidateRect 등의 함수로 직접 설정하며 클리핑 영역은
BeginPaint에 의해 계산된다. 이렇게 계산된 클리핑 영역은 DC에 선택되며 모든 GDI 함수들은 클리핑 영역을
참고하여 자신이 그리지 말아야 할 부분을 잘라낸다.


# 윈도우 스타일

WS_CLIPCHILDREN : 차일드가 차지한 영역을 그리기에서 제외된다.
WS_CLIPSIBLINGS : 차일드끼리 겹쳐 있을 때 형제끼리 서로의 영역을 침범하지 않도록 한다.

CS_HREDRAW, CS_VREDRAW 스타일도 무효영역과 관계가 있다. 이 스타일이 지정되면 가로, 세로 방향으로
윈도우의 크기가 변경될 때 전체 작업영역을 무효화 하여 다시 그리기를 한다.


# PAINTSTRUCT

WM_PAINT의 선두에서는 항상 BeginPaint 함수를 호출한다. 이 함수는 윈도우 핸들과 PAINTSTRUCT 구조체의
포인터를 인수로 취하는데 이 구조체를 잘 활용하면 그리기 속도를 비약적으로 개선할 수 있다.
typedef struct tagPAINTSTRUCT { 
HDC hdc;
BOOL fErase;
RECT rcPaint;
BOOL fRestore;
BOOL fIncUpdate;
BYTE rgbReserved[32];
} PAINTSTRUCT;
이 구조체는 BeginPaint 함수에 의해 채워지며 응용 프로그램은 필요한 경우 이 구조체의 정보를 사용할 수 있다.
뒤쪽 세개의 멤버은 내부적으로 사용하는 런타임 데이터이므로 신경 쓸 필요없다. 첫번째 멤버 hdc는 그리기에
사용될 DC의 핸들값이며 BeginPaint가 리턴하는 값과 동일하다.
두번째 멤버 fErase는 배경을 지울 것인가 아닌가를 지정하는데 이 멤버가 0이 아닌값을 가지면 프로그램이 배경을
직접 지워야 할 필요가 있다는 뜻이다. 윈도우 클래스에 배경브러시가 정의되어 있지 않거나 WM_ERASEBKGND
메시지에서 배경을 지우지 않아 0(=FALSE)을 리턴하면 이 멤버는 0이 아닌 값을 가지게 되는데 응용 프로그램은
이 값이 0이 아니면 직접 배경을 지워야 한다. 그러나 통상 FALSE이다.
세번째 멤버 rcPaint는 그리기를 해야 할 사각영역을 가진다. 이 영역은클리핑 영역을 포함하는 최소한의 사각영역
이다. 보통은 이 영역을 무시하고 전체 윈도우를 다 그리지만 이 영역을 잘 활용하면 그리는 속도를 훨씬 더 빠르게
할 수 있다.


# BeginPaint

WM_PAINT의 선두에서 호출되는 BeginPaint는 다음과 같은 아주 중요한 일을 한다.

① 그리기를 위해 DC를 발급받는다. 이 DC 핸들은 BeginPaint의 리턴값으로 돌려지기도 하며 PAINTSTRUCT
구조체의 hdc 멤버에도 대입되는데 이 DC 핸들을 사용하여 그리기를 하면된다. fErase,rcPaint도 조사해서 채운다.

② 클리핑 영역을 조사하여 DC에 설정한다. 모든 출력함수는 DC에 설정된 클리핑 영역을 참고하여 영역 바깥
으로 출력되는 것을 잘라낸다. 이때 무효영역, 윈도우의 Z 순서, 보이기 상태 등의 여러가지 정보를 참조한다.

③  무효영역을 없애(=유효화) 다시 WM_PAINT 메시지가 호출되지 않도록 한다. 만약 화면만 다시 그리고 무효
영역을 그대로 내버려두면 운영체제는 계속해서 WM_PAINT 메시지를 보낼 것이다.

④ 다시 그려지는 영역에 캐럿이 있다면 그리기를 시작하기 전에 숨겨 캐럿이 파괴되지 않도록 한다.
이렇게 숨겨진 캐럿은 그리기를 끝내는 시점인 EndPaint에 의해 복구된다.

⑤ 윈도우의 배경을 백그라운드 브러시로 지우기 위해 WM_ERASEBKGND 메시지를 보내 처리한다.
또한 WM_NCPAINT 메시지를 보내 비작업영역을 그리도록 한다.

응용 프로그램이나 또는 운영체제에 의해 설정된 무효영역을 WM_PAINT의 BeginPaint 함수에 의해 제거 되므
로써 새로운 무효영역이 생기기 전에는 WM_PAINT 메시지가 전달되지 않는다.
무효영역이 있으므로 WM_PAINT메시지가 발생하는데 WM_PAINT 메시지 처리 루틴에서 무효영역을 없애지
않는 다면 WM_PAINT 메시지가 계속 발생 할 것이다.
이런 이유로 GetDC를 사용해서는 안되며 반드시 BeginPaint 함수를 사용하여 DC를 얻어야 한다.

WM_PAINT는 다른 메시지와는 달리 그릴게 없다고 하더라도 메시지 처리코드를 비워두어선 안된다.

댓글 로드 중…

최근에 게시된 글