Fred's Tutorial #7: Multi-File Project Using Various Windows Controls

Started by Frederick J. Harris, September 09, 2007, 07:18:43 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Frederick J. Harris

ShowData.bas

     All the programs so far in this tutorial were constructed from an architecture termed SDI or 'Single Document Interface'.  This is in contrast to MDI or 'Multiple Document Interface' architectures.  Examples of multiple document architectures would be the programming editor you are using such as PBEdit.exe, or Microsoft Word or Excel.  With these MDI programs it is possible to have multiple documents open simultaneously.  Certain application development scenarios lend themselves to this latter architecture.  However, the single document interface architecture does not preclude large programs with many documents.  This tutorial provides an example architecture of an SDI program with three windows, the first of which is a kind of 'switchboard' which launches the other two through the click of a button.

     The application is ShowData.bas and it will also help to clarify the relationship between  various  large scale objects and concepts we have been working with such as WinMain(), the message loop, window classes, and the window procedure.  For you see,  this application has three window classes and three window procedures.  First there is the window class and window procedure for the main application window which are both named "frmMainWindow" for Form Main Window.  During the message processing of the WM_CREATE message for this main app window two more window classes are registered, and they are the frmTextBox class and the frmListBox class.  Windows of these classes are launched from the main window by clicking a button.  These windows also need a separate window procedure because that is the way the Windows operating system itself was constructed.  Each class needs its own separate window procedure to process messages for however many windows of that class were created.  In Checkers.bas we saw a situation where 25 child windows were created on top of a main application window and each of these windows were serviced by fnChildWndProc()

     This application, ShowData.bas, also shows a different program architecture from what we have been doing so far in terms of how the code is organized.  In all the programs so far there was only one source code file.  However, most of the programs – except for short Console Compiler ones - #Included "Win32Api.inc" .  Well, we'll be doing that here.  As you build larger and larger projects it becomes to unwieldy to have ten, fifteen, twenty thousand or more lines of code in one file.  In an SDI program with multiple Forms/Windows/Dialogs a natural and logical way to modularize the monolith is to put all code relating to a specific form in its own include file. That is what we are going to do with ShowData.bas. 

     There will be a main program file named 'ShowData.bas'.  Below is what the compiler directives at the top of ShowData.bas look like:


#Compile Exe  "ShowData"      'ShowData.bas
#Dim All
#Include "Win32api.inc"       'Main Windows Api Include File.  Contains equates & declares.
#Include "ShowData.inc"       'Main Include File For This App.
#Include "frmTextBox.inc"     'Form Text Box.  Code That Runs The Text Box Example
#Include "frmListBox.inc"     'Form List Box.  Code That Runs The List Box Example


     As you can see, after the obligatory Windows Include file, there are three more files.  The first is ShowData.inc.  I frequently create an include file to contain equates, declares, and functions for an application that will be useful or needed throughout the application.  Examples would be database related functions that might be used in every Form/Dialog in the whole application.  Other examples might be string or date functions.

     Following that are the includes for the Forms/Dialogs that comprise most of the functionality of the application.  In this particular application there is an include for frmTextBox and another for frmListBox.  All these Forms/Dialogs do is display a window which contains a text box or list box in which text data can be scrolled.  In the case of the text box of frmTextBox I fill it with the ODBC SQLDrivers() database driver information we so painstakingly elucidated in Tutorial #6.  In frmListBox I just filled a list box with a hundred lines of arbitrary text, and showed how to extract text lines from the list box using various SendMessage() function calls to the list box.  You'll further note that the respective window procedure for each of these Forms/Dialogs is included with the code of the respective Form/Dialog. 

     A significant high level observation you might make from all this is that there is one WinMain() function containing one message loop that services the entire application.  When a message is retrieved from the message queene windows will dispatch it to the proper window procedure even if there are many separate window procedures in the application – as is the case with this one.  Further, the window procedure to which  message needs to be sent doesn't even have to be one you have personally constructed.  It may reside in a system dll as would be the case if you used any controls such as text boxes, list boxes, etc.

     So below is ShowData.bas.  There will be four separate posts – one for each file comprising the program.  Attached at the bottom of this post (the first) is a zip file containing the four files.  While this is the 7th tutorial in my 'Beginning SDK Style Windows Programming Tutorial Series', a listing of the others plus links can be found at...

http://www.jose.it-berater.org/smfforum/index.php?topic=1243.0

Also, a discussion thread for all these posts can be found at...

http://www.jose.it-berater.org/smfforum/index.php?topic=1255.0


'************************
'File Name:  ShowData.bas
'************************

'******************************************************************************************
'This app shows how to display data in a text box and a list box using Api function calls.
'It differs from the first six programs in this series in that multiple source code files
'are used.  This file, ShowData.bas, is the main program file.  It contains all the code
'necessary to show a very simple and small Main Window containing just two buttons.  The
'top button causes another form/dialog/window to open which contains a multi-line text box
'covering the entire extent of the form.  In creating the form and making it visible an
'ODBC function, SQLDrivers() is called so as to amass multiple lines of system database
'driver info to fill the text box and allow scrolling through the data.  This is the same
'SQLDrivers() function we covered at the end of Tutorial #6.
'
'The second button on the Main Window loads another seperate form containing a list box and
'a button at the bottom.  When the user clicks the button the list box fills with a hundred
'lines of pretty boring text manufactured in a For Loop.  That is why I decided to fill the
'text box on the first form with the SQLDriver() ODBC info - to eliminate monotomy!  When
'you double click on an item in the listbox a message box pops up showing you which item
'and text string you chose.
'
'So what I'm saying here for those who want to compile and run the program in this tutorial
'is that instead of pasting one file in your programming editor as was the case with the
'previous programs, you are going to have to copy the code from four seperate posts (this
'one and the following three) into four seperate files and name them in accordance with my
'file name in each post.  That way, the compiler will be able to find them when it reads
'the following Includes just below.  This is a highly workable means of organizing a large
'programming project where each file contains code for one form/dialog in the program.  So
'with this program we have one main switchboard type form which launches the two display
'forms constituting the 'meat' of the program, so to speak.  A file with the same base part
'of the file name as this file, i.e., 'ShowData', but with the extension .inc, contains
'equates, types, and declares used by code throughout the application.  Here are the
'includes:
'******************************************************************************************
#Compile Exe  "ShowData"      'ShowData.bas
#Dim All
#Include "Win32api.inc"       'Main Windows Api Include File.  Contains equates & declares.
#Include "ShowData.inc"       'Main Include File For This App.
#Include "frmTextBox.inc"     'Form Text Box.  Code That Runs The Text Box Example
#Include "frmListBox.inc"     'Form List Box.  Code That Runs The List Box Example



'******************************************************************************************
'Function Name:  frmMainWindow_OnCreate()  Handles WM_CREATE Message For Main Window
'******************************************************************************************

'******************************************************************************************
'If you are an ex VB programmer, this is the Form_Load() event handler.  This function will
'be called when the CreateWindowEx() call is made down in WinMain() (all the way at the
'bottom -I'm a bottom-up programmer).  The main work this function does is register two window
'classes with the operating system for the two display forms in this program, and create the
'two buttons the clicking of which will respectively instantiate the form containing a text
'box in the first case and a listbox in the second case.
'******************************************************************************************
Function frmMainWindow_OnCreate(wea As WndEventArgs) As Long 'if from VB this is Form_Load()
  Local hBut As Dword,dwStyle As Dword,hIns As Dword
  Local sz1 As Asciiz*64,sz2 As Asciiz*64
  Local pCreateStruct As CREATESTRUCT Ptr
  Local winclass As WndClassEx
  Local szTextBoxDisplay As Asciiz*16
  Local  szListBoxDisplay As Asciiz*16

  pCreateStruct=wea.lParam               'When Windows sends a WM_CREATE message the lParam
  hIns=@pCreateStruct.hInstance      'parameter is a pointer to a CREATESTRUCT Type and
  sz1="View SQLDrivers Data In Text Box" 'one of the fields is the instance handle in
  sz2="View Strings Looped Into Listbox" 'WinMain()
  dwStyle=%WS_CHILD Or %WS_VISIBLE
  'Create the two buttons on the form
  hBut=CreateWindow("button",sz1,dwStyle,50,25,325,25,wea.hWnd,%IDC_BUTTON_1,hIns,0)
  hBut=CreateWindow("button",sz2,dwStyle,50,60,325,25,wea.hWnd,%IDC_BUTTON_2,hIns,0)
  'Load WndClassEx type with desired characteristics for Forms / windows
  szTextBoxDisplay="frmTextBox"   'Register frmTextBox Class
  winclass.lpszClassName=VarPtr(szTextBoxDisplay)
  winclass.lpfnWndProc=CodePtr(frmTextBox)
  winclass.hbrBackground=%COLOR_BTNFACE+1
  winclass.cbSize=SizeOf(winclass)
  winclass.style=%CS_HREDRAW Or %CS_VREDRAW
  winclass.cbClsExtra=0  : winclass.cbWndExtra=0
  winclass.hInstance=hIns
  winclass.hIcon=LoadIcon(%NULL, ByVal %IDI_APPLICATION)
  winclass.hCursor=LoadCursor(%NULL, ByVal %IDC_ARROW)
  winclass.lpszMenuName=%NULL
  Call RegisterClassEx(winclass)
  szListBoxDisplay="frmListBox"   'Register frmListBox Class
  winclass.lpszClassName=VarPtr(szListBoxDisplay)
  winclass.lpfnWndProc=CodePtr(frmListBox)
  winclass.hbrBackground=%COLOR_BTNFACE+1
  Call RegisterClassEx(winclass)

  frmMainWindow_OnCreate=0
End Function



'******************************************************************************************
'Function Name:  frmMainWindow_OnCommand()  Handles WM_COMMAND Message For Main Window
'******************************************************************************************
'
'******************************************************************************************
'Clicking a button control causes Windows to send a WM_COMMAND message to the window procedure
'of the parent of the button control.  Note when the buttons were created just above in
'frmMainWindow_OnCreate(), the eighth parameter in the CreateWindow() call made wea.hWnd (the
'handle of the Main Window) the parent of the buttons.  So when the user (you) clicks either
'button the Window Procedure for the Main Window ( frmMainWindow() ) will receive a WM_COMMAND
'message and Select Case logic will route processing to here, i.e., frmMainWindow_OnCommand().
'Just below more Select Case logic will examine the wParam parameter associated with the
'message to determine which botton was pressed, as the form contains two buttons, and either
'one could be the source of the WM_COMMAND message.  Also note in the CreateWindow() call in
'frmMainWindow_OnCreate where the buttons were created the ninth parameter was an equate
'specifying the control id for each button.  These control ids have Global scope in the
'application and are defined in ShowData.inc.  Note that ShowData.inc was included above right
'below the #Include Win32Api.inc so when the compiler hit this procedure it knew what they
'were.  The first statement in each of the cases causes the main window to become not visible.
'The next statement assigns some bit patterns to dwStyle.  This variable goes in the third
'parameter of the CreateWindow() call that will create an instance of either the form with the
'text box or the form with the list box.  If you examine the window styles applicable to the
'CreateWindow() call you'll see quite a variety and some of the styles are composite styles
'that contain groups of other commonly used window attributes.  That is the case with the
'commonly used %WS_OVERLAPPEDWINDOW style, although here I didn't think a maximize button
'made much sence, so it was easier for me to 'xor' it out than forgo use of the overlapped
'window style and list five or so styles individually.  At some point I'll cover fiddling
'with bits in another tutorial.  For now just trust me that xor-ing a bit against itself sets
'it to zero or off.  The CreateWindow() call then instantiates for each button a window of
'either the frmTextBox or frmListBox class.  Then the window is made visible.  At that point
'the window which the code in this file pertains to will still be in memory but the form
'will be invisible.  Supposing the user clicks the top button to load the frmTextBox form,
'this narritive will jump then to frmTextBox.inc which file contains code that will pertain
'to that form.  If you are reading this as a tutorial, then jump to there.
'******************************************************************************************
Function frmMainWindow_OnCommand(wea As WndEventArgs) As Long
  Local hForm As Dword, dwStyle As Dword

  Select Case LoWrd(wea.wParam)
    Case %IDC_BUTTON_1
      Call ShowWindow(wea.hWnd,%SW_HIDE)  'Hide MainWindow
      dwStyle=%WS_OVERLAPPEDWINDOW Xor %WS_MAXIMIZEBOX  'Zor out maximize button
      'The Following Call Creates The Text Box Display Form.
      hForm= _
      CreateWindowEx _
      ( _
        0, _                  'No extended styles
        "frmTextBox", _       'We registered this class in frmMainWindow_OnCreate
        "frmTextBox", _       'Caption for window
        dwStyle, _            'We set this just above.
        200,100,375,550, _    'Location/size info
        wea.hWnd, _           'Parent is this window (frmMainWindow)
        0, _                  'No menu or id
        GetWindowLong(wea.hWnd,%GWL_HINSTANCE), _   'Instance handle
        Byval 0 _             'No extra creation data.
      )
      Call ShowWindow(hForm,%SW_SHOWNORMAL)
    Case %IDC_BUTTON_2
      Call ShowWindow(wea.hWnd,%SW_HIDE)  'Hide MainWindow
      dwStyle=%WS_OVERLAPPEDWINDOW Xor %WS_MAXIMIZEBOX  'Zor out maximize button
      'The Following Call Creates The List Box Display Form.
      hForm= _
      CreateWindowEx _
      ( _
        0, _                   'No extended styles
        "frmListBox", _        'We registered this class in frmMainWindow_OnCreate
        "frmListBox", _        'Caption for window
        dwStyle, _             'We set this just above.
        150,100,525,475, _     'Location/size info
        Wea.hWnd, _            'Parent is this window (frmMainWindow)
        0, _                   'No menu or id
        GetWindowLong(wea.hWnd,%GWL_HINSTANCE), _
        Byval 0 _              'No extra creation data.
      )
      Call ShowWindow(hForm,%SW_SHOWNORMAL)
  End Select

  frmMainWindow_OnCommand=0
End Function



'******************************************************************************************
'Function Name:  frmMainWindow_OnClose()  Handles WM_CLOSE Message For Main Window
'******************************************************************************************
Function frmMainWindow_OnClose(wea As WndEventArgs) As Long
  Call PostQuitMessage(0)   'Will cause WinMain() to drop out of the message
  frmMainWindow_OnClose=0   'processing loop and the app to close.
End Function



'*********************************************************************************************************
'Function Name:  frmMainWindow  Window Procedure For Main Window
'*********************************************************************************************************

'All messages for any and all windows of the frmMainWindow class come here.  In this app there is only
'one window, and the caption of that window is ShowData.  In this procedure a variable of type WndEventArgs
'is created with static duration.  When any of the three messages that this procedure processes are received,
'the WndEventArgs variable is loaded with the data received in the function's parameters, and processing is
'routed to the correct message handling function.  Note that if a message is received other than the three
'within the Select Case statement, the message is passed to DefWindowProc().
Function frmMainWindow(ByVal hWnd As Long,ByVal wMsg As Long,ByVal wParam As Long,ByVal lParam As Long) As Long
  Local wea As WndEventArgs

  Select Case wMsg
    Case %WM_CREATE
      wea.hWnd=hWnd : wea.wParam=wParam : wea.lParam=lParam
      frmMainWindow=frmMainWindow_OnCreate(wea)
      Exit Function
    Case %WM_COMMAND
      'When a button is clicked processing is re-routed
      'to  frmMainWindow_OnCommand(wea)
      wea.hWnd=hWnd : wea.wParam=wParam : wea.lParam=lParam
      frmMainWindow=frmMainWindow_OnCommand(wea)
      Exit Function
    Case %WM_CLOSE
      wea.hWnd=hWnd : wea.wParam=wParam : wea.lParam=lParam
      frmMainWindow=frmMainWindow_OnClose(wea)
      Exit Function
  End Select

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



'************************************************************************************************
'This is actually the entry point of this app.  I believe this is called 'bottom up' programming
'style.  In this style each function's declaration is combined with the function's definition, so
'that seperate DECLAREs are not needed.
'************************************************************************************************
Function WinMain(ByVal hIns As Long, ByVal hPrev As Long,ByVal lpCL As Asciiz Ptr,ByVal Is As Long) As Long
  Local szAppName As Asciiz * 16,szClassName As Asciiz*16
  Local hMainWnd As Dword,dwStyle As Dword
  Local winclass As WndClassEx
  Local Msg As tagMsg

  szAppName="ShowData"
  szClassName="frmMainWindow"
  winclass.cbSize=SizeOf(winclass)
  winclass.style=%CS_HREDRAW Or %CS_VREDRAW
  winclass.lpfnWndProc=CodePtr(frmMainWindow)
  winclass.cbClsExtra=0 : winclass.cbWndExtra=0
  winclass.hInstance=hIns
  winclass.hIcon=LoadIcon(%NULL, ByVal %IDI_APPLICATION)
  winclass.hCursor=LoadCursor(%NULL, ByVal %IDC_ARROW)
  winclass.hbrBackground=%COLOR_BTNFACE+1
  winclass.lpszMenuName=%NULL
  winclass.lpszClassName=VarPtr(szClassName)
  Call RegisterClassEx(winclass)
  dwStyle=%WS_OVERLAPPEDWINDOW Xor %WS_MAXIMIZEBOX 'Maximize box won't help much.
  hMainWnd=CreateWindowEx(0,"frmMainWindow","ShowData",dwStyle,400,300,440,150,0,0,hIns,ByVal 0)
  Call ShowWindow(hMainWnd,Is)
  Call UpdateWindow(hMainWnd)
  While GetMessage(Msg,%NULL,0,0)
    TranslateMessage Msg
    DispatchMessage Msg
  Wend

  Function=msg.wParam
End Function

Frederick J. Harris

ShowData.Inc     ''Main Include for ShowData.bas


'**************************
'File Name:  ShowData.inc
'**************************

'**************************************************************************************************
'This is essentially the main include file for the application.  I find that in a major application
'containing many forms/dialogs there will be common functions that aren't specific to any particular
'form.  These routines can be kept in a main include file such as this.  Also, types, equates and
'global variables can go there too.  In this particular application I put in here several equates
'(the control ids for the buttons, list box and text box), and the type I use to pass the window
'procedure parameters to message handlers.  Here they are...
'**************************************************************************************************
%IDC_BUTTON_1               =  1201
%IDC_BUTTON_2               =  1202
%IDC_EDIT_BOX               =  1203
%IDC_LIST_BOX               =  1204
%IDC_LB_LOAD                =  1205

Type WndEventArgs
  hWnd                      As Dword
  wParam                    As Dword
  lParam                    As Dword
End Type

'**************************************************************************************************
'Also with this file I included the ODBC equates and function declarations I needed to make this app
'work in the absence of including the three main ODBC includes along with the includes at the top
'of ShowData.bas (the main program file).  For you see, by my including that ODBC function call to
'load the edit box with database driver information, I complicated things for myself considerably
'here.  I didn't want to force my readers into having to go to the internet (at www.Powerbasic.com)
'to download special files (the ODBC Includes) just to get this app to work, so I pulled four
'function declarations and eight equates out of the ODBC includes, and am including them right here
'in the source code of ShowData.inc.  Here they are...
'**************************************************************************************************

'ODBC equates and function declarations needed to make SQLDrivers() work.
%SQL_NULL_HANDLE             = 0&
%SQL_HANDLE_ENV              = 1
%SQL_ATTR_ODBC_VERSION       = 200
%SQL_OV_ODBC3                = 3???
%SQL_IS_INTEGER              = (-6)
%SQL_FETCH_NEXT              = 1
%SQL_NO_DATA                 = 100
%SQL_ERROR                   = -1

Declare Function SQLAllocHandle Lib "ODBC32.DLL" Alias "SQLAllocHandle" _
( _
  Byval HandleType          As Integer,_
  Byval InputHandle         As Dword,_
  Byref OutPutHandle        As Dword _
) As Integer

Declare Function SQLSetEnvAttr LIB "ODBC32.DLL" ALIAS "SQLSetEnvAttr" _
( _
  Byval EnvironmentHandle   As Dword,_
  Byval Attribute           As Long,_
  Byref Value               As Any,_
  Byval StringLength        As Long _
) As Integer

Declare Function SQLDrivers LIB "ODBC32.DLL" ALIAS "SQLDrivers" _
( _
  Byval henv                As Dword,_
  Byval fDirection          As Word,_
  Byref szDriverDesc        As Asciiz,_
  Byval cbDriverDescMax     As Integer,_
  Byref pcbDriverDesc       As Integer,_
  Byref szDriverAttributes  As Asciiz,_
  Byval cbDrvrAttrMax       As Integer,_
  Byref pcbDrvrAttr         As Integer _
) As Integer

Declare Function SQLFreeHandle LIB "ODBC32.DLL" ALIAS "SQLFreeHandle" _
( _
  Byval HandleType          As Integer,_
  Byval TheHandle           As Dword _
) As Integer


'*****************************************************************************************
'Function Name:  blnGetSQLDriverData(hEditBox As Dword) As Long
'Purpose:        Sets up ODBC Environment for Calls To SQLDrivers
'                and fills text box with driver names and driver
'                attribute-value pairs
'Return Value    %TRUE or %FALSE indicating success or failure
'Parameters      One parameter - handle to edit box to fill with
'                driver descriptions and driver attribute values
'******************************************************************************************
Function blnGetSQLDriverData(hEditBox As Dword) As Long
  Local iLen1 As Integer,iLen2 As Integer
  Local szDriverAttr As Asciiz*256
  Local szDriverDes As Asciiz*64
  Local ptrByte As Byte Ptr
  Local strArr() As String
  Local strText As String
  Local hEnvr As Dword
  Register i As Long

  If SQLAllocHandle(%SQL_HANDLE_ENV,%SQL_NULL_HANDLE,hEnvr)<>%SQL_ERROR Then
     Call SQLSetEnvAttr(hEnvr,%SQL_ATTR_ODBC_VERSION,Byval %SQL_OV_ODBC3,%SQL_IS_INTEGER)
     While SQLDrivers(hEnvr,%SQL_FETCH_NEXT,szDriverDes,64,iLen1,szDriverAttr,256,iLen2)<>%SQL_NO_DATA
       strText=strText & szDriverDes & Chr$(13,10) & Chr$(13,10)
       Decr iLen2
       ptrByte=VarPtr(szDriverAttr)  'Loop through szDriverAttr buffer one byte at a time for iLen2 bytes
       For i=0 To iLen2              'substituting commas ( Chr$(44) ) for null bytes.  iLen1 and iLen2
         If @ptrByte[i]=0 Then       'are output parameters in SQLDrivers() ODBC function call.  Then
            @ptrByte[i]=44           'PowerBASIC's Parse statement will parse driver attribute pairs into
         End If                      'string array for output.
       Next i
       @ptrByte[iLen2]=0             'The last byte at iLen2 was a null that got turned into a comma.
       ReDim strArr(ParseCount(szDriverAttr)-1)
       Parse szDriverAttr,strArr()
       For i=0 To UBound(strArr,1)
         strText=strText & strArr(i) & Chr$(13,10)
       Next i
       strText=strText & Chr$(13,10) & Chr$(13,10)
     Loop
     Call SQLFreeHandle(%SQL_HANDLE_ENV,hEnvr)
     Call SetWindowText(hEditBox,Byval StrPtr(strText))
     blnGetSQLDriverData=%TRUE
  Else
     blnGetSQLDriverData=%FALSE
  End If
End Function


Frederick J. Harris

frmTextBox.inc    'Code included for ShowData.bas that is for frmTextBox


'**************************
'File Name:  frmTextBox.inc
'**************************

'**********************************************************************************************
'Function Name:  frmTextBox_OnCreate  Handles WM_CREATE Message For frmTextBox Window
'**********************************************************************************************

'**********************************************************************************************
'The code in this file will run when the user clicks the top button on the MainWindow form.
'The Window procedure there, fnMainWndProc(), will sort out which button was pressed, and
'an instance of a frmTextBox class will be instantiated by a CreateWindow() call in
'fnMainWndProc_OnCommand(). That CreateWindow() call will cause the Window Procedure associated
'with the frmTextBox class to be called, here frmTextBox(), and the first message will
'be the WM_CREATE message.  Select Case logic there will cause the WM_CREATE message handler
'frmTextBox_OnCreate() to be called (this function directly below).
'
'The first job this function does is get the app instance handle from window creation data.
'Windows maintains for each class a type CREATESTRUCT that is initially filled with the various
'window creation parameters that you used in the original CreateWindow() call to create the
'window.  Any of the fields/members of the type can be retrieved during the processing of the
'WM_CREATE message because the lParam parameter is set to the address in memory of where Windows
'is storing the window creation data.
'
'The next job for the function is to come up with the window styles for a multi-line text box
'that will contain scroll bars for viewing more data than can fit on one screen.  If you search
'your Windows Api Help or go to MSDN you can find all the styles applicable to text/edit boxes.
'This is certainly one of the most important if not the most important windows control, so don't
'be too surprised to find a large number of styles for this control.  Styles are a very important
'subject when it comes to creating controls on your forms/dialogs, and frequently when you can't
'seem to get the control working the right way, it is because you don't have the right
'combination of styles in your CreateWindow() call.
'
'The text box on the form is sized to fill up the entire form.  When frmTextBox_OnCreate()
'is called, a GetClientRect() Api call is made to obtain the size of the client area of the
'form/window.  After the call the Type RECT variable rc will contain in its rc.nRight and
'rc.nBottom members the right most and bottom most extremities of the window in pixels.  These
'numbers are used in the CreateWindow() call that creates the edit box for the sixth and seventh
'parameters.
'**********************************************************************************************
Function frmTextBox_OnCreate(wea As WndEventArgs) As Long
  Local hEdit As Dword,dwStyle As Dword, hIns As Dword
  Local pCreateStruct As CREATESTRUCT Ptr
  Local strText As String
  Register i As Long
  Local rc As RECT

  pCreateStruct=wea.lParam        'get app instance handle
  hIns=@pCreateStruct.hInstance   'from window creation data
  'Below are styles you need to set if you want the text box to be multi-line with scroll bars.
  dwStyle=%WS_CHILD Or %WS_VISIBLE Or %ES_MULTILINE Or %WS_VSCROLL Or %WS_HSCROLL
  Call GetClientRect(wea.hWnd,rc) 'After this call rc will contain height and width of client area.
  'This CreateWindow() call below will create a window of the 'edit' class (A text box).
  hEdit=CreateWindow("edit","",dwStyle,0,0,rc.nRight,rc.nBottom,wea.hWnd,%IDC_EDIT_BOX,hIns,0)
  If blnGetSQLDriverData(hEdit)<>%TRUE Then  'In this call I pass the handle of the edit box to
     For i = 1 To 100                        'blnGetSQLDriverData() and that function fills the
       strText=strText & Str$(i) & _         'text box with database driver data from your computer.
       " Simple Demo Showing How To Display And Scroll Text In Edit Box" & _  'If the function fails
       Chr$(13) & Chr$(10)   'the text box gets filled up with these boring and useless strings.
     Next i
     Call SetWindowText(hEdit,ByVal StrPtr(strText)) '<<<Puts data into text box
  End If

  frmTextBox_OnCreate=0
End Function



'**********************************************************************************************
'Function Name:  frmTextBox_OnDestroy  Handles WM_DESTROY Message For frmTextBox Window
'**********************************************************************************************
Function frmTextBox_OnDestroy(wea As WndEventArgs) As Long
  Call ShowWindow(wea.hWnd, %SW_HIDE)
  Call ShowWindow(FindWindowEx(ByVal 0,ByVal 0,"frmMainWindow","ShowData"),%SW_SHOWNORMAL)

  frmTextBox_OnDestroy=0
End Function



'**********************************************************************************************
'Function Name:  frmTextBox  Window Procedure For frmTextBox Window
'**********************************************************************************************
Function frmTextBox(ByVal hWnd As Long,ByVal wMsg As Long,ByVal wParam As Long,ByVal lParam As Long) As Long
  Local wea As WndEventArgs

  Select Case wMsg
    Case %WM_CREATE
      wea.hWnd=hWnd : wea.wParam=wParam : wea.lParam=lParam
      frmTextBox=frmTextBox_OnCreate(wea)
      Exit Function
    Case %WM_DESTROY
      wea.hWnd=hWnd : wea.wParam=wParam : wea.lParam=lParam
      frmTextBox=frmTextBox_OnDestroy(wea)
      Exit Function
  End Select

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


Frederick J. Harris

frmListbox.inc   'Include for ShowData.bas that has code pertaining to the Form/Dialog/Window with a listbox on it.


'**************************
'File Name:  frmListBox.inc
'**************************


'******************************************************************************************
'Function Name:  frmListBox_OnCreate    Handles %WM_CREATE message for frmListBox
'******************************************************************************************

'******************************************************************************************
'Before the CreateWindow() call back in frmMainWindow_OnCommand() that creates this form
'returns, this procedure here will be called as a result of this window/form receiving a
'%WM_CREATE message.  Within this procedure itself are also two CreateWindow() call.  The
'first is to create the listbox that covers most of the form, and the second is for the
'button window along the bottom of the form the clicking of which will cause the listbox to
'fill with data.  In order for the listbox on this form to respond to double click messages
'it was necessary to add the %LBS_NOTIFY style bit to its variable holding its styles.
'******************************************************************************************
Function frmListBox_OnCreate(wea As WndEventArgs) As Long
  Local hLBox As Dword, dwStyle As Dword, hBut As Dword, hIns As Dword
  Local pCreateStruct As CREATESTRUCT Ptr
  Local szStr As Asciiz*64
  Local rc As RECT

  pCreateStruct=wea.lParam
  hIns=@pCreateStruct.hInstance
  dwStyle = %LBS_HASSTRINGS Or %WS_CHILD Or %WS_VISIBLE Or %WS_VSCROLL Or %LBS_NOTIFY
  Call GetClientRect(wea.hWnd,rc)
  hLBox=CreateWindow("listbox","",dwStyle,0,0,rc.nRight,rc.nBottom-40,wea.hWnd,%IDC_LIST_BOX,hIns,0)
  szStr="Click This Button To Load Data Into Listbox"
  dwStyle=%WS_CHILD Or %WS_VISIBLE
  hBut=CreateWindow("button",szStr,dwStyle,75,rc.nBottom-35,360,25,wea.hWnd,%IDC_LB_LOAD,hIns,0)

  frmListBox_OnCreate=0
End Function



'*********************************************************************************************
'Function: LoadListBox(hListBox As Dword)  Loads hListBox with some useless example strings
'*********************************************************************************************
Sub LoadListBox(hListBox As Dword)
  Local strText As String
  Register i As Long

  For i=1 To 100
    strText=Str$(i) & " Simple Demo Showing How To Display And Scroll Text In List Box"
    Call SendMessage(hListBox, %LB_ADDSTRING, 0, Strptr(strText))
  Next i
End Sub



'*********************************************************************************************
'Function Name:  frmListBox_OnCommand()  Handles WM_COMMAND Message For frmListBox Window/Form
'*********************************************************************************************

'*********************************************************************************************
'%WM_COMMAND messages can result from quite a few sources.  Select Case logic in this procedure
'is only interested in two of them, however.  The first is when the button along the bottom of
'the form is clicked, and the second is when an item in the listbox is double clicked.  This
'latter case is the more interesting of the two in that two seperate bits of data are packaged
'within the wea.wParam variable.  The low order word of wea.wParam contains the control id of
'the control to which the message applies, and the high order word contains the particular
'listbox message being sent.  If the control id is %IDC_LIST_BOX and the message is %LBN_DBLCLK
'then a message is sent to the listbox asking for the zero based index of the item within the
'list box that was double clicked.  Did you get that?  This is I believe the first time in these
'six or seven apps in this tutorial that we sent a message anywhere.  So far, we have only been
'at the receiving end of this constant messageing business.  But to be absolutely clear, that
'listbox is on this form, but to communicate with it we send it a message.  The return value of
'that particular SendMessage() call with that particular configuration of message arguements is
'the zero based index we want (the line double clicked).  Armed with the index of the line
'double clicked, we can send another message to the listbox requesting return of the line of
'text corresponding to the index of the item double clicked.  Notice that the arguements of this
'latter SendMessage() call are different than for the first.  This will universally be the case
'because SendMessage() is a very powerful and adaptable function where the arguements depend on
'the type of control the message is being sent to, and the response or functionality being
'requested.
'
'Perhaps we should take a look at SendMessage()'s parameters, one by one.  The first parameter
'is the handle of the control.  Do you remember where handles of controls come from?  Since we're
'talking about this particular list box - the only list box on this form, we only need to look
'as far as frmListBox_OnCreate() just above to see the handle of the listbox was returned by the
'CreateWindow() call that created the listbox.  But, in exmining frmListBox_OnCreate() have you
'picked up on the fact that hLBox is declared as a local Dword variable in that procedure?  What
'that means to us here in this procedure is that it is of absolutely no good to us anymore.  That
'variable and whatever value it had ceased to exist when that procedure exited.
'*********************************************************************************************
Function frmListBox_OnCommand(wea As WndEventArgs) As Long
  Local iIndex As Dword,hLBox As Dword
  Local szBuffer As Asciiz*64

  hLBox=GetDlgItem(wea.hWnd,%IDC_LIST_BOX)
  Select Case Lowrd(wea.wParam)
    Case %IDC_LB_LOAD
      Call LoadListBox(hLBox)
      MsgBox("Double Click A Line To Extract From List Box!")
    Case %IDC_LIST_BOX
      If Hiwrd(wea.wParam)=%LBN_DBLCLK Then
         iIndex=SendMessage(hLBox,%LB_GETCURSEL,0,0)
         Call SendMessage(hLBox,%LB_GETTEXT,iIndex,Varptr(szBuffer))
         MsgBox szBuffer,%MB_ICONINFORMATION, _
         "See fnListBoxDisplay_OnCommand() For Extracting Strings From Listbox"
      End If
  End Select

  frmListBox_OnCommand=0
End Function


'**********************************************************************************************
'Function Name:  frmListBox_OnDestroy  Handles WM_DESTROY Message For frmListBox Window
'**********************************************************************************************
Function frmListBox_OnDestroy(wea As WndEventArgs) As Long
  Call ShowWindow(wea.hWnd, %SW_HIDE)
  Call ShowWindow(FindWindowEx(ByVal 0,ByVal 0,"frmMainWindow","ShowData"),%SW_SHOWNORMAL)
  frmListBox_OnDestroy=0
End Function



'******************************************************************************************
'Function Name:  frmListBox  Window Procedure For Windows of Class frmListBox
'******************************************************************************************

'******************************************************************************************
'Back in frmMainWindow, when the user clicks the second button, "View Strings Looped Into
'Listbox", frmMainWindow_OnCommand() is called with the wea.wParam variable containing in its
'lower two bytes the control id of the second button, i.e., %IDC_BUTTON_2=1202.  Within that
'procedure is the CreateWindow() call that brings this form into existance.  Thereafter, all
'messages pertaining to this form or any of its child controls will pass through this
'procedure
'******************************************************************************************
Function frmListBox(ByVal hWnd As Long,ByVal wMsg As Long,ByVal wParam As Long,ByVal lParam As Long) As Long
  Local wea As WndEventArgs

  Select Case wMsg
    Case %WM_CREATE
      wea.hWnd=hWnd : wea.wParam=wParam : wea.lParam=lParam
      frmListBox=frmListBox_OnCreate(wea)
      Exit Function
    Case %WM_COMMAND
      wea.hWnd=hWnd : wea.wParam=wParam : wea.lParam=lParam
      frmListBox=frmListBox_OnCommand(wea)
      Exit Function
    Case %WM_DESTROY
      wea.hWnd=hWnd : wea.wParam=wParam : wea.lParam=lParam
      frmListBox=frmListBox_OnDestroy(wea)
      Exit Function
  End Select

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