. 선택중 자동 스크롤

선택이란 문서 전체에서 작업 대상이 될 범위를 지정하는 행위이며 현재 화면에 보이는 부분만을 대상으로 하지 않는다. 그래서 화면에 당장 보이지 않는 부분도 스크롤해가며 선택할 수 있도록 해야 한다. 선택 중에 커서가 작업영역 밖으로 벗어나면 해당 방향으로 문서를 스크롤시키면서 선택영역을 계속 확장한다. 만약 이렇게 하지 않는다면 화면보다 더 넓은 범위는 마우스로 선택할 방법이 없다.

이 처리에 한 가지 문제가 있는데 아무리 커서를 캡처했더라도 마우스가 가만히 있으면 WM_MOUSEMOVE메시지가 전달되지 않는다는 점이다. 조금이라도 작업영역을 벗어났으면 마우스가 움직이지 않더라도 계속 그 방향으로 스크롤을 해야 하는데 이 처리를 위해 타이머가 필요하다. 마우스가 작업영역을 벗어나는 즉시 타이머를 설치하고 OnTimer에서 마우스 메시지를 계속 보내 주도록 하면 자동으로 스크롤될 것이다. OnMouseMove에 다음 코드를 추가한다.

 

void OnMouseMove(HWND hWnd, int x, int y, UINT keyFlags)

{

    BOOL bInstTimer;

    RECT crt;

    int r,c;

    int s,e;

 

     if (bCapture == FALSE) {

          return;

     }

 

     off=SelEnd=GetOffFromXY(x+xPos,y+yPos);

     SetCaret();

     InvalidateRect(hWnd,NULL,TRUE);

 

    bInstTimer=FALSE;

    GetClientRect(hWnd,&crt);

    if (y>crt.bottom) {

        SendMessage(hWnd, WM_VSCROLL, SB_LINEDOWN, 0L);

        bInstTimer=TRUE;

    }

    if (y <0) {

        SendMessage(hWnd, WM_VSCROLL, SB_LINEUP, 0L);

        bInstTimer=TRUE;

    }

    if (nWrap == 0) {

        GetRCFromOff(SelEnd,r,c);

        GetLine(r,s,e);

        if (x>crt.right && SelEnd != e) {

            SendMessage(hWnd, WM_HSCROLL, SB_LINERIGHT, 0L);

            bInstTimer=TRUE;

        }

        if (x<0 && SelEnd != s) {

            SendMessage(hWnd, WM_HSCROLL, SB_LINELEFT, 0L);

            bInstTimer=TRUE;

        }

    }

    if (bInstTimer==TRUE) {

        SetTimer(hWnd, 1, 100, NULL);

    } else {

        KillTimer(hWnd, 1);

    }

}

 

대표적으로 첫 번째 if문만 분석해보자. y 좌표가 crt.bottom보다 더 크다는 조건은 곧 커서가 작업영역의 아래쪽으로 내려갔다는 뜻이다. 이때 SB_LINEDOWN 메시지를 보내 밑으로 한 칸 스크롤되도록 하여 보이지 않는 부분까지 선택할 수 있도록 한다. 그리고 bInstTimer TRUE로 만들어 0.1초 간격의 타이머를 설치한다. OnTimer에서는 현재 커서 위치를 조사한 후 WM_MOUSEMOVE 메시지를 반복적으로 보내주는 일을 한다.

 

void OnTimer(HWND hWnd, UINT id)

{

     POINT pt;

 

     switch (id) {

     case 1:

          GetCursorPos(&pt);

          ScreenToClient(hWnd, &pt);

          SendMessage(hWnd, WM_MOUSEMOVE, 0, MAKELPARAM(pt.x, pt.y));

          break;

     }

}

 

이렇게 되면 1초에 열 번씩 WM_MOUSEMOVE 메시지가 발생하며 계속 한 줄씩 아래로 스크롤시키면서 선택을 확장한다. 그러다가 커서가 다시 작업영역 안으로 들어오면 즉시 타이머를 제거하여 스크롤을 중지하도록 하였다. 위쪽으로의 자동 스크롤도 방향만 다를 뿐 논리는 동일하다.

수평으로 자동 스크롤은 약간의 조건이 더 필요하다. 일단 수평으로 자동 스크롤이 가능하려면 자동개행 상태가 아니어야 한다. 자동개행 상태에서는 수평스크롤 자체가 불필요하다. 또한 선택영역이 줄의 끝에 있을 때는 더 이상 오른쪽으로 스크롤할 수 없으며 줄의 처음에 있을 때는 더 이상 왼쪽으로 스크롤할 수 없다. SelEnd가 속한 줄의 오프셋 범위 s e를 구하고 SelEnd와 이 범위를 비교해 본 후 스크롤을 할 수 있는 상황일 때만 스크롤 메시지를 보내 주었다.

수평 스크롤 범위는 항상 모든 줄의 길이보다 더 길게 설정되어 있다. 이 조건 점검을 하지 않으면 줄 끝을 지나쳐서 오른쪽으로 스크롤되어 캐럿이 화면 왼쪽으로 사라질 것이고 다음 번 OnMouseMove 호출에서 SetCaret은 캐럿이 있는 곳으로 강제 스크롤을 하게 된다. 결국 이대로 내버려 두면 선택영역은 변화가 없는데 캐럿이 보였다가 숨었다가를 계속 반복하게 될 것이다. 자동 스크롤 코드는 오른쪽으로 계속 스크롤하려고 할 것이고 SetCaret은 캐럿이 보이도록 왼쪽으로 계속 스크롤하려고 하기 때문에 보기에 무척 좋지 않다. 버튼을 놓을 때는 타이머를 해제한다.

 

void OnLButtonUp(HWND hWnd, int x, int y, UINT keyFlags)

{

     bCapture=FALSE;

     ReleaseCapture();

    KillTimer(hWnd,1);

}

 

선택 과정에서 타이머를 반복적으로 설치 및 제거하고 있는데 그래도 상관없다. 이미 설치되어 있는 타이머를 같은 ID로 또 설치하면 타이머 주기가 리셋될 뿐 새로운 타이머가 설치되는 것은 아니므로 자원의 낭비는 없다. 또 타이머가 설치되어 있지 않은 상태에서 KillTimer를 호출하더라도 에러가 리턴될 뿐 별다른 문제를 일으키지는 않으므로 KillTimer를 호출하기 전에 타이머가 설치되어 있었는지 따위는 점검해보지 않아도 된다.