. 무효영역 최소화

이 프로그램은 삽입, 삭제와 같은 버퍼의 변화는 물론이고 윈도우의 크기 변화, 포커스 변화, 선택의 변화 등 약간의 변화에 대해서도 항상 InvalidateRect(hWnd,NULL,TRUE)를 호출함으로써 작업영역 전체를 무효화시킨다. 이렇게 해도 더블 버퍼링을 제대로 하고 있기 때문에 깜박거린다거나 심하게 느려지지는 않는다. 하지만 다시 그릴 필요가 없는 영역까지 몽땅 재출력함으로써 실행시간을 낭비하고 있는 것은 분명하다.

전체를 다시 그리는 시간이 사람의 조작 속도보다 빠르기만 하다면 별 문제가 없으며 현재의 컴퓨터 속도상으로 넉넉하다. 하지만 프로그램을 사용할 수 있는 컴퓨터 사양은 낮을수록 좋은 것이고 또 앞으로의 기능 확장을 고려하여 좀 더 속도를 높일 수 있는 방법이 있다면 힘닿는 데까지 최적화를 해놓는 것이 좋다.

항상 작업영역 전체를 무효화하지 말고 꼭 필요한 부분만 다시 그리도록 하면 많은 속도 향상을 기대할 수 있다. 무효한 영역만 그리기 위해서는 먼저 OnPaint가 작업영역에 대해서 그리기를 할 것이 아니라 무효영역을 기준으로 그리기를 해야 한다. , 무효영역을 인식하도록 수정해야 한다.

 

void OnPaint(HWND hWnd)

{

     HDC hdc, hMemDC;

     PAINTSTRUCT ps;

     int l;

     RECT crt;

    int s,e,t;

     HBRUSH hBrush;

     HBITMAP OldBitmap;

     RECT lrt;

 

     hdc=BeginPaint(hWnd,&ps);

     GetClientRect(hWnd,&crt);

 

    t=yPos/LineHeight;

    s=(yPos+ps.rcPaint.top)/LineHeight;

    e=(yPos+ps.rcPaint.bottom-1)/LineHeight;

     e=min(e,TotalLine-1);

 

     hMemDC=CreateCompatibleDC(hdc);

     if (hBit == NULL) {

          hBit=CreateCompatibleBitmap(hdc,crt.right,LineHeight);

     }

     OldBitmap=(HBITMAP)SelectObject(hMemDC,hBit);

 

     hBrush=GetSysColorBrush(COLOR_WINDOW);

     SetRect(&lrt,0,0,crt.right,LineHeight);

 

     for (l=s;l<=e;l++) {

          FillRect(hMemDC,&lrt,hBrush);

          DrawLine(hMemDC,l);

        BitBlt(hdc,0,(l-t)*LineHeight,crt.right,(l-t)*LineHeight+LineHeight,

            hMemDC,0,0,SRCCOPY);

     }

 

    SetRect(&lrt,0,(l-t)*LineHeight,crt.right,crt.bottom);

     FillRect(hdc,&lrt,hBrush);

 

     DeleteObject(hBrush);

     SelectObject(hMemDC,OldBitmap);

     DeleteDC(hMemDC);

     EndPaint(hWnd,&ps);

}

 

출력할 범위인 s,e를 계산하는 식이 작업영역을 기준으로 하지 않고 클리핑영역인 rcPaint를 기준으로 한다. 클리핑영역이란 다시 그려져야 할 무효영역 중에서 보이는 가시 영역인데 이 영역에 속해 있는 줄만 다시 그리면 된다. 출력 시작줄 s는 클리핑영역의 위쪽 좌표를 줄간으로 나누면 구할 수 있다. e는 클리핑영역의 끝점에서 1을 빼 준 후 줄간으로 나누었는데 여기서 1을 빼 주는 이유는 무효영역의 끝 좌표를 제외하기 위해서이다.

예를 들어 24~48까지 무효영역으로 지정했다고 하자. 이 범위는 두 번째 줄의 수직좌표와 동일한데 줄간 24로 각각 나누면 출력 범위는 1~2가 된다. 이때 2번째 줄은 출력할 필요가 없는데도 범위에 들게 되므로 48에서 1을 빼준 47 24로 나눔으로써 1번째 줄만 출력하도록 한다.

각 줄의 출력위치는 화면상의 몇 번째 줄인가를 계산하여 구했는데 이제 s가 더 이상 화면상의 첫 줄이 아니므로 l-s식으로는 몇 번째 줄인지 알 수가 없게 되었다. 그래서 화면상의 첫 줄번호를 구하기 위해 t라는 변수가 새로 선언되었으면 출력되는 좌표는 l-t로 계산된다.

OnPaint는 무효영역 내부만 다시 그리도록 수정되었으므로 이제 문서를 변경하는 모든 곳에서 꼭 필요한 부분만 무효화시켜야 한다. 그런데 문서편집코드들은 화면상의 픽셀 좌표를 다루는 것이 아니라 오프셋을 주된 작업 대상으로 하는데다가 OnPaint는 줄단위로 출력하므로 오프셋으로부터 줄번호를 먼저 조사해야 하는 번거로움이 있다. 그래서 무효화만 전문적으로 수행하는 별도의 유틸리티 함수를 작성하였다.

 

void Invalidate(int Pos1, int Pos2/*=-1*/)

{

     RECT rt,crt;

     int x,y,y1,y2;

 

     if (Pos1 == -1) {

          InvalidateRect(hWndMain,NULL,FALSE);

          return;

     }

 

     GetClientRect(hWndMain,&crt);

     GetXYFromOff(Pos1,x,y);

     y1=y-yPos;

 

     if (Pos2 == -1) {

          y2=crt.bottom;

     } else {

          GetXYFromOff(Pos2,x,y);

          y2=y-yPos+LineHeight;

     }

 

     SetRect(&rt,0,y1,crt.right,y2);

     InvalidateRect(hWndMain,&rt,FALSE);

}

 

이 함수는 두 개의 오프셋 Pos1 Pos2를 입력받아 두 오프셋이 속해 있는 줄들을 모두 무효화한다. 이때 두 오프셋은 반드시 크기순으로 정렬되어 있어야 한다. , Pos1은 항상 Pos2보다 작은 값이어야 하며 그래야 무효영역을 쉽게 구할 수 있다. Pos2는 디폴트 인수 -1로 정의되어 있는데 이 인수가 -1이면 작업영역의 끝까지 무효화한다. 만약 Pos1 -1이면 작업영역 전체를 무효화한다. 이 함수의 호출 예는 다음과 같다.

 

Invalidate(1234);           // 1234가 속한 줄 이후 화면 끝까지 무효화

Invalidate(1234,5678);        // 1234가 속한 줄 ~ 5678이 속한 줄까지 무효화

Invalidate(-1);                  // 작업영역 전체 무효화

 

이 함수는 전달받은 오프셋으로부터 문서상의 픽셀 좌표를 계산하고 스크롤 값을 빼 화면상의 픽셀 좌표를 만든다. 그리고 두 범위 사이에 속한 줄들을 모두 무효화한다. 이때 InvalidateRect 함수의 세 번째 인수는 FALSE로 주었는데 어차피 배경 브러시가 없는 현재 이 인수는 TRUE, FALSE간의 차이가 전혀 없다. 배경 브러시가 없으므로 배경은 지워지지 않으며 그래서 이 인수는 FALSE로 두는 것이 논리적으로 더 맞는 것 같다.

만약 Pos1 Pos2가 속한 줄이 화면 영역의 밖이면 무효영역이 작업영역보다 더 커질 수도 있다. 선택영역은 화면 영역보다 더 커질 수 있기 때문에 이것은 충분히 가능하며 이렇게 되면 출력 범위가 좁아지는 것이 아니라 오히려 넓어질 것이다. 그래서 Pos1 Pos2로 구한 y1 y2는 다음처럼 한계 점검을 해야 한다.

 

y1=max(y1,0);

y2=min(y2,crt.bottom);

 

하지만 이 처리를 하지 않은 이유는 InvalidateRect 함수가 작업영역보다 더 큰 무효영역을 설정해도 문제가 전혀 없기 때문이다. OnPaint에서 최종적으로 계산되는 클리핑영역은 무효영역 중에서 가시 영역으로 제한되기 때문에 클리핑영역이 작업영역보다 더 커지는 일은 없다. 이 함수는 오프셋을 전달받아 오프셋이 속한 줄을 무효화하므로 문서를 변경하는 각 루틴에서 호출하게 될 것이다.