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