. 블록 표시 방법

ApiEdit4의 선택영역 표시 방법은 아주 원론적이다. 마우스나 키보드로 SelStart SelEnd값을 적절히 변경하고 선택영역에 조금이라도 변화가 있으면 작업영역 전체를 무효화하여 변경된 블록을 화면에 다시 그리도록 한다. 선택을 하는 코드들은 오로지 SelStart SelEnd값을 조작하는 데만 관심을 가지며 블록을 표시하는 코드는 이 두 값을 참조하여 화면에 선택 블록을 출력하기만 한다.

어떻게 보면 당연한 것 같지만 이 방법은 아주 원시적인 방법이다. 선택영역에 조금만 변화가 생겨도 화면을 통째로 다시 그리는 비효율적인 방법을 사용하고 있는 것이다. 선택을 하고 블록을 표시하는 방법을 바꾸면 이 방법보다는 훨씬 더 고속으로 동작하는 선택 코드를 작성할 수 있다. 앞에서 실습해본 선택 방법과는 질적으로 다른 방법인데 이 방법도 알아 두면 응용할 수 있는 곳이 많으므로 구경해보도록 하자.

실습을 위해 ApiEdit3 프로젝트를 복사하여 ApiEdit4a 프로젝트를 새로 작성한다. OnCreate에서 샘플 텍스트로 버퍼를 초기화하는 것과 GetOffFromXY 함수, 그리고 선택 전역변수를 선언하고 초기화하는 데까지는 ApiEdit4와 같다. 그러나 선택을 하는 방법과 선택영역을 보여주는 방법은 달라진다. 시간이 없거나 별로 필요를 느끼지 못한다면 이 실습은 하지 않아도 좋다. 어디까지나 이런 식으로도 선택을 할 수 있다는 것을 보여주고자 하는 것뿐이다.

먼저 선택이 이미 되어 있는 상황에서 이 영역을 화면에 보여주는 방법부터 연구해보자. 화면을 그릴 때는 선택영역을 고려하지 않고 완전히 다 그린 후 선택영역만 반전시켜 보여주면 된다. 그러기 위해서는 선택영역으로부터 반전시킬 영역을 구해야 하는데 선택영역이란 사각형 영역이 아니라 다각형이므로 RECT 구조체로는 이 영역을 표현할 수 없다. 다각형이나 곡면 또는 떨어져 있는 영역을 표현하려면 리전을 사용해야 한다. 다음 함수는 선택의 시작점 Sel1과 끝점 Sel2를 인수로 전달받아 이 선택영역을 표현하는 리전 객체를 만들어준다.

 

void MakeSelRgn(int Sel1, int Sel2, HRGN &Rgn)

{

     RECT rt1, rt2, rt3;

     HRGN rgn1, rgn2, rgn3;

     int x1,y1,x2,y2;

     int SelFirst, SelSecond;

     RECT crt;

     BOOL OldbLineEnd;

 

     GetClientRect(hWndMain,&crt);

     SelFirst=min(Sel1,Sel2);

     SelSecond=max(Sel1,Sel2);

 

     OldbLineEnd=bLineEnd;

     bLineEnd=TRUE;

     GetXYFromOff(SelFirst,x1,y1);

     GetXYFromOff(SelSecond,x2,y2);

     bLineEnd=OldbLineEnd;

     x1-=xPos;

     y1-=yPos;

     x2-=xPos;

     y2-=yPos;

 

     SetRect(&rt1,0,0,0,0);

     SetRect(&rt2,0,0,0,0);

     SetRect(&rt3,0,0,0,0);

 

     if (SelFirst != SelSecond && y1==y2) {

          SetRect(&rt1,x1,y1,x2,y2+LineHeight);

     }

 

     if (y2-y1 >= LineHeight) {

          SetRect(&rt1,x1,y1,crt.right,y1+LineHeight);

          SetRect(&rt3,crt.left,y2,x2,y2+LineHeight);

     }

 

     if (y2-y1 >= LineHeight*2) {

          SetRect(&rt2,crt.left,y1+LineHeight,crt.right,y2);

     }

 

     rgn1=CreateRectRgnIndirect(&rt1);

     rgn2=CreateRectRgnIndirect(&rt2);

     rgn3=CreateRectRgnIndirect(&rt3);

     Rgn=CreateRectRgn(0,0,0,0);

     CombineRgn(Rgn,rgn1,rgn2,RGN_OR);

     CombineRgn(Rgn,Rgn,rgn3,RGN_OR);

     DeleteObject(rgn1);

     DeleteObject(rgn2);

     DeleteObject(rgn3);

}

 

선택영역의 두 지점인 Sel1 Sel2 SelFirst SelSecond로 정규화되며 이 두 지점의 좌표는 x1, y1, x2, y2에 각각 조사된다. 이때는 자동개행된 줄의 처음과 끝을 굳이 구분할 필요가 없으므로 bLineEnd는 무조건 TRUE(FALSE로 해도 비슷하다)로 하여 좌표를 계산했다. 이 좌표와 선택영역의 범위에 따라 선택 리전이 만들어진다. 선택영역은 한 줄, 두 줄, 세 줄 이상인 경우로 각각 분류할 수 있다.

한 줄만 선택되어 있는 경우 선택영역은 사각 영역 하나로 표현된다. 두 줄이 선택되어 있으면 윗줄과 아래줄 두 개의 사각 영역으로 표현된다. 세 줄 이상인 경우는 선택 시작점이 있는 줄의 사각형과 선택 끝점이 있는 줄의 사각형, 그리고 그 사이에 있는 사각형 셋이 모여야 선택영역을 제대로 표현할 수 있다.

MakeSelRgn 함수는 세 개의 사각영역 rt1, rt2, rt3를 선언하고 선택 유형에 따라 블록을 구성하는 작은 사각 영역을 구한다. 그리고 이렇게 구해진 세 개의 사각영역을 모두 합쳐 하나의 리전으로 만들면 이 리전이 바로 선택영역이 되는 것이다. SelStart SelEnd로 선택 리전을 만들어 화면에 표시하는 일은 다음 함수가 한다.

 

void InvertSelect(HDC hdc)

{

     HRGN SelRgn;

     MakeSelRgn(SelStart,SelEnd,SelRgn);

     InvertRgn(hdc,SelRgn);

     DeleteObject(SelRgn);

}

 

MakeSelRgn 함수로 SelStart SelEnd 사이의 리전을 구한 후 InvertRgn API 함수를 호출하여 이 영역을 반전시키는 것이다. OnPaint에서는 모든 텍스트를 다 출력한 후에 이 함수를 호출하기만 하면 된다.

 

void OnPaint(HWND hWnd)

{

     ....

    if (SelStart != SelEnd) {

        InvertSelect(hdc);

    }

     EndPaint(hWnd,&ps);

}

 

텍스트를 출력할 때는 선택영역을 전혀 신경쓰지 않으며 텍스트가 정상적으로 출력된 후 InvertSelect 함수를 호출하면 선택영역이 반전되어 보일 것이다.