Уроки программирования на C/C++ под Windows 95


Улучшенная прокрутка


Вы наверное заметили, что вертикальная прокрутка, организованная в предыдущей программе, не очень эффективна. На данном уроке мы улучшим вертикальную прокрутку и добавим горизонтальную.
Вот пример программы:


//файл sm.h остался прежним, поэтому он здесь не приводится



//файл main.cpp

#include <windows.h>

#include "sm.h"



//описание оконной процедуры

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);



//Это главная функция программы.

int WINAPI WinMain(HINSTANCE hInst,

                   HINSTANCE hPrevInst,

                   PSTR szCmdLine,

                   int iCmdShow)

{

  HWND hwnd;

  MSG  msg;

  WNDCLASSEX w;

  static CHAR *szAppName={"TextOutExample"};

  w.cbSize=sizeof(w);

  w.style=CS_HREDRAW|CS_VREDRAW;

  w.lpfnWndProc=WndProc;

  w.cbClsExtra=0;

  w.cbWndExtra=0;

  w.hInstance=hInst;

  w.hIcon=LoadIcon(NULL,IDI_APPLICATION);

  w.hCursor=LoadCursor(NULL,IDC_ARROW);

  w.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);

  w.lpszMenuName=NULL;

  w.lpszClassName=szAppName;

  w.hIconSm=w.hIcon;

  RegisterClassEx(&w);



  hwnd=CreateWindow(

       szAppName,

       "System metrics",

       WS_OVERLAPPEDWINDOW|WS_VSCROLL,

       CW_USEDEFAULT, CW_USEDEFAULT,

       CW_USEDEFAULT, CW_USEDEFAULT,

       NULL,

       NULL,

       hInst,

       NULL);

  ShowWindow(hwnd,iCmdShow);

  UpdateWindow(hwnd);



  while(GetMessage(&msg,NULL,0,0))

  {

    TranslateMessage(&msg);

    DispatchMessage(&msg);

  }

  return msg.wParam;

}



//Оконная процедура

LRESULT CALLBACK WndProc(HWND hwnd, UINT imsg,

                         WPARAM wParam, LPARAM lParam)

{

  PAINTSTRUCT ps;

  HDC         hdc;

  TEXTMETRIC  tm;

  static int  cxChar,cyChar,iVscrollPos=0,iVscrollMax,cyClient,cxClient,

              iMaxWidth,iHscrollMax,iHscrollPos;

  int         iPaintBeg,iPaintEnd,iVscrollInc,iHscrollInc,i,x,y;

  CHAR        szBuffer[10];



  switch(imsg)

  {

    case WM_CREATE:

         hdc=GetDC(hwnd);

         GetTextMetrics(hdc,&tm);

         cxChar=tm.tmAveCharWidth;

         cyChar=tm.tmHeight+tm.tmExternalLeading;

         iMaxWidth=120*cxChar;

         ReleaseDC(hwnd,hdc);

         return 0;



    case WM_SIZE:

         cxClient=LOWORD(lParam);

         cyClient=HIWORD(lParam);



         iVscrollMax=max(0, N_LINES+2-cyClient/cyChar);

         iVscrollPos=min(iVscrollPos, iVscrollMax);

         SetScrollRange(hwnd,SB_VERT,0,iVscrollMax,FALSE);

         SetScrollPos(hwnd,SB_VERT,iVscrollPos,TRUE);



         iHscrollMax=max(0, 2+(iMaxWidth-cxClient)/cxChar);

         iHscrollPos=min(iHscrollPos,iHscrollMax);

         SetScrollRange(hwnd,SB_HORZ,0,iHscrollMax,FALSE);

         SetScrollPos(hwnd,SB_HORZ,iHscrollPos,TRUE);

         return 0;



    case WM_PAINT:

         hdc=BeginPaint(hwnd, &ps);

         iPaintBeg=max(0,iVscrollPos+ps.rcPaint.top/cyChar-1);

         iPaintEnd=min(N_LINES,iVscrollPos+ps.rcPaint.bottom/cyChar);



         for(i=iPaintBeg; i<iPaintEnd; i++)

         {

           x=cxChar*(1-iHscrollPos);

           y=cyChar*(1-iVscrollPos+i);

           TextOut(hdc,x,y,sm[i].szLabel,

                   lstrlen(sm[i].szLabel));

           wsprintf(szBuffer,"%5d",

                    GetSystemMetrics(sm[i].i));

           TextOut(hdc,x+22*cxChar,y,szBuffer,

                   lstrlen(szBuffer));

           TextOut(hdc,x+24*cxChar+8*cxChar,y,

                   sm[i].szDesc,lstrlen(sm[i].szDesc));

         }

         EndPaint(hwnd, &ps);

         return 0;



    case WM_VSCROLL:

         switch(LOWORD(wParam))

         {

           case SB_TOP:

                iVscrollInc = -iVscrollPos; break;

           case SB_BOTTOM:

                iVscrollInc = iVscrollMax-iVscrollPos; break;

           case SB_LINEUP:

                iVscrollInc = -1;

                break;

           case SB_LINEDOWN:

                iVscrollInc = 1;

                break;

           case SB_PAGEUP:

                iVscrollInc = min(-1,-cyClient/cyChar);

                break;

           case SB_PAGEDOWN:

                iVscrollInc = max(1,cyClient/cyChar);

                break;

           case SB_THUMBTRACK:

                iVscrollInc=HIWORD(wParam)-iVscrollPos;

                break;

           default: iVscrollInc=0;

         }



         iVscrollInc=max(-iVscrollPos,

                         min(iVscrollInc,iVscrollMax-iVscrollPos));

         if(iVscrollInc!=0)

         {

           iVscrollPos += iVscrollInc;

           ScrollWindow(hwnd,0,-cyChar*iVscrollInc,NULL,NULL);

           SetScrollPos(hwnd,SB_VERT,iVscrollPos,TRUE);

           UpdateWindow(hwnd);

         }

         return 0;



    case WM_HSCROLL:

         switch(LOWORD(wParam))

         {

           case SB_LINEUP:

                iHscrollInc=-1; break;

           case SB_LINEDOWN:

                iHscrollInc=1; break;

           case SB_PAGEUP:

                iHscrollInc=-8; break;

           case SB_PAGEDOWN:

                iHscrollInc=8; break;

           case SB_THUMBPOSITION:

                iHscrollInc=HIWORD(wParam)-iHscrollPos;

                break;

           default: iHscrollInc=0;

         }

         iHscrollInc=max(-iHscrollPos,

                         min(iHscrollInc,iHscrollMax-iHscrollPos));

         if(iHscrollInc!=0)

         {

           iHscrollPos+=iHscrollInc;

           ScrollWindow(hwnd,-cxChar*iHscrollInc,0,NULL,NULL);

           SetScrollPos(hwnd,SB_HORZ,iHscrollPos,TRUE);

         }

         return 0;



    case WM_DESTROY:

         PostQuitMessage(0);

         return 0;

  }



  return DefWindowProc(hwnd,imsg,wParam,lParam);

}

В этой программе есть небольшие улучшения:

  • Вы не сможете прокрутить окно так, чтобы последняя строка оказалась в самом верху. Прокрутка продолжается только до тех пор, пока появится последняя строка. Для того, чтобы сделать это, программа расчитывает диапазон полосы прокрутки и положение бегунка при обработке сообщения WM_SIZE. (Сообщение WM_SIZE посылается окну тогда, когда изменяется его размеры). Диапазон прокрутки вычисляется на основании числа строк текста (N_LINES) и размера рабочей области (cyClient).
    Если рабочая область будет достаточно велика, чтобы в ней поместились все строки, тогда минимальное и максимальное положение диапазона прокрутки будет равно нулю. Это приведет к тому, что Windows удалит полосу прокрутки, т.к. она уже больше не нужна.
    Аналогично, если ширина рабочей области будет достаточна для отображения 120 символов (см. WM_CREATE: ... iMaxWidth=120*cxChar;), то исчезнет и горизонтальная полоса прокрутки.
  • Перед тем, как прокрутить окно, расчитывается приращение текущей позиции прокрутки - iVscrollInc (для вертикальной прокрутки), iHscrollInc (для горизонтальной прокрутки). Затем выполняется прокрутка окна с помощью функции

ScrollWindow(hwnd, xInc, yInc, pRect, pClipRect);


Параметры xInc и yInc задают величину прокрутки. Параметры pRect и pClipRect устанавливаются в NULL для того, чтобы прокрутить всю рабочую область.
В результате вызова функции ScrollWindow рабочая область, открываемая после прокрутки, делается недействительной, что заставляет Windows послать окну сообщение WM_PAINT. При обработке этого сообщения перерисовываются те строки, которых не было видно до прокрутки окна (а не все строки, как в предыдущей версии программы).

  • Поскольку сообщение WM_PAINT обрабатывается быстрее, чем в предыдущей версии программы (т.к. перерисовываются не все строки, а только вновь открытые), теперь программа обрабатывает сообщение SB_THUMBTRACK вместо SB_THUMBPOSITION, т.е. теперь вертикальная прокрутка выполняется при движении ползунка, а не при отпускании левой клавиши мыши, как в предыдущей версии.

 



[   ВЕРНУТЬСЯ К ОГЛАВЛЕНИЮ   ]