Fred's Tutorial #11: Scrolling Controls In A Window

Started by Frederick J. Harris, January 20, 2009, 09:29:19 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Frederick J. Harris

Scrolling Windows Controls Instead of Text

     The past two scrolling tutorials showed how to scroll text on a Form/Dialog. But what if you have a small dialog or form that for whatever reason can't be made larger, and you need to put quite a few controls on it – and they don't all fit? Controls such as textboxes, combo boxes, or anything else for that matter can also be scrolled about within a larger 'container'. However, the technique is a bit different than with scrolling text. What works best is to create a 'pane', that is, an intermediate window that is itself a child of the main window or dialog, and upon this 'pane' place the subordinate child window controls such as text boxes or whatever. Then this pane is moved about with Windows Api calls so as to bring hidden or 'clipped' parts of the pane into view. This technique can be understood by noting the variable types of the parameters to the CreateWindowEx() and MoveWindow() calls relating to the size and location of the window. Here is the description of CreateWindowEx()...


HWND CreateWindowEx
(
  DWORD dwExStyle,      // extended window style
  LPCTSTR lpClassName,  // pointer to registered class name
  LPCTSTR lpWindowName, // pointer to window name
  DWORD dwStyle,        // window style
  int x,                // horizontal position of window
  int y,                // vertical position of window
  int nWidth,           // window width
  int nHeight,          // window height
  HWND hWndParent,      // handle to parent or owner window
  HMENU hMenu,          // handle to menu, or child-window identifier
  HINSTANCE hInstance,  // handle to application instance
  LPVOID lpParam        // pointer to window-creation data
);


     Of course, that is a C description, but in C an int is a signed 32 bit value. In all the example programs so far we have never placed negative values in x and y parameters of our function calls, but it is definitely possible to do so. Think about what would happen if you created a window whose x,y coordinates were -1000 respectively. The origin of the windows upper left corner would then be -1000, -1000. Its likely you wouldn't even see such a window because Windows can't display objects somewhere off your computer screen; however, the concept itself is at least mathematically possible.

     Scrolling controls is made possible by the MoveWindow() call however, and here is its description...


BOOL MoveWindow
(
  HWND hWnd,      // handle to window
  int X,          // horizontal position
  int Y,          // vertical position
  int nWidth,     // width
  int nHeight,    // height
  BOOL bRepaint   // repaint flag
);


     As you can see we have the same parameters as in the CreateWindowEx() call with respect to window position and size. What we do to cause the window to scroll is to move the pane containing the controls with MoveWindow(). For example, if the main window upon which scrolling will take place is 250 pixels wide by 250 pixels high, and the pane sitting upon it 250 pixels wide by 500 pixels high, then only the top half of the pane will be visible. The lower half, i.e., all pixel locations whose y value is greater than 250 – will be clipped. However, the lower half of the pane can be made visible if a MoveWindow call is made where the Y parameter is set to -250. Simple, really. Below is a simple program demonstrating this technique.


#Compile              Exe
#Include              "Win32api.inc"
%ID__PANE             = 1500             'Equate numbers as proxies
%IDC_FIRST_NAME       = 1505             'for hWnds
%IDC_MIDDLE_NAME      = 1510
%IDC_LAST_NAME        = 1515
%IDC_ADDRESS1         = 1520
%IDC_ADDRESS2         = 1525
%IDC_CITY             = 1530
%IDC_STATE            = 1535
%IDC_COUNTRY          = 1540
%IDC_ZIP              = 1545
%IDC_EMAIL            = 1550
%IDC_TELEPHONE        = 1555
%IDC_INTERESTS        = 1560
%IDC_SUBMIT           = 1565

Type WndEventArgs                        'Just collecting Window Procedure   
  wParam              As Long            'parameters into a type
  lParam              As Long
  hWnd                As Dword
  hInst               As Dword
End Type


Type MessageHandler                      'Type to associate a Window's Message with the run time address
  wMessage            As Long            'of the function that handles the message.  There are only three
  dwFnPtr             As Dword           'messages for the main window that this program handles, i.e.,
End Type                                 'WM_CREATE, WM_VSCROLL, and WM_CLOSE.  Note that there are no calls
Global MsgHdlr()      As MessageHandler  'in this program to fnPtr.  This is just a model procedure for
Declare Function fnPtr(Wea As WndEventArgs) As Long  'PowerBASIC's use in setting up the Call Dword mechanism.


Function fnPaneProc(ByVal hWnd As Long,ByVal wMsg As Long,ByVal wParam As Long,ByVal lParam As Long) As Long
  If wMsg=%WM_COMMAND And Lowrd(wParam)=%IDC_SUBMIT Then
     MsgBox("You Apparently Want To Submit The Information")  'This is the Window Procedure for the 'Pane'
  End If                                                      'window that contains labels, textboxes, and
                                                              'the button.  It is this window which 'scrolls'.
  fnPaneProc=DefWindowProc(hWnd,wMsg,wParam,lParam)
End Function           


Function fnWndProc_OnCreate(Wea As WndEventArgs) As Long
  Local pCreateStruct As CREATESTRUCT Ptr
  Local hCtrl,hPane As Dword
  Local szPane As Asciiz*8
  Local vsi As SCROLLINFO
  Local wc As WndClassEx
  Local iMsg As Long
  Local rc As RECT
   
  pCreateStruct=Wea.lParam                  'When a WM_CREATE message is received, the lParam is a pointer to a
  Wea.hInst=@pCreateStruct.hInstance        'CREATESTRUCT which contains all the parameters of the CreateWindow()
                                            'call that created the window (so you don't need global variables to
  'Set up 'Pane' class                      'maintain the state of any CreateWindow() parameters here).
  szPane="Pane"                                                         
  wc.cbSize=SizeOf(WNDCLASSEX)              'Note that an instance of this class will be created just below to
  wc.style=%CS_HREDRAW Or %CS_VREDRAW       'serve as a container for the labels, textboxes, and button.  It is
  wc.lpfnWndProc=CodePtr(fnPaneProc)        'this 'Pane' object which actually scrolls.  Further note that the
  wc.cbClsExtra=0                           'pane is a child of the main program window, and the labels, textboxes
  wc.cbWndExtra=0                           'and button are childs of the pane window.  If you examine the
  wc.hInstance=Wea.hInst                    'CreateWindow() call just below, you'll see that the height (y) of the
  wc.hIcon=0                                'pane was set to 490 pixels.  If you look down in WinMain() where the
  wc.hCursor=LoadCursor(%NULL,ByVal %IDC_ARROW)  'CreateWindow() call is located that creates the main program window,
  wc.hbrBackground=%COLOR_BTNFACE+1         'you'll see that the main program window is only 300 pixels.  Therefore,
  wc.lpszMenuName=%NULL                     'the pane is 190 pixels larger than the main program window, and is large
  wc.lpszClassName=VarPtr(szPane)           'enough to contain all the child window controls.  The child window controls
  wc.hIconSm=0                              'that are brought into view by scrolling are the result of MoveWindow()
  Call RegisterClassEx(wc)                  'calls in the Message Handler for WM_VSCROLL.
  hPane=CreateWindowEx(0,szPane,"",%WS_CHILD Or %WS_VISIBLE ,0,0,325,490,Wea.hWnd,%ID__PANE,Wea.hInst,ByVal 0)
  Call SetWindowLong(Wea.hWnd,0,hPane)      'Store hPane in cbWndExtra bytes because we'll need it in fnWndProc_OnVScroll()
 
  'Create all the child window controls on the 'Pane'
  hCtrl=CreateWindowEx(0,"static","First Name",%WS_CHILD Or %WS_VISIBLE,10,10,100,25,hPane,-1,Wea.hInst,Byval %NULL)   
  hCtrl=CreateWindowEx(%WS_EX_CLIENTEDGE,"edit","",%WS_CHILD Or %WS_VISIBLE Or %WS_BORDER,140,10,150,25,hPane,%IDC_FIRST_NAME,Wea.hInst,Byval %NULL)                                               
  hCtrl=CreateWindowEx(0,"static","Middle Name",%WS_CHILD Or %WS_VISIBLE,10,40,100,25,hPane,-1,Wea.hInst,Byval %NULL)   
  hCtrl=CreateWindowEx(%WS_EX_CLIENTEDGE,"edit","",%WS_CHILD Or %WS_VISIBLE Or %WS_BORDER,140,40,150,25,hPane,%IDC_MIDDLE_NAME,Wea.hInst,Byval %NULL)
  hCtrl=CreateWindowEx(0,"static","Last Name",%WS_CHILD Or %WS_VISIBLE,10,70,100,25,hPane,-1,Wea.hInst,Byval %NULL)   
  hCtrl=CreateWindowEx(%WS_EX_CLIENTEDGE,"edit","",%WS_CHILD Or %WS_VISIBLE Or %WS_BORDER,140,70,150,25,hPane,%IDC_LAST_NAME,Wea.hInst,Byval %NULL)
  hCtrl=CreateWindowEx(0,"static","Address1",%WS_CHILD Or %WS_VISIBLE,10,100,100,25,hPane,-1,Wea.hInst,Byval %NULL)   
  hCtrl=CreateWindowEx(%WS_EX_CLIENTEDGE,"edit","",%WS_CHILD Or %WS_VISIBLE Or %WS_BORDER,140,100,150,25,hPane,%IDC_ADDRESS1,Wea.hInst,Byval %NULL)                                               
  hCtrl=CreateWindowEx(0,"static","Address2",%WS_CHILD Or %WS_VISIBLE,10,130,100,25,hPane,-1,Wea.hInst,Byval %NULL)   
  hCtrl=CreateWindowEx(%WS_EX_CLIENTEDGE,"edit","",%WS_CHILD Or %WS_VISIBLE Or %WS_BORDER,140,130,150,25,hPane,%IDC_ADDRESS2,Wea.hInst,Byval %NULL)
  hCtrl=CreateWindowEx(0,"static","City",%WS_CHILD Or %WS_VISIBLE,10,160,100,25,hPane,-1,Wea.hInst,Byval %NULL)   
  hCtrl=CreateWindowEx(%WS_EX_CLIENTEDGE,"edit","",%WS_CHILD Or %WS_VISIBLE Or %WS_BORDER,140,160,150,25,hPane,%IDC_CITY,Wea.hInst,Byval %NULL)
  hCtrl=CreateWindowEx(0,"static","State",%WS_CHILD Or %WS_VISIBLE,10,190,100,25,hPane,-1,Wea.hInst,Byval %NULL)   
  hCtrl=CreateWindowEx(%WS_EX_CLIENTEDGE,"edit","",%WS_CHILD Or %WS_VISIBLE Or %WS_BORDER,140,190,150,25,hPane,%IDC_STATE,Wea.hInst,Byval %NULL)                                               
  hCtrl=CreateWindowEx(0,"static","Country",%WS_CHILD Or %WS_VISIBLE,10,220,100,25,hPane,-1,Wea.hInst,Byval %NULL)   
  hCtrl=CreateWindowEx(%WS_EX_CLIENTEDGE,"edit","",%WS_CHILD Or %WS_VISIBLE Or %WS_BORDER,140,220,150,25,hPane,%IDC_COUNTRY,Wea.hInst,Byval %NULL)
  hCtrl=CreateWindowEx(0,"static","Zip Code",%WS_CHILD Or %WS_VISIBLE,10,250,100,25,hPane,-1,Wea.hInst,Byval %NULL)   
  hCtrl=CreateWindowEx(%WS_EX_CLIENTEDGE,"edit","",%WS_CHILD Or %WS_VISIBLE Or %WS_BORDER,140,250,150,25,hPane,%IDC_ZIP,Wea.hInst,Byval %NULL)
  hCtrl=CreateWindowEx(0,"static","Email",%WS_CHILD Or %WS_VISIBLE,10,280,100,25,hPane,-1,Wea.hInst,Byval %NULL)   
  hCtrl=CreateWindowEx(%WS_EX_CLIENTEDGE,"edit","",%WS_CHILD Or %WS_VISIBLE Or %WS_BORDER,140,280,150,25,hPane,%IDC_EMAIL,Wea.hInst,Byval %NULL)
  hCtrl=CreateWindowEx(0,"static","Telephone",%WS_CHILD Or %WS_VISIBLE,10,310,100,25,hPane,-1,Wea.hInst,Byval %NULL)   
  hCtrl=CreateWindowEx(%WS_EX_CLIENTEDGE,"edit","",%WS_CHILD Or %WS_VISIBLE Or %WS_BORDER,140,310,150,25,hPane,%IDC_TELEPHONE,Wea.hInst,Byval %NULL)
  hCtrl=CreateWindowEx(0,"static","Interests",%WS_CHILD Or %WS_VISIBLE,10,340,100,25,hPane,-1,Wea.hInst,Byval %NULL)   
  hCtrl=CreateWindowEx(%WS_EX_CLIENTEDGE,"edit","",%WS_CHILD Or %WS_VISIBLE Or %WS_BORDER Or %ES_MULTILINE,140,340,150,100,hPane,%IDC_TELEPHONE,Wea.hInst,Byval %NULL)
  hCtrl=CreateWindowEx(0,"button","Submit",%WS_CHILD Or %WS_VISIBLE,100,455,100,25,hPane,%IDC_SUBMIT,Wea.hInst,Byval %NULL)
 
  'Initialize Window's internal scrolling apparatus (vsi think verticle scroll info)
  Call GetClientRect(Wea.hWnd,rc)       'Need size of main window's client area to determine .nMax
  vsi.cbSize=Sizeof(SCROLLINFO)         'Api docs say to do this
  vsi.nMin=0
  vsi.nMax=490-rc.nBottom               
  vsi.nPos=0
  vsi.fMask=%SIF_POS Or %SIF_RANGE
  Call SetScrollInfo(Wea.hWnd,%SB_VERT,vsi,%TRUE)
 
  fnWndProc_OnCreate=0
End Function


Function fnWndProc_OnVScroll(Wea As WndEventArgs) As Long
  Local vsi As SCROLLINFO
  Local hPane As Dword
 
  hPane=GetWindowLong(Wea.hWnd,0)
  Select Case As Long Lowrd(Wea.wParam)
    Case %SB_LINEUP
      vsi.cbSize=Sizeof(SCROLLINFO)
      vsi.fMask=%SIF_POS Or %SIF_RANGE
      Call GetScrollInfo(Wea.hWnd,%SB_VERT,vsi)
      If vsi.nPos>0 Then
         vsi.nPos=vsi.nPos-10
         If vsi.nPos<0 Then
            vsi.nPos=0
         End If   
         Call MoveWindow(hPane,0,-1*vsi.nPos,325,500,%TRUE)
         vsi.fMask=%SIF_POS
         Call SetScrollInfo(Wea.hWnd,%SB_VERT,vsi,%TRUE)
      End If   
    Case %SB_LINEDOWN
      vsi.cbSize=Sizeof(SCROLLINFO)
      vsi.fMask=%SIF_POS Or %SIF_RANGE
      Call GetScrollInfo(Wea.hWnd,%SB_VERT,vsi)
      If vsi.nPos<vsi.nMax Then
         vsi.nPos=vsi.nPos+10
         Call MoveWindow(hPane,0,-1*vsi.nPos,325,500,%TRUE)
         vsi.fMask=%SIF_POS
         Call SetScrollInfo(Wea.hWnd,%SB_VERT,vsi,%TRUE)
      End If
  End Select   
   
  fnWndProc_OnVScroll=0
End Function


Function fnWndProc_OnClose(wea As WndEventArgs) As Long
  Call PostQuitMessage(0)
  fnWndProc_OnClose=0
End Function


Function fnWndProc(ByVal hWnd As Long,ByVal wMsg As Long,ByVal wParam As Long,ByVal lParam As Long) As Long
  Register i As Long

  For i=0 To 2
    If wMsg=MsgHdlr(i).wMessage Then
       Local wea As WndEventArgs
       Local iReturn As Long
       wea.hWnd=hWnd: wea.wParam=wParam: wea.lParam=lParam
       Call Dword MsgHdlr(i).dwFnPtr Using fnPtr(wea) To iReturn
       fnWndProc=iReturn
       Exit Function
    End If
  Next i

  fnWndProc=DefWindowProc(hWnd,wMsg,wParam,lParam)
End Function


Sub AttachMessageHandlers()
  ReDim MsgHdlr(2) As MessageHandler   'Associate Windows Message With Message Handlers
  MsgHdlr(0).wMessage=%WM_CREATE   :   MsgHdlr(0).dwFnPtr=CodePtr(fnWndProc_OnCreate)
  MsgHdlr(1).wMessage=%WM_VSCROLL  :   MsgHdlr(1).dwFnPtr=CodePtr(fnWndProc_OnVScroll)
  MsgHdlr(2).wMessage=%WM_CLOSE    :   MsgHdlr(2).dwFnPtr=CodePtr(fnWndProc_OnClose)
End Sub


Function WinMain(ByVal hIns As Long,ByVal hPrev As Long,ByVal lpCL As Asciiz Ptr,ByVal iShow As Long) As Long
  Local hWnd As Dword, dwStyle As Dword
  Local szAppName As Asciiz * 16
  Local winclass As WndClassEx
  Local Msg As tagMsg

  szAppName="ScrollControls"
  Call AttachMessageHandlers()
  winclass.cbSize=SizeOf(winclass)
  winclass.style=%CS_HREDRAW Or %CS_VREDRAW
  winclass.lpfnWndProc=CodePtr(fnWndProc)
  winclass.cbClsExtra=0
  winclass.cbWndExtra=8
  winclass.hInstance=hIns
  winclass.hIcon=LoadIcon(%NULL, ByVal %IDI_APPLICATION)
  winclass.hCursor=LoadCursor(%NULL, ByVal %IDC_ARROW)
  winclass.hbrBackground=GetStockObject(%WHITE_BRUSH)
  winclass.lpszMenuName=%NULL
  winclass.lpszClassName=VarPtr(szAppName)
  winclass.hIconSm=LoadIcon(hIns, ByVal %IDI_APPLICATION)
  RegisterClassEx winclass
  dwStyle=%WS_CAPTION Or %WS_MINIMIZEBOX Or %WS_VISIBLE Or %WS_VSCROLL Or %WS_SYSMENU
  hWnd=CreateWindowEx(0,szAppName,szAppName,dwStyle,200,100,325,300,0,0,hIns,ByVal 0)
  Call ShowWindow(hWnd,iShow)
  While GetMessage(Msg,%NULL,0,0)
    TranslateMessage Msg
    DispatchMessage Msg
  Wend

  Function=msg.wParam
End Function