< BeginPaint >

 

WM_PAINT 선두에서 호출되는 BeginPaint의 동작은 아주 중요하기 때문에 동작 순서를 자세히 알아두는 것이 좋다.

 

1. 그리기를 위해 DC를 발급받는다. 이 DC 핸들은 BeginPaint의 리턴값으로 돌려지기도 하며 PAINTSTRUCT 구조체의 hdc 멤버에도

   대입되는데 이 DC의 핸들을 사용하여 그리기를 하면 된다. 또한, PAINTSTRUCT 구조체의 fErase, rcPaint에 값을 채운다.

 

2. 클리핑 영역을 조사하여 DC에 설정한다. 모든 출력 함수는 DC에 설정된 클리핑 영역을 참고하여 영역 바깥으로 출력되는 것을

   잘라낸다.

 

3. 무효영역을 없애 다시 WM_PAINT 메시지가 호출되지 않도록 한다. 만약 화면만 그리고 무효영역을 없애지 않는다면, 운영체제는

   계속해서 WM_PAINT 메시지를 보낼 것이다.

 

4. 다시 그려지는 영역에 캐럿이 있다면 그리기를 시작하기 전에 숨겨 캐럿이 파괴되지 않도록 한다. 이렇게 숨겨진 캐럿은 그리기를

   끝내는 시점인 EndPaint에 의해 복구된다.

 

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

   비작업영역을 그리도록 한다.

 

여기서 중요한 것은 무효영역을 없애는 기능이다. 무효영역이 있음으로써 WM_PAINT 메시지가 발생하는데 WM_PAINT 메시지 처리

루틴에서 무효영역을 없애는 것은 생각해보면 당연하다.

그렇기 때문에 WM_PAINT 메시지 처리 루틴에서는 반드시 BeginPaint 함수를 사용하여 DC를 얻어야 하며 GetDC를 사용해서는 안 된다.

앞서 말했듯이 BeginPaint는 단순히 DC를 발급받는 것 이상의 중요한 일을 하기 때문이다.

 

또한, 아무것도 그릴것이 없다고해서 WM_PAINT 메시지 처리 코드를 비워 두어서는 안되는데 이유는 무효영역이 없어지지 않기 때문에

WM_PAINT 메시지가 무한히 날라오게 된다. 그릴 것이 없다면 WndProc에서 WM_PAINT를 아예 명시하지 않고 DefWindowProc으로

보내야 한다. 디폴트 처리 루틴에서 WM_PAINT에 대해 Beginpaint, EndPaint를 차례대로 호출하도록 되어 있기 때문이다.

 

< 그리기 메시지 >

 

BeginPaint가 하는 일 중에 WM_NCPAINT 메시지와 WM_ERASEBKGND 메시지를 보내는 것이 있다.

 

WM_NCPAINT는 비작업영역이 그려져야 할 때 운영체제가 보내는 메시지이며 타이틀바, 경계선, 시스템 메뉴 등을 그려야 한다.

이 메시지는 DefWindowProc에서 처리하며 응용 프로그램에서 처리하지 않는다.

 

WM_ERASEBKGND 메시지는 윈도우 클래스에 등록된 배경 브러시로 작업 영역의 배경을 지우는 역할을 한다.

윈도우 클래스 정의문을 보면 배경 브러시를 보통 흰색으로 지정한다.

 

1
    WndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
cs

 

WM_ERASEBKGND 메시지는 배경 브러쉬가 등록되어 있을 경우 이 브러시로 배경을 칠하는데 이는 곧 배경을 지우는 것이다.

(무효영역이 발생 시 다시 그릴 때 기본 설정된 브러시로 그려진다는 얘기임)

 

만약 배경을 단순한 브러시로 칠하는 방법을 사용하고 싶지 않다면 이 메시지에서 배경을 마음대로 바꿀수 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    HDC hdc;
    int i,j;
    RECT rt;
 
    switch(iMessage) {
    case WM_ERASEBKGND:
        hdc = (HDC)wParam;
        GetClientRect(hWnd, &rt);
        for (i=0; i<rt.right; i+=50) {
            for (j=0; j<rt.bottom; j+=50) {
                Rectangle(hdc, i, j, i+50, j+50);
                Ellipse(hdc, i, j, i+50, j+50);
            }
        }
        return TRUE;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
 
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs

 

상기 코드는 배경을 원과 사각형으로 채운 코드이며 하기와 같이 비트맵 이미지로 배경을 채울 수도 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    HDC hdc, MemDC;
    PAINTSTRUCT ps;
    static HBITMAP MyBitmap;
    HBITMAP OldBitmap;
    int x,y;
    RECT rt;
    BITMAP bit;
    static int ex, ey;
 
    switch(iMessage) {
    case WM_CREATE:
        MyBitmap = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP1));
        ex = 100;
        ex = 100;
        return 0;
    case WM_LBUTTONDOWN:
        ex = LOWORD(lParam);
        ey = HIWORD(lParam);
        InvalidateRect(hWnd, NULL, TRUE);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        Ellipse(hdc, ex-30, ey-30, ex+30, ey+30);
        EndPaint(hWnd, &ps);
        return 0;
    case WM_ERASEBKGND:
        hdc = (HDC)wParam;
        MemDC = CreateCompatibleDC(hdc);
        OldBitmap = (HBITMAP)SelectObject(MemDC,MyBitmap);
        GetObject(MyBitmap, sizeof(BITMAP), &bit);
        GetClientRect(hWnd, &rt);
        for (x=0; x<rt.right; x+=bit.bmWidth) {
            for (y=0; y<rt.bottom; y+=bit.bmHeight) {
                BitBlt(hdc, x, y, x+bit.bmWidth, y+bit.bmHeight, MemDC, 00, SRCCOPY);
            }
        }
 
        SelectObject(MemDC, OldBitmap);
        DeleteDC(MemDC);
        return TRUE;
    case WM_DESTROY:
        DeleteObject(MyBitmap);
        PostQuitMessage(0);
        return 0;
    }
 
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs

 

상기 코드는 비트맵을 배경에 연속적으로 채운 후 마우스 클릭 시 타원을 그리는 코드이다.

배경이 비트맵으로 채워졌으며 윈도우의 크기를 변경해도 배경은 전혀 깜박거리지 않는다.

비트맵 자체가 배경이므로 타원의 위치가 바뀔 때 원래 자리에 비트맵으로 덮어 쓰며 타원은 새로운 위치에 그려진다.

 

또한, 이 메시지를 처리할 때는 반드시 wParam으로 전달되는 DC를 얻어서 사용해야 하며 DC를 해제할 필요도 없다.

만약 하기와 같이 GetDC를 얻어서 처리한다면....

1
2
3
4
    case WM_ERASEBKGND:
        hdc = GetDC(hWnd);
        //비트맵 출력
        ReleaseDC(hWnd);
cs

 

윈도우를 움직여 타원이 화면 밖으로 이동했다가 다시 돌아왔을 때 타원이 사라져 있을 것이다.

 

GetDC로 얻은 DC는 클리핑 영역이 설정되어 있지 않아 화면 어느 곳이나 그릴 수 있으며 따라서 원이 배경에 의해 덮여 버린다.

(화면 밖에 나가져 있는 부분까지 비트맵 이미지로 전부 덮어버린다는 소리임.)

게다가 WM_PAINT는 클리핑 영역 안쪽만 복구하므로 원이 다시 그려지지 않는 것이다.

(화면에 보여지는 부분에서만 다시 그린다는 소리임)

결국, GetDC로 얻은 DC는 화면 밖으로 나간 부분까지 비트맵으로 덮어버리고 WM_PAINT는 화면에 보여지는 부분만 그리기 때문에

밖으로 나간 원은 다시 그려지지 않는 것이다.

 

WM_ERASEBKGND의 wParam으로 전달되는 DC는 BeginPaint가 클리핑 영역을 계산해 놓은 DC라 화면에 보여지는 부분만 비트맵

이미지로 덮어버리기 때문에 화면 밖에 나가져 있는 부분은 비트맵 이미지로 덮이지 않아 기존 상태 그대로 유지될 수 있는 것이다.

+ Recent posts