. 스크롤바 추가

스크롤(Scroll)이란 문서의 보이지 않는 부분으로 이동하는 동작이다. 문서가 저장되는 메모리는 굉장히 넓은 영인인데 비해 이 문서를 보여주는 화면은 상대적으로 좁아서 문서 전체를 한 화면에 보여줄 수가 없다. 그래서 편집기는 전체 문서 중 지금 편집하고 있는 일부분만 보여주되 대신 스크롤 기능을 제공하여 문서의 다른 부분으로 이동할 수 있도록 한다.

스크롤이 왜 필요하고 기술적으로 어떻게 구현하는지에 대해서는 따로 설명하지 않을 계획이다. 왜냐하면 스크롤은 윈도우즈 프로그래밍 토픽 중 비교적 쉬운 부분에 속하며 이 책의 독자들은 이미 한 번쯤은 스크롤 프로그래밍을 해 봤을 것이기 때문이다. 만약 그렇지 않다면 API 정복 16장의 내용을 참조하기 바라며 CD-ROM에도 16장의 원고가 포함되어 있다. 여기에 나오는 스크롤 코드 중 일부는 API 정복 16장의 코드를 그대로 가져와 사용하고 있으므로 16장의 예제를 이해한다면 이 장의 코드는 아주 쉬울 것이다.

이왕이면 스크롤에 대한 상세한 이론을 살펴보고 관련 예제까지 만들어본 후에 실습을 진행하면 좋겠으나 그렇게 할 수가 없다. 왜냐하면 독자들간에도 편차가 있어 적당한 수준에서 절제를 하지 않을 수가 없기 때문이다. 지나치게 상세하게 다루다 보면 지면이 낭비됨은 물론이고 대부분의 독자들에게는 이것이 오히려 불만 사항이 될 수도 있어 실전이라는 제목에 걸맞게 실습을 진행하고자 한다. 내가 모르는 모든 것을 다 알려주는 입맛에 맞는 그런 책은 존재할 수가 없으니 수고스럽겠지만 이 책의 부족한 부분은 스스로 찾아서 공부하기를 간절히 부탁하는 바이다.

, 그럼 이제 프로젝트를 작성해보자. ApiEdit2의 사본을 만들어 ApiEdit3 프로젝트를 새로 만들고 OnCreate에 있는 임시 코드를 다음과 같이 수정한다. 스크롤 테스트를 원활하게 해보려면 좀 더 긴 테스트 문장이 필요하다.

 

BOOL OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct)

{

     ....

     buf=(TCHAR *)malloc(65536);

     memset(buf,0,65536);

     lstrcpy(buf,"이 문장은 스크롤 기능 테스트를 위해 작성한 것이며"

          "아래 위 이동을 충분히 테스트할만큼의 길이를 가지고 있다.\r\n"

          "Line 1\r\nLine 2\r\nLine 3\r\nLine 4\r\nLine 5\r\nLine 6\r\n"

          "Line 7\r\nLine 8\r\nLine 9\r\nLine 10\r\nLine 11\r\nLine 12\r\n"

          "Line 13\r\nLine 14\r\nLine 15\r\nLine 16\r\nLine 17\r\nLine 18\r\n"

          "동해물과 백두산이 마르고 닳도록 하느님이 보우하사 우리나라 만세\r\n"

          "무궁화 삼천리 화려 강산 대한 사람 대한으로 길이 보전하세");

 

스크롤에 필요한 전역변수는 다음 다섯 개이다.

 

int yPos,xPos;

int yMax,xMax;

int FontWidth;

 

yMax, xMax는 각각 수직, 수평스크롤 범위값이며 yPos, xPos는 현재 스크롤 위치값이다. 이 중 특히 수직스크롤 위치값인 yPos는 프로그램 전반에 걸쳐 현재 편집중인 부분을 가리키는 아주 중요한 역할을 하며 많은 함수에서 이 값을 참조한다. FontWidth는 현재 DC에 선택된 글꼴의 평균폭이며 수평스크롤 범위 설정에 필요하다. 이 변수들을 OnCreate에서 초기화한다.

 

BOOL OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct)

{

     ....

     hdc=GetDC(hWnd);

     GetTextMetrics(hdc,&tm);

     FontHeight=tm.tmHeight;

    FontWidth=tm.tmAveCharWidth;

     LineRatio=120;

     LineHeight=int(FontHeight*LineRatio/100);

     ReleaseDC(hWnd,hdc);

     nWrap=2;

     PrevX=0;

     bNoFirstSpace=FALSE;

     bLineEnd=FALSE;

    xMax=1024;

    xPos=0;

    yPos=0;

 

     return TRUE;

}

 

최초 스크롤되지 않은 상태로 시작하므로 현재 스크롤 위치는 좌상단인 (0,0)이다. xMax는 일단 1024로 대충 초기화했는데 자동개행 옵션을 해제할 때 다시 정확한 값을 찾게 되며 yMax는 여기서 초기화하지 않고 별도의 함수에서 정확하게 계산할 것이다. 문자의 평균폭 FontWidth TEXTMETRIC tmAveCharWidth 멤버를 대입하면 된다.

WinMain에서 메인 윈도우에 수직 스크롤바를 달아 준다. 윈도우 스타일에 WS_VSCROLL 스타일을 추가하도록 하자. 통상 문서의 길이는 화면의 길이보다 길기 때문에 수직 스크롤바는 항상 화면 오른쪽에 배치하되 스크롤할 수 없을 때는 사용 금지시킨다.

 

     hWnd=CreateWindow(lpszClass,lpszClass,WS_OVERLAPPEDWINDOW | WS_VSCROLL,

          CW_USEDEFAULT,CW_USEDEFAULT,300,200,

          NULL,(HMENU)NULL,hInstance,NULL);

 

자동개행 상태에서는 수평으로 스크롤되지 않으므로 당장 수평 스크롤바를 달아줄 필요가 없다. 대신 nWrap 변수가 변경될 때 윈도우의 스타일을 바꿔주면 된다. nWrap 변수는 SetWrap에서 변경하므로 여기에 코드를 작성하도록 하자.

 

void SetWrap(int aWrap)

{

    DWORD dwStyle;

 

     nWrap=aWrap;

    dwStyle=GetWindowLong(hWndMain,GWL_STYLE);

    if (nWrap) {

        dwStyle = dwStyle & ~WS_HSCROLL;

        xPos=0;

    } else {

        dwStyle = dwStyle | WS_HSCROLL;

    }

    SetWindowLong(hWndMain,GWL_STYLE,dwStyle);

    SetWindowPos(hWndMain,HWND_NOTOPMOST,0,0,0,0,

        SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);

     InvalidateRect(hWndMain,NULL,TRUE);

     SetCaret();

}

 

nWrap 0이 아니면 윈도우 스타일에 WS_HSCROLL 스타일을 추가하고 그렇지 않으면 이 스타일을 빼 준다. 비작업영역을 다시 그리도록 하기 위해서는 SetWindowPos 함수를 SWP_FRAMECHANGED 플래그와 함께 호출해야 한다. 이 함수는 윈도우의 위치와 크기를 변경하는데 윈도우의 스타일이 바뀔 때 프레임을 다시 그리는 역할도 한다. 수평 스크롤바가 사라질 때는 수평스크롤 값인 xPos 0으로 초기화했다. 그렇지 않으면 수평스크롤된 상태에서 정렬 상태를 바꿀 때 수평 스크롤바도 없는데 xPos만 오른쪽으로 이동해 있는 불일치가 발생한다. 실행중에 F9로 자동개행 상태를 변경하면 작업영역 아래쪽에 수평 스크롤바가 나타날 것이다.