Announcement

Collapse

Forum Guidelines

This forum is for finished source code that is working properly. If you have questions about this or any other source code, please post it in one of the Discussion Forums, not here.
See more
See less

Beginning Win32 SDK Style Programming Example Series

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Beginning Win32 SDK Style Programming Example Series

    This is the first program in a series I have started to advance the use of the Windows Application Programming Interface ( Api ) for creating Windows programs with PowerBASIC. A comment thread has been started in Articles Tutorials and HowTos at:



    As of middle August 2007 there are 11 seperate posts, as described below...

    Post #1 (this post) Form1.bas. The very basics of using the Windows Api to construct a window. Kind of a 'Hello, World!' without the Hello. Mostly covers some basics such as Win32Api.inc, parameter passing and function calls. Also delves into WinMain(). It is located at



    Post #2 Starts working on the concept of the Window Procedure, and finishes with a program that writes to a display window the current mouse position, window sizes, keypresses and mouse left button clicks. It’s at



    The original Post #2 had a statically defined variable in it which drew some comment. I replaced that variable in an alternate version which uses a pointer to dynamic memory. It is for more advanced beginners and is at



    Post #3 Continues discussions of parameter passing and interpretation of C Api documentation in the context of PowerBASIC. Shows how to modularize code, create windows through button presses, draw text, etc. It is located at



    Post #4 Shows how to create a simple menu using a resource file. It’s at



    Post #5 Shows how to use Open File Common Dialog Box with resource script created menu, also includes an ordinary resource script created dialog box;



    Post #6
    Post #7
    Post #8
    Post #9 SwowData.bas Introduces multi-file projects which display multiple windows and does this in the context of showing how to display scrolling data in several simple controls (see PowerBasic.com – not here yet)

    Post #10 Simplest.bas Introduces how to write scroll code (see PowerBasic.com – not here yet)

    Post #11 Further elaborates on Simplest.bas by showing how to use the more advanced ScrollWindow() to scroll text in a window. (see PowerBasic.com – not here yet)

    First, just a few housekeeping chores about using these example programs before we jump right into the tutorial. You should be able to use the PowerBASIC Console Compiler to compile and run all these programs if you don't have the Windows Compiler. The only thing different that will happen is that a console window will open in addition to whatever graphical output the program displays. And even this can even be turned off through the use of the #Console Off directive right after #Compile Exe. If you have any difficulties, send me an email. If we can't resolve the issue I'll refund you your money!

    Also, if you are new to the PowerBASIC Forums (or even if you aren't) let me clue you into an excellent way of getting these posts into your programming editor. The reason you need to know this is that if you copy code right out of the html/ubb display of your browser, all code formatting will be lost in the html shuffle, along with the carriage return, line feed sequences your code editor needs. You will then have a truly miserable job trying to correctly reformat the code.

    Here is what you must do. When in the Source Code Forum viewing a post you are interested in copying, click on the icon to edit the post - just as if it were your own post. I don't believe the PowerBASIC Forum software will actually allow you to change and re-submit someone elses post, however, it will display it for you in the textbox/edit control in which it was originally entered. At that point you can Select all the text through a 'Right Click', do a 'Copy', and 'Paste' it into your programming editor with all the original author's formatting preserved. Naturally, you'll have to remove non-compilable text from the editor before it will compile, but you will have saved yourself a lot of hassle. Amazingly enough, even some very experienced Forum members are unaware of this. With that out of the way, lets get to work.

    You won't be an expert after working through these examples, but if you follow my explanations carefully, you'll have at least that first toehold you need to get started. As a prerequisite to mastering this material you should understand functions, return values, parameter passing mechanisms, and TYPEs. It is a great deal of fun. So lets begin.

    Here are the four steps for creating an SDK or Win32 style Windows Program with PowerBASIC or C:

    1) Include Windows header file, i.e., Win32Api.inc for PowerBASIC or Windows.h for C;
    2) Fill out a WndClassEx TYPE (PB) or a WNDCLASSEX structure (C) and Register a Window Class;
    The latter will require making a call to the Api RegisterClassEx() function;
    3) Create a window of the registered class by making a call to CreateWindowEx();
    4) Receive and process messages from Windows by creating a message loop and Window Procedure.

    We'll take each one, step by step. Step #1 is pretty easy. The following program, which looks pretty short and doesn't seem to do anything, completes that step. Please compile and run it...

    Code:
    #Compile Exe
    #Dim All
    #Include "Win32Api.inc"
    
    Function PBMain() As Long
    End Function
    Please run that code in the PowerBASIC compiler. If you've set up your IDE to generate and display compiler output you'll see the compiler report that the program contains 49,697 lines of code and it generates an executable containing 9216 bytes on disk! How so, you might ask? Well, the answer is that huge Win32Api.inc file. It contains all the equates, type definitions, and function prototypes (declares) for the core Windows dlls that comprise the operating system. If you are not sure what all those terms mean, you shortly will, as I'm going to have you digging around in that file pretty deep.

    If you ran the above program you saw that nothing seemed to happen, at least nothing apparent.

    Now lets make one Windows Api call to find the size of the Windows desktop. Paste this program in your code editor, then compile and run it. I'll dissect it to death shortly.

    Code:
    #Compile Exe
    #Dim All
    #Include "Win32Api.inc"
    
    Function PBMain() As Long
      Local rc As RECT
      
      Call SystemParametersInfo(%SPI_GETWORKAREA,0,rc,0)
      MsgBox("rc.nRight=" & Trim$(Str$(rc.nRight)) & " And rc.nBottom=" & Trim$(Str$(rc.nBottom)))
      
      PBMain=0
    End Function
    When you ran this program you should have seen a message box display the width and height of your computer screen in pixels. Now I want to explain to you what happened. First, open up the Win32Api.inc file in PBEdit or whatever editor you are using, or even Notepad would be good enough.

    The file should be included with other #Includes in the \Includes directory of your PB install.

    When you have it open go to the edit menu and do a 'Find' for 'SystemParametersInfo'. Your editor will locate the DECLARE for that function almost at the bottom of the file. It is very long and you'll probably need to scroll to see it all. Copy that declare and paste it somewhere for temporary safe keeping. Here is how I oftentimes reformat these things to make them easier to examine:

    Code:
         Declare Function SystemParametersInfo Lib "USER32.DLL" Alias "SystemParametersInfoA" _
         ( _
           ByVal uAction As Dword, _
           ByVal uParam As Dword, _
           ByRef lpvParam As Any, _
           ByVal fuWinIni As Dword _
         ) As Long
    The next thing I want you to do is either go to MSDN and search for the SystemParametersInfo function, or better yet, open your copy of Api Help you should have gotten with your compiler. Below is what you'll see at the top of the page:

    Code:
         BOOL SystemParametersInfo(
           UINT uiAction, // system parameter to query or set
           UINT uiParam,  // depends on action to be taken
           PVOID pvParam, // depends on action to be taken
           UINT fWinIni   // user profile update flag
         );
    Now the SystemParametersInfo Api function is a fairly complex one that can return piles of disparate data, but all we want of it is to get the size of a computer's desktop screen. You tell SystemParametersInfo what info you want through the first parameter to the function described in the declare as a 'ByVal uAction as Dword'. If you peruse the possible values for this first parameter you'll find SPI_GETWORKAREA near the bottom. This is an equate. This particular function has quite a variety of them. I want you to now search the Win32Api.inc file for this equate. In the 'Find' dialog box type %SPI_GETWORKAREA. Below is what you'll find:

    %SPI_GETWORKAREA = 48

    Nothing too magical about that. It's equal to 48. I've more work for you to do. Return to the Win32Api.inc file and now do a search for Type RECT. This is what you'll find...

    Code:
         Type RECT
           nLeft As Long
           nTop As Long
           nRight As Long
           nBottom As Long
         End Type
    Now we have all the pieces of the puzzle together in one place where we can see what is going on. In this next program we will eliminate the Win32Api.inc file altogether just to prove there is nothing magical about it, and so you can see better what is actually happening in an Api program. Here is the next program...

    Code:
    #Compile Exe
    #Dim All
    %SPI_GETWORKAREA = 48
    
    Declare Function SystemParametersInfo Lib "USER32.DLL" Alias "SystemParametersInfoA" _ 'We're embedding
    ( _                                                                                    'this declare
      ByVal uAction As Dword, _                                                            'from Win32Api
      ByVal uParam As Dword, _                                                             'right in our
      ByRef lpvParam As Any, _                                                             ' program.
      ByVal fuWinIni As Dword _
    ) As Long
    
    Type RECT                       'This complex variable type made up of
      nLeft As Long                 'four simple 32 bit longs is used in
      nTop As Long                  'the function call
      nRight As Long
      nBottom As Long
    End Type
    
    Function PBMain() As Long
      Local rc As RECT              'To get the screen size the function needs this variable
    
      Call SystemParametersInfo(%SPI_GETWORKAREA,0,rc,0)
      MsgBox("rc.nRight=" & Trim$(Str$(rc.nRight)) & " And rc.nBottom=" & Trim$(Str$(rc.nBottom)))
    
      PBMain=0
    End Function
    It should produce the exact same output as before, at least in terms of the running program. The compiler output, however, will be considerably different. Instead of the compiler reporting that the program contains almost 50,000 lines, it will report 28. If equates scare you (they still scare me a little bit), you can delete the %SPI_GETWORKAREA in the program's third line and just stick '48' in its place in the function. That makes the thing look even more tame.

    Note that we took everything we needed out of Win32Api.inc and included it right in our little program, just to show in an easy and non-mysterious manner the pieces of the puzzle and what they do.

    All of these pieces are things with which you should already be somewhat if not greatly familiar. First off, we have an equate, namely %SPI_GETWORKAREA. This is conceptually no different from %TRUE = 1 and %FALSE = 0. No mystery there. Then we have a rather nasty declare we obtained from the Win32Api.inc file for SystemParametersInfo. But conceptually, this isn't a whole lot different from something as simple as a function to square a number with a declare like so...

    Code:
         Declare Function MySquare(x As Double) As Double
    
         ...and a function definition like so...
    
         Function MySquare(x As Double) As Double
           MySquare=x*x
         End Function
    Its just a little bigger and uglier is all. Finally, we have a particular TYPE required by one of the parameters of the function itself, that is, a RECT type. These are simply a bunch of Longs for the coordinates of a rectangle. Believe me, you'll run into that one a lot in Windows programming as most things ultimately resolve to a rectangle!

    In terms of the function call itself, there are several things worth noting. First, since this function returns a wide variety of information, depending of course on the equate fed into it, you need to carefully pour through the documentation to know what values need to be filled out, and which don't. Many Api functions are like this. In our case above we only needed the first and third parameters, and the second and fourth we left at zero. Here is what the Api reference says about SPI_GETWORKAREA...

    "Retrieves the size of the work area on the primary display monitor. The work area is the portion of the screen not obscured by the system taskbar or by application desktop toolbars."

    "The pvParam parameter must point to a RECT structure that receives the coordinates of the work area, expressed in virtual screen coordinates."

    Now there is a lot for me to comment on there, and a very great deal with which you might be confused. So if you don't fully understand what it is saying, don't be alarmed. I'll take you through it all piece by piece. The part that I'm thinking you might have trouble with is the "pvParam parameter must point to a RECT structure that receives the coordinates of the work area".

    Lets really back up and start at the beginning, perhaps at something you may not have even noticed about this program. If you look at the way I invoked the SystemParametersInfo function above you'll note I used the Call reserved word of PowerBASIC. How can this be? After all, you may be thinking, doesn't a function return a value? What I've done is thrown away the value returned by the function, which, by the way, wasn't the size of the screen that we were looking for anyway. For you see, this Api function as well as almost all others return values through function parameters, not return values from functions.

    If you look carefully at the C description of the function in your Api reference or where I copied it into this document above, you'll see the function returns a BOOL, which, in the PowerBASIC declare was converted to a Long. The fact is, Api functions return SUCCESS/FAILURE codes through return values - not data. In using the Api you really have to disabuse yourself of the idea of returning data through function return values.

    What you really need to do is think of functions/procedures as two way data transfer mechanisms in which data is both transferred to and received from the function through its parameters rather than through a simple scalar return value. Another important point is that due to the complexity of the operating system itself, there are oftentimes a great deal of data to transfer through parameters, and to avoid excessively long parameter lists, complex data types are used. In PowerBASIC we term them TYPEs and in C structures. In the above program we declared a variable of TYPE RECT, and we passed that object to the SystemParametersInfo function in the third parameter to the function. Further note that we declared the rc variable of TYPE RECT in PBMain() but we never initialized it to anything before passing it to the function. The reason we didn't initialize it to anything is because its purpose is to receive information from the function - not to provide the function with information. It is an Output parameter.

    In terms of that sort of terminology, the first parameter to that function is an Input parameter (SPI_GETWORKAREA) and the third parameter is an Output parameter (rc).

    And now for the bad part. This is the part where you get lost or the whole thing seems fuzzy or incomprehensible. I mean the part about "The pvParam parameter must point to a RECT structure....". Yes, that part. I won't deny it, this is hard. The reason its hard is because this is a completely C centric way of looking at the computer. Had Windows been written in PowerBASIC, or even had the Windows Api documentation been written in PowerBASIC, this difficulty would not exist. But the fact is that the documentation on the Windows Api is written in C, so if you want to use it with PowerBASIC, you have to learn how to translate the documentation to BASIC datatypes and function calls. So here is what is happening here. If the above program was written in C as a console program the PBMain() part would look like this...

    Code:
         int main(void)
         {
          RECT rc;
          //
          SystemParametersInfo(SPI_GETWORKAREA,0,&rc,0)
          printf("rc.Right=%u\trc.Bottom=%u\n",rc.Right,rc.Bottom);
          system("PAUSE");
          //
          return 0;
         }
    Note the variable declaration of rc. It is backwards from PowerBASIC but nonetheless easily discernable. Now note the third parameter to the function. It looks like this...

    &rc.

    Now what could that mean? What's with the ampersand? If you refer back to the docs - that '&' symbol is heavily implicated in the "...point to a RECT structure" bit. For you see, a pointer is a variable that holds a particular and very specific kind of value, and what that value must be is the address of another variable. In C, pointers have to be declared as such, and in the above C example it could be done like so...

    RECT rc;
    RECT *ptrRectangle;

    The asterisk in front of ptrRectangle, i.e., *ptrRectangle, tells the C compiler that the variable is not a rectangle structure, but rather a pointer to a ectangle structure, i.e., it holds the address of a rectangle structure allocated in memory somewhere else. Had I done that in the above program it would have looked like this...

    RECT rc;
    RECT *ptrRectangle;

    ptrRectangle = &rc;
    SystemParametersInfo(SPI_GETWORKAREA,0,ptrRectangle,0)

    What the ampersand symbol is is the 'address of' operator. It is exactly equivalent to PowerBASIC's VarPtr() function. It takes the address of anything it is pre-pended to. Rather than encumbering a program with an unneeded variable, most C programmers would do as in the first case and just put an '&rc' n the function call rather than create a pointer variable and assign the address of the RECT to it. And don't lose sight of the fact that what this function needs is the address of a RECT variable allocated someplace, anywhere, to put RECT data into (our screen size). So, for example, if when the program is loaded into memory and storage provided for a RECT TYPE/structure, and the address of the variable happens to be say, 1048256, then that function wants to get 1048256 fed to it. When it receives 1048256, it already knows what a RECT type is, because it found a type declaration for that type. And whether that type declaration was read out of the Win32Api.inc, or read from the top of the main module's source it neither knows nor cares. What is important to the function is that when it has the address of a RECT type, bytes 0 - 3 offset from the base address (1048256) get the upper left x coordinate of the RECT put
    into it, bytes 4 - 7 the top, and so on.

    Now the situation with PowerBASIC is interesting. Recall all we had for that problematic third parameter was a declaration of the parameter like so...

    Local rc As RECT

    ...and it was used in the function call similiarly...

    Call SystemParametersInfo(%SPI_GETWORKAREA,0,rc,0)

    So now you might legitimately ask me this. "OK smart guy, you say that the function needs the address of a RECT type variable passed to it, and PowerBASIC uses the VarPtr() function to do what the '&' operator does in C, so why doesn't the function have a VarPtr(rc) term in it???" Well, smart guy retorts, "PowerBASIC passes parameters to procedures by reference unless told to do otherwise, which means that the compiler, in setting up the function call mechanism, automatically passes the address of any parameters of the function call!" So there you have it. Sixteen bytes of storage were allocated for a RECT type at the top of PBMain(), and in the function call itself PowerBASIC passed the address in memory of that variable to the SystemParametersInfo function because the PowerBASIC declare specified a By Reference parameter. This is of such overwhelming importance I'll have much more to say about it.

    We will directly turn to creating our first window using the Api, but before we do that lets review what you should have learned in my discussion above. First, the Win32Api.inc file contains equates, type declarations, and procedure declarations which are necessary for the PowerBASIC compiler, or for any other compiler for that matter, to connect to functions contained in the critical operating system dlls.

    In our simple program above we had an example of each of these items, and we in fact pulled them right out of the Win32Api.inc file and embedded them right in our program. The next important issue involves passing function parameters to Win32 functions. There are issues here that you might not have had to face before if all your previous programming experience was within the basic language calling only procedures contained within the run time engine of that language. This topic we just briefly touched upon, and will have considerably more to say about it.

    Let us now turn to step #2 in my original 4 step process of creating a Windows program (all of the above was only step #1, unfortunately!). As was the case with SystemParametersInfo() above where we found we needed a RECT type in the function call, creating a window will require a type known as WNDCLASSEX. You should look for it in Win32Api.inc, but for discussion purposes I have copied it in only slightly altered form below...

    Code:
    Type WNDCLASSEX
      cbSize As Dword              'size of the type (just count up the size of its constituent members)
      style As Dword               'class styles are obtained from the docs on the type in Api reference
      lpfnWndProc As Long          'the address of the window procedure
      cbClsExtra As Long           'extra bytes for class specific data
      cbWndExtra As Long           'extra bytes for each window of the class that is instantiated
      hInstance As Dword           'handle to instance of program (actually an address)
      hIcon As Dword               'handle to an icon
      hCursor As Dword             'handle to a cursor
      hbrBackground As Dword       'handle to background brush for window's background
      lpszMenuName As Asciiz Ptr   'pointer to a string containing menu name
      lpszClassName As Asciiz Ptr  'pointer to class name string
      hIconSm As Dword             'yet another handle to an icon
    End Type
    In order to create a main window for a program it is necessary to create a variable of this type, fill out the fields of the type variable, then call an Api function named RegisterClassEx() passing to it the address of the variable itself. This has some similarities to what we did above with SystemParametersInfo(). In that case we created a RECT variable, passed the address of that variable to the function, and the function dutifully filled out the fields for us so we could extract the width and height of our desktop. In that sence the RECT type variable was an output parameter. In this case, our WNDCLASSEX type variable will be an input parameter in that we'll fill out the fields for the function call and then pass the address of the variable to the function. Don't be overwhelmed by the large number of fields or the seeming complexity of the variables as I'll lead you through the most difficult parts.

    And I'm pretty sure the most difficult parts for you will be the pointers. The handles I consider to be pretty much in the nature of boilerplate. In our SystemParametersInfo() program above we already had our first brush with pointers in that the third parameter to that function wanted a pointer to a RECT type. We really got off easy in that case because in placing that rc variable of RECT type as an argument to that function, the PowerBASIC compiler passed its address automatically to the function, which, after all, is what any function wants when it has a pointer for a parameter. This occurred, as we previously learned, because PowerBASIC's default parameter passing mechanism is to pass the address of the parameter to the function. In this case, however, there is no function call involved in filling out the members of the type, and several member variables in the Type require an address, and we are going to have to provide it. PowerBASIC has several internal functions we can easily use for this. Take note of the next to last member - lpszClassName. I consider this to be one of the two most important members of the WNDCLASSEX class, because it is the name Windows will know this class by when it comes time to request of Windows that it instantiate a window of this class for us.

    At this point I'm going to make a slight digression and explain to you something about the variable names in this class, particularly the lpsz stuff. If you are going to learn the Sdk style of Windows programming you are going to have to learn just a little bit about pointers, addresses and asciiz strings so that you can translate C documentation. This is actually very important or I wouldn't be doing it so please bear with me.

    Below is a complete C program which arranges a memory allocation for the literal string "Form1", then outputs to the screen all the characters and their addresses. The output from the program is directly beneath it.

    Code:
    #include <stdio.h>
    
    int main(void)
    {
      char szName[]="Form1";
      unsigned int i;
    
      printf("i\tszName[i]\tAsci\tchar\n");
      printf("====================================\n");
      for(i=0;i<strlen(szName);i++)
          printf("%u\t%u\t\t%u\t%c\n",i, &szName[i], szName[i], szName[i]);
      printf("\n");
      printf("%u\t%s\n", szName, szName);
      getchar();
    
      return 0;
    }
    
    Output:
    
    i       szName[i]       Asci    char
    ====================================
    0       2293600         70      F
    1       2293601         111     o
    2       2293602         114     r
    3       2293603         109     m
    4       2293604         49      1
    
    2293600 Form1
    The first line of the program beneath main() declares an array of characters and assigns the string literal to it. The empty braces tell the C compiler to allocate enough memory in the program's data segment to hold the following string and to terminate the string with a null character. Many Windows programmers prefix string variables with an 'sz' to denote 'string terminated with a zero byte'. This is the low level representation of character strings used in C and assembly language. All the C, assembler, and Windows Api Win32 functions expect strings to be represented this way. In this sense, a string consists of a starting memory address followed by the characters in the string, and then ending with that null byte.

    Note in the printf() function there is a string containing what are termed format specifiers. The first %u tells the function to interpret the first variable, 'i', after the string as an unsigned interger and to print it. The %c tells the function to interpret the final szName as a character. Take note that the first szName in the variable list to output has the ampersand symbol prepended to it, and that causes the address in memory of the character to be printed. In this case the string started at 2,293,600.

    Note finally in the last printf function call that we've asked the function to output szName twice.

    However, in the output you'll see that what got printed was the following: '2293600 Form1'. Can you imagine that? What is happening here is that even though we've told the function to print the same variable twice, we've also told the function to interpret the first szName as an unsigned integer due to the %u format specifier, and to interpret the second szName as a character string with the %s format specifier. Further, note that the address for szName, i.e., 2293600 is the same number printed out in the loop above for &szName[0]. This is important and is due to the fact that in the C programming language an array name without the brackets such as szName indicates the address of the first byte of the string. In using the Windows Api you'll constantly have occasion to to fill out member variables of Api Types (structures in C), and you'll constantly see this exact terminology. Strings are everywhere. They're in combo boxes, listboxes, text boxes, on and on.

    Now lets take a look at the exact same program in PowerBASIC using the Console Compiler....

    Code:
    #Compile Exe
    
    Function PBMain() As Long
      Local szName As Asciiz*8
      Local pChar As Byte Ptr
      Local i As Dword
    
      szName="Form1"
      pChar=VarPtr(szName)
      Print " i             pChar         @pChar       Chr$(@pChar)"
      Print "======================================================"
      For i=0 To 4
        Print i,pChar,@pChar,Chr$(@pChar)
        Incr pChar
      Next i
      Print
      Print "VarPtr(szName)="VarPtr(szName),"szName="szName
      WaitKey$
    
      PBMain=0
    End Function
    Code:
    Output:
    
     i             pChar         @pChar       Chr$(@pChar)
    ======================================================
     0             1244804       70           F
     1             1244805       111          o
     2             1244806       114          r
     3             1244807       109          m
     4             1244808       49           1
    
    VarPtr(szName)= 1244804     szName=Form1
    You can see it took a few more lines to represent it than in the C program. C is brutally terse. We used an Asciiz string to store the term 'Form1', as we did in the C program, and we used the PowerBASIC VarPtr function to extract the address of the string itself. A Byte Ptr was used to move along the string and print out characters and addresses. The important point to take home though is that to obtain the address of an Asciiz string we are going to have to use the VarPtr function. An important rule emerges from this that will be extremely helpful to you in your interpretation of Api documentation, and the rule is that when filling out types that contain a pointer term, i.e., they need the address of a asciiz string, or the address of a structure such as the RECT structure, you are going to have to use the VarPtr function to obtain the required address. The second part of the rule is that if a pointer is required for a parameter in a function call (and this is a very different thing), if the parameter is being passed by reference, then you just substitute the variable in the function call without using the VarPtr function on it. The reason you can do this should be clear to you by now. PowerBASIC will pass the required address as part of the setup of the function call.

    Having laid this important groundwork, we are now in a position to fully understand how to fill out the WNDCLASSEX type so that we can create a window with PowerBASIC. Below is finally Form1.bas. I have made it about as short and simple as I could. Please compile and run it. Explanations will follow
    afterwards.

    Code:
    Program Name Form1.bas
    #Compile Exe
    #Include "Win32api.inc"        'Equates, declares, types translated from Windows.h
    
    Function WndProc(ByVal hWnd As Long,ByVal wMsg As Long,ByVal wParam As Long,ByVal lParam As Long) As Long
      If wMsg=%WM_DESTROY Then       'This is the all important window procedure.  It is not called anywhere
         Call PostQuitMessage(0)     'in this program.  Windows itself calls this function when any event
         WndProc=0                   'occurs that pertains to this window.  The runtime address of this
         Exit Function               'procedure was obtained for Windows by PowerBASIC's CodePtr function
      End If                         'back in WinMain() when the wc.lpfnWndProc member was set in the
                                     'WndClassEx type variable.  All this particular WndProc is interested in
      WndProc=DefWindowProc(hWnd,wMsg,wParam,lParam) 'is being WM_DESTROY'ed when a %WM_DESTROY message comes
    End Function                                     'through
    
    Function WinMain(ByVal hIns As Long,ByVal hPrev As Long,ByVal lpCmdLn As Asciiz Ptr,ByVal iShow As Long) As Long
      Local szClassName As Asciiz*6 'The address of this string containing the Class Name will be fed to a WndClassEx type
      Local wc As WndClassEx        'A variable of this type needs to be filled out its address passed to RegisterClassEx()
      Local hMainWnd As Dword       'Handle of Main Window returned by CreateWindowEx() Call
      Local Msg As tagMsg           'Type defined in Win32Api.inc that Windows uses to pass message information
    
      szClassName             ="Form1"
      wc.cbSize               =SizeOf(wc)
      wc.style                =0
      wc.lpfnWndProc          =CodePtr(WndProc)    'Critically important!  Address of window procedure!
      wc.cbClsExtra           =0
      wc.cbWndExtra           =0
      wc.hInstance            =hIns  'this was obtained from the first parameter in WinMain()
      wc.hIcon                =LoadIcon(%NULL,ByVal %IDI_APPLICATION)  'Api call to LoadIcon()
      wc.hCursor              =LoadCursor(%NULL,ByVal %IDC_ARROW)      'Api call to LoadCursor()
      wc.hbrBackground        =GetStockObject(%LTGRAY_BRUSH)           'Api call to set window color
      wc.lpszMenuName         =%NULL
      wc.lpszClassName        =VarPtr(szClassName) 'Simple, but very important!
      wc.hIconSm              =LoadIcon(%NULL,ByVal %IDI_APPLICATION)
      Call RegisterClassEx(wc)'This call registers the Form1 class with Windows
      hMainWnd=CreateWindowEx(0,szClassName,"Form1",%WS_OVERLAPPEDWINDOW,200,100,325,300,%HWND_DESKTOP,0,hIns,ByVal 0)
      Call ShowWindow(hMainWnd,iShow)
      While GetMessage(Msg,%NULL,0,0)    'infamous message loop.  WinMain() will be running this message processing
        Call TranslateMessage(Msg)       'loop the entire time your application is running.  When Windows detects that
        Call DispatchMessage(Msg)        'any event pertaining to this program occurs, it will GetMessage() here and
      Wend                               'route the message to the Window Procedure by calling it - here WndProc()
    
      WinMain=msg.wParam
    End Function
    From our first simple explorations with SystemParametersInfo(), you should be able to identify all the elements of this program. Almost at the top, just inside the WndProc() function is an equate - %WM_DESTROY. Api functions are all over the place. The first one encountered is PostQuitMessage(). You should know where to find out about this function by now. If you search your Win Api reference material for PostQuitMessage you'll find a description of it. If you do a 'Find' in Win32Api.inc you'll come up with the DECLARE. But my purpose here is not to get to involved with the Window Procedure. I want you to understand WinMain().

    Back at the very beginning I listed the four steps for creating a window and you can see three of them in WinMain(). At the top a variable of WndClassEx type is dimensioned. Most of the bulk of WinMain() is the filling out of the fields of this type. You can think of the type as representing general charactistics of a Window you want to create. In this case it is a pretty much 'plain jane' window. I made it as simple as possible. A lot of it is 'boilerplate'. Once the fields are filled out the very important function RegisterClassEx() is called. If you by chance have any C documentation on this function handy, perhaps the Api docs or even Charles Petzold's often recommended Windows books you'll see that a pointer to the WNDCLASSEX variable needs to be passed to this function. And, being as you have studied very hard and understood everything I explained above about passing parameters in C and PowerBASIC, you clearly see that all you have to do in this PowerBASC program is stick that wc variable between the brackets in the function call because PowerBASIC will pass its address to the Api function.

    That finally brings us to the BIG ONE - the CreateWindowEx() call. This is the call that will attempt to create a specific instance of the Window Class you just registered. At this point we are no longer talking generalities. We want to create a window of a specific class - specifically the szClassName class. We want it to have a very specific caption, i.e., "Form1'. The equate %WS_OVERLAPPEDWINDOW is a composite style we would like the window to have. These can be found in your docs and in Win32Api.inc.

    We want the window to be 200 pixels from the left border of the screen, 100 from the top, 325 pixels wide and 300 pixels deep. Finally, the parent of the window should be the desktop (another equate).

    The instant the CreateWindowEx() function executes, and before it returns to hit ShowWindow(), Windows will send a %WM_CREATE message to the window procedure listed in the lpfnWndProc (long pointer to function Window procedure) member of the WndClassEx type variable registered for the szClassName member of the type.

    That is the first instant the program will have access to a valid window handle for the program (the hWnd will come through in WndProc()). After the function returns the ShowWindow() function will cause the window to become visible. After that the program will drop into the message loop, and keep spinning there until a %FALSE or 0 value is received by the GetMessage() function. In that case the program will end and WinMain() return.

    This program of course doesn't do much, although it took a fair amount to get us here, didn't it?

    Well, to get the program to do anything we pretty much have to leave WinMain() and move on to the Window Procedure. I'd venture to say that Windows programmers don't spend much time fooling around in WinMain().

    That is pretty much a copy and paste deal and out! Next we'll move to WndProc()!
    Last edited by Fred Harris; 31 Aug 2007, 08:24 AM.
    Fred
    "fharris"+Chr$(64)+"evenlink"+Chr$(46)+"com"

  • #2
    Form2.bas

    The one thing Form1.bas has in common with Form2.bas is that they both have only two procedures
    within themselves, i.e., a WinMain() function, and a Window Procedure - WndProc(). Looking a
    little closer you'll see that while the WinMain() function is virtually identical in both
    programs, the Window Procedure - WndProc() - is much larger in Form2.bas. By this time you
    certainly should have compiled and ran Form1.bas, and when you did that you would have seen
    that while the program did create a normal window on your computer desktop, the program
    otherwise didn't do much. And I don't say this to belittle in any way the effort it took us to
    get to the point of creating that first very simple window, because there certainly are many
    concepts one must understand to get even that far. Don't ever let any naysayers about Windows
    fool you! It is an extremely elegant, complex, sophisticated operating environment, and,
    because of this, programming for it requires mastering some complex architectural issues. But
    if you compile and run Form2.bas, you'll finally see a program that actually does something
    other that wait for its own destruction through the click of the'x' button in its title bar.
    After dismissing an initial message box about which I'll have more to say momentarily, you'll
    see a small white window with two lines of text. If you move your mouse pointer anywhere over
    the window it will rapidly and accurately display for you in its first line the present pixel
    location of the mouse pointer. Also, if you drag either or both borders of the window to
    resize it, the window will immediately update that second line of text to reflect the new
    window width and height dimensions. But there's still more the window will do, but you
    probably havn't figured it out. Try typing something with your keyboard, your name, for
    example. Anything you type will show up on the Window's third line if the window has the
    focus. Finally, if you do a left mouse button click anywhere in the client area of the window
    a location message of the click will be drawn at that point on the window.

    Being as the WinMain() function is virtually identical in both programs one can only come to
    the conclusion that the additional functionalities of Form2 stem from additional code in
    Form2's window procedure. And this is indeed the case. Lets back up a second and examine
    Form1's meager window procedure before moving on to Form2. As you may recall, we purposefully
    disregarded discussing the window procedure back in Form1, but now is the time to examine it
    carefully. Reproduced below is the Window Procedure from Form1.bas...

    Code:
    Function WndProc(ByVal hWnd As Long,ByVal wMsg As Long,ByVal wParam As Long,ByVal lParam As Long) As Long
      If wMsg=%WM_DESTROY Then   'This is the all important window procedure.  It is not called anywhere
         Call PostQuitMessage(0) 'in this program.  Windows itself calls this function when any event
         WndProc=0               'occurs that pertains to this window.  The runtime address of this
         Exit Function           'procedure was obtained for Windows by PowerBASIC's CodePtr function
      End If                     'back in WinMain() when the wc.lpfnWndProc member was set in the
                                 'WndClassEx type variable.  All this particular WndProc is interested in
      WndProc=DefWindowProc(hWnd,wMsg,wParam,lParam) 'is being WM_DESTROY'ed when a %WM_DESTROY message comes
    End Function
    The Window Procedure (often termed a 'callback function') is a function which is called by the
    Windows operating system itself to notify a program that an event has occurred which pertains
    to that program. There are hundreds of events possible, and every one has a corresponding
    equate defined in the windows header files. To end Form1 you must click on the little 'x'
    button in its title bar, and when you do that Windows sends the program several messages, among
    them %WM_CLOSE and %WM_DESTROY, and you can see the If statement testing the wMsg parameter of
    the WndProc() function looking for that %WM_DESTROY message. Any messages the program receives
    that are not %WM_DESTROY messages will not enter the body of the If statement, but will rather
    fall through to a call to DefWindowProc() in the function's terminating statement. This is
    known as calling Windows 'default window procedure'. All messages Windows sends to the window
    procedure should be handled either by code you yourself provide, or by the default window
    procedure. Note that when a %WM_DESTROY message is received and program logic enters the body
    of the If statement, WndProc() is set equal to zero and the function makes an early exit. Zero
    is the most common value to return to the operating system when a program handles a message,
    although a few messages require other return values (more about that later). The thing that
    causes the program to end, however, is not the WW_DESTROY message. That only causes the main
    window to be destroyed. The program is still running down in WinMain(), only now it doesn't
    have any windows! The program's destruction is caused by the PostQuitMessage(0) call. That
    causes the message pump, i.e., message processing loop, to terminate and WinMain() returns.

    Moving on to Form2's WndProc() you will note that it lacks an If statement in its window
    message processing logic. A Select Case was used instead because this particular window
    procedure is testing for seven different messages. If you recall back in Form1.bas I stated
    that when WinMain() executes a CreateWindow() call, a %WM_CREATE message will be sent to the
    window's window procedure. This is the first message a window will receive upon creation. It
    is in most instances the best place to place initialization code for a window that you are
    creating, especially if it will be a top level main program window which you will adorn with
    textboxes, listboxes, etc. Also, at the time of the WM_CREATE message the window is not yet
    visible, and this message will be received in the window procedure before the CreateWindow()
    call in WinMain() returns. So, the first place a valid window handle for this window will
    become available is through the first parameter in the window procedure's parameter list, here
    hWnd, during that WM_CREATE message. Window handles are 32 bit numbers that Windows assigns to
    windows upon their creation.

    In this particular WndProc() there are six lines of code that execute when the %WM_CREATE
    message is received. The purpose of those six lines of code is to determine on your computer
    system how high in pixels a character is in terms of your computer configuration. I can imagine
    at this point that probably sounds outrageous to you but it isn't really so outrageous when
    you consider that the program prints several lines of text to your screen, and for decent
    looking output it is necessary to know at what spacing the lines of text should be drawn. Don't
    forget we're not in DOS anymore. Even text is graphics. Things have gotten more complicated!

    If you search your Windows Api documentation you will find a TYPE known as TEXTMETRIC. We have
    declared a local in WndProc() of this type, i.e., 'tm', and we pass the address of this variable
    to the GetTextMetrics() Api function (exactly like we did with rc of type RECT back in Form1)
    and when it returns the tm.tmHeight member variable will contain the character height we are
    looking for. In order to call GetTextMetrics() you see we needed something called a handle to a
    device context ( hDC ) and this we obtained with the GetDC() api call. I know I'm hitting you
    with some heavy stuff here fast and furious, and if your eyes are starting to glaze over don't
    worry too much, as you'll eventually get through this if you stick with it. Let me give you a
    little hint though if the above is a bit too much at this point. If you eliminate wCharHt from
    the program, then completely delete the WM_CREATE message from the WndProc() function, then
    finally replace both occurances of wCharHt with 16 in WM_PAINT, the program will probably work
    exactly the same for you! This might be a good exercise for you to try out in any case.

    Continuing then, I store the character height in a statically dimensioned variable, wCharHt,
    and this is the value you see displayed in the small message box when the program first starts.
    Note that you see the message box before you see the program window, because, like I said, we
    are still in the processing of the WM_CREATE message, and the CreateWindow() call down in
    WinMain() hasn't even returned yet. This is still before the ShowWindow() call in WinMain()
    that will make the window visible.

    Perhaps I had best point out something about the variable declarations at the top of the window
    procedure - WndProc(). Except for hDC, they are all declared as statics. This means that they
    won't be accessable from anywhere else in the program - which protects them from inadvertant
    corruption - but it also means that they will retain their values between invocations of
    WndProc(). This is actually critical, because, believe it or not, depending on the speed of
    your computer, the window procedure may be getting called 25, 50, even 100 times per second,
    depending if windows has placed messages for your program in its 'message queene' (another topic,
    I'm afraid). The reason it is critical here is that, had for example, wCharHt been declared as
    local and its value initialized during the WM_CREATE message, that value would have been lost
    after WM_CREATE processing was complete and the function exited. Then, when the window finally
    became visible and the lines of text needed to be printed, the second and third lines would
    have overwritten the first line. You see, all drawing on the window is done (in this program
    anyway) during the processing of the WM_PAINT message. This fact in itself will probably seem
    very strange to you if you even thought about it, but I'll have a bit more to say on that later.

    However, we havn't gotten that far yet. The next message the window will handle is the WM_SIZE
    message. When ShowWindow() is called down in WinMain(), and before the window becomes visible,
    Windows will send a WM_SIZE message, and here things start to get interesting!

    You may note that so far we've discussed only two of the four parameters in that Window
    Procedure, i.e., WndProc(). We've shown that the 1st parameter, hWnd, is the Window Handle to
    which the message, 'wMsg', applies. In this program there is only one main window so most
    messages this window's WndProc() will receive pertain to this specific window. Those third and
    fourth parameters, - wParam and lParam, are important though. They are message specific
    containers for information Windows wishes for us to know concerning the specific message in
    question. In the case of the WM_SIZE message the low 16 bits of that 32 bit lParam variable
    will contain the width of the window in pixels, and the high 16 bits will contain the window
    height. These values are obtained with the LOWRD() and HIWRD() macros. After obtaining these
    values (which, by the way are held in variables of static duration) an Api function named
    InvalidateRect() is called, and this call forces windows to send a WM_PAINT message to the
    Window Procedure - WndPrioc(). This is how the drawing of text on the window occurs. Note
    that even if the InvalidateRect() call wasn't made the window would have received a WM_PAINT
    message after a WM_CREATE and WM_SIZE message, but the InvalidateRect() call in WM_SIZE
    specifically relates to the manual resizing you may do with your mouse after the window
    becomes visible.

    Moving on then to the next message this window will receive during its creation, that message
    would be WM_PAINT. This is a very important message if you are drawing any text or graphics on
    a window. If all you are doing is placing windows controls on a window such as text boxes,
    listboxes, etc., it is not importand and can even be omitted. Which brings up another
    important point! All programs don't process every available message. It is only necessary to
    process a message if your program needs to do something in relation to that message. That
    point will eventually become clear to you.

    Starting back in Form1.bas I tried to make the point that the Windows Api documentation was
    critically important to Api programming, and that you need to be able to find your way around
    the Win32Api.inc file. As you can see under the WM_PAINT message, several new Api functions
    are called. And there sits another TYPE too. This time something called a 'PAINTSTRUCT'.

    I'm going to make a sleight digression here. Hopefully I've convinced you by this point that to
    do this API style programming you need quick and efficient access to two other documents. The
    first would be a Windows Api help file and viewer program, and the Win32Api.inc file. I
    personally use the documents that came with my version of Visual Studio 6, and I keep a shortcut
    to that on my desktop. Oftentimes when I'm coding I keep it open and minimized. In terms of
    the Win32Api.inc, I use the Lynx Project Explorer (obtainable free) in conjunction with either
    the JellyFish Pro Editor from Paul Squires, or SED from Jose Roca's web site. With this Lynx
    Project Explorer, if you need information on an equate, a TYPE, a DECLARE, or anything else, its
    only a few mouse clicks and a second or two away. This is infinitely easier than opening a
    header file in your programming editor and doing a search for any of these items. For example,
    right now I'm looking at that PAINTSTRUCT in the left pane of the SED editor, and I can see all
    the fields of which it is comprised. If I want to actually view the TYPE in the header file
    itself, that would be one more click away. Highly, highly recommended for this work!

    So, BeginPaint() and EndPaint() are two Api functions you must call if you intend to handle the
    WM_PAINT message in the Window Procedure. BeginPaint() is a function whose return value is a
    handle to a device context as we met in WM_CREATE when attempting to obtain character heights
    associated with the current font selected into the device context. If you wish to draw to the
    screen in messages other than WM_PAINT then you use GetDC() for your device context handle. In
    WM_PAINT though you need BeginPaint() and EndPaint(). If you look up BeginPaint() in your Api
    reference you'll see this...

    Code:
    BeginPaint
    '
    The BeginPaint function prepares the specified window for painting and fills a PAINTSTRUCT
    structure with information about the painting.
    '
    HDC BeginPaint
    (
      HWND             hwnd,       // handle to window
      LPPAINTSTRUCT    lpPaint     // pointer to structure for paint information
    );
    '
    Parameters
    '
    hwnd      Handle to the window to be repainted.
    lpPaint   Pointer to the PAINTSTRUCT structure that will receive painting information.
    '
    Return Values
    '
    If the function succeeds, the return value is the handle to a display device context for the
    specified window.
    '
    As you see below under WM_PAINT, this is how I satisfied these requirements in PowerBASIC...
    '
    Local ps As PAINTSTRUCT
    hDC=BeginPaint(hWnd,ps)
    '
    The ps variable is of course a variable (complex type) of TYPE PAINTSTRUCT. Since the placement
    of that variable in the second position of the function call will cause PowerBASIC to pass the
    variable's address to the BeginPaint() function, this satisfies the function's requirement of
    needing a pointer to a PAINTSTRUCT structure. Why? Because, as I'm sure you've either heard or
    read a thousand times, PowerBASIC passes variables to functions 'By Reference', which means the
    variable's address gets passed, which address is the definition of a pointer! You may wish to
    examine the PowerBASIC DECLARE for BeginPaint() at this time to prove this to yourself. And
    where would that DECLARE be? You guessed it! In none other than your Win32Api.inc file!

    Although I don't use any of the fields in the PAINTSTRUCT type after BeginPaint() is called, we
    nontheless needed to make the call in order to use the TextOut() function to output text.
    TextOut() for Windows is like 'Print' for DOS or Console mode PowerBASIC. The handle to a
    device context returned by BeginPaint() is needed as a parameter to TextOut(). The TextOut()
    function is covered in some depth in Form3.bas (the next program in this tutorial), but I'll
    cover it some here too. It is defined like so...

    Code:
    TextOut
    '
    The TextOut function writes a character string at the specified location, using the currently
    selected font, background color, and text color.
    '
    BOOL TextOut
    (
      HDC hdc,           // handle to device context
      int nXStart,       // x-coordinate of starting position
      int nYStart,       // y-coordinate of starting position
      LPCTSTR lpString,  // pointer to string
      int cbString       // number of characters in string
    );
    '
    '
    Parameters
    '
    hdc           Handle to the device context.
    nXStart       Specifies the logical x-coordinate of the reference point that the system uses to
                  align the string.
    nYStart       Specifies the logical y-coordinate of the reference point that the system uses to
                  align the string.
    lpString      Pointer to the string to be drawn. The string does not need to be zero-terminated,
                  since cbString specifies the length of the string.
    cbString      Specifies the number of characters in the string.
    While I didn't cover the WM_MOUSEMOVE processing, it isn't at all different from the WM_SIZE
    processing which I did cover. The first line of text to be drawn by TextOut() during the
    WM_PAINT processing is a line that contains the coordinates of the mouse pointer if you have it
    over the window. The code from WM_PAINT is as follows...
    '
    Local szLine As Asciiz*32
    '
    hDC=BeginPaint(hWnd,ps)
    szLine="MouseX="+Trim$(Str$(xCoord)) & " MouseY="+Trim$(Str$(yCoord))
    TextOut(hDC,0,0,szLine,Len(szLine))
    '
    I used an Asciiz string to contain the text as they are a bit easier to use in Api functions
    than dynamic strings. Asciiz strings are assembler or C strings that are essentially just a
    fixed length buffer containing the characters to be drawn and terminated by a NULL character.
    This documentation states that the string doesn't even have to be null terminated, but
    PowerBASIC will null terminate it anyway. The 0,0 in the function call tell TextOut() to start
    printing in the upper left corner of the window. szLine is the character buffer (string) to
    draw, and Len(szLine) is the required length.

    The second line of text will contain the window size as determined during the processing of the
    WM_SIZE message. Perhaps now you can see why these variables were declared as statics as they
    must retain their values across invocations of WndProc(). Note also the overall architecture
    of the program where a WM_SIZE, WM_MOUSEMOVE or WM_CHAR message is received, and an
    InvalidateRect() call is made to force a WM_PAINT message, which message causes the updating or
    painting of the window. 'Invalidate' and 'Validate' are terms you will come across in indows
    rogramming books and the documentation, and refer to internal data structures Windows uses to
    keep track of which windows or parts of windows are no longer valid and are in need of
    repainting. When Windows determines that a window's contents are no longer valid, it will send
    that window a WM_PAINT message, and that window must be prepared to replace the contents of that
    window, as we have done here. We had to do that here because the purpose of this program was to
    demonstrate these things to you the reader, and to do that we output text of ever changing
    content as you moved the mouse, drug the window borders, or typed text. If instead the Window
    only 'contained windows controls such as text boxes or combo boxes of which Windows itself was
    responsible, then we wouldn't have had to do anything special in our WM_PAINT message. In fact,
    we wouldn't have even needed a WM_PAINT message handler in our program, as Windows would have
    taken care of the restoration of these controls in the internal WM_PAINT message handlers of the
    controls themselves, none of which would have been our responsibility.

    Finally, I should discuss the processing of keypresses, or, more accurately, WM_CHAR messages.
    Up to this point I have led you through the three messages this window will receive during its
    creation for which message handling code is in place. The three messages are WM_CREATE,
    WM_SIZE, and WM_PAINT. After the receipt of these three messages the window will be visible
    on the screen. The next messages to be processed will depend on what you, the user, does. If
    you move the mouse pointer across the window the program's window procedure will be barraged
    with WM_MOUSEMOVE messages. For every message the program receives the code under WM_MOUSEMOVE
    will execute, and the execution of that code will cause another message to be sent to the
    window procedure to repaint the window and the mouse pointer coordinate position will be
    updated along with the rest of the window. Somewhat similiar processing will occur with any
    resizing of the window borders or minimize or maximize operations. The processing of WM_CHAR
    messages is only slightly different.

    Remember when I stated that the wParam and lParam parameters are containers for message packets
    of information windows wishes us to have concerning the specific message sent. Well, that's
    the case here too with WM_CHAR messages. These messages are received whenever a character key
    is pressed when the window in question has the focus. Windows places the decimal value of the
    character's Asciiz code in the wParam parameter. PowerBASIC's Chr$() function easily converts
    this code to the character itself. This statement...

    szText=szText+Chr$(wParam)

    concatenates each character to a statically allocated Asciiz buffer, and the InvalidateRect()
    call invokes its printing on the third line of the window through the processing of the
    WM_PAINT message.

    In terms of the WM_LBUTTONDOWN message processing, I wasn't going to put that in this program,
    as I was going to leave that as a recommendation for you to try it out yourself. But I thought
    I had better at least try it out first to see whether or not it might be too difficult for a
    beginner, and in doing that I finished it and left it in the program. I guess I want all the
    glory and fun for myself! But all is not lost! There are still middle buttons, the right
    button, as well as various control and shift key states you could try. I would like you to try
    though. Why don't you attempt to duplicate what I did with the left button, but do it with
    the right button? I'll even give you a hint! In your Win Api help do a search for the
    WM_LBUTTONDOWN message or the WM_RBUTTONDOWN message.

    The program is just below. I left several lines of code in the program that are commented out.
    The first one of these is a line declaring a global variable 'fp' for file pointer or 'handle'.
    All the lines relating to this variable are commented out in the program, but you might want to
    un-comment these lines so as to allow the program to open up an Output.txt file in the directory
    you are running the program. This would be useful to you in seeing the number of times Windows
    actually calls the Window Procedure while you are moving the mouse, re-sizing borders, typing
    text, clicking the mouse button, etc. It may be quite an eye opener for you.

    Finally, right below the program is a second version of the program that shows the way I
    personally code Windows Sdk style programs. In other words, it is my own personal style, and it
    is a bit different from standard Sdk style you'll see in books. It shows a highly modularized
    approach to coding whereby each message is channelled to its own message handling function for
    processing. All that remains of the Window Procedure then is a small 'switchboard' structure
    which directs processing elsewhere as each message is received. Also, I have a personal
    preference for short parameter lists, so instead of passing the four Window Procedure parameters
    to each message handler function, I agglomerate them into a single type, i.e., WndEventArgs
    (Windows Event Arguements) and this type I pass to the message handlers. The reason I came by
    this style is that when I first taught myself Windows programming back in the nineties with C,
    and I began putting real programs together as opposed to just demonstration programs like this
    one, I ended up with final programs with only two procedures in them, i.e., a WinMain() function
    with about 40 lines of code, and a WndProc() with 2000 or 3000 lines of code! And jumbled up
    at the top of the program and at the top of WndProc() were tons of globals! It was truely
    miserable and would have been my end if I hadn't come up with some way to straighten it out. The
    second program shows how I straightened out the mess. It was really no great intellectual leap
    for me to come by what I did. During the nineties I coded a lot with MFC and Visual Bacic, and
    both these development environments encapsulated code into message processing routines. Later,
    with VB.NET and C#.NET I noted that parameters from the window procedure were agglomerated into
    types such as 'pea' for 'Paint Event Arguments', for example, in Paint Event Handlers. So I
    borrowed from there. Anyway, here is pure SDK style Form2.bas...

    Code:
    'Program Name Form2.bas
    #Compile Exe
    #Include "Win32api.inc"  'Equates, declares, types translated from Windows.h
    'Global fp As Integer
    '
    '
    Function WndProc(ByVal hWnd As Long,ByVal wMsg As Long,ByVal wParam As Long,ByVal lParam As Long) As Long
      Static xCoord As Word, yCoord As Word, wWidth As Word, wHeight As Word, wCharHt As Word
      Static xPos As Word, yPos As Word
      Static szText As Asciiz*256
      Local hDC As Long
      '
      Select Case As Long wMsg
        Case %WM_CREATE
          'fp=Freefile
          'Open "Output.txt" For Output As #fp
          'Print #fp,"In WM_CREATE"
          Local tm As TEXTMETRIC
          hDC=GetDC(hWnd)
          Call GetTextMetrics(hDC,tm)
          wCharHt=tm.tmHeight
          Call ReleaseDC(hWnd,hDC)
          MsgBox("wCharHt=" & Trim$(Str$(tm.tmHeight)))  'Remark this out and use 
          'Print "wCharHt=" & Trim$(Str$(tm.tmHeight))   'Print if using Console Compiler
        Case %WM_MOUSEMOVE
          'Print #fp,"In WM_MOUSEMOVE"
          xCoord=LoWrd(lParam) : yCoord=HiWrd(lParam)
          Call InvalidateRect(hWnd,ByVal 0,%TRUE)
          WndProc=0
          Exit Function
        Case %WM_LBUTTONDOWN
          'Print #fp,"In WM_LBUTTONDOWN"
          If wParam=%MK_LBUTTON Then
             xPos=LoWrd(lParam) : yPos=HiWrd(lParam)
             Call InvalidateRect(hWnd,ByVal 0,%TRUE)
             WndProc=0
             Exit Function
          End If
        Case %WM_SIZE
          'Print #fp,"In WM_SIZE"
          wWidth=LoWrd(lParam) : wHeight=HiWrd(lParam)
          Call InvalidateRect(hWnd,ByVal 0,%TRUE)
          WndProc=0
          Exit Function
        Case %WM_CHAR
          'Print #fp,"In WM_CHAR"
          szText=szText+Chr$(wParam)
          Call InvalidateRect(hWnd,ByVal 0,%TRUE)
          WndProc=0
          Exit Function
        Case %WM_PAINT
          'Print #fp,"In WM_PAINT"
          Local szLine As Asciiz*32
          Local ps As PAINTSTRUCT
          hDC=BeginPaint(hWnd,ps)
          szLine="MouseX="+Trim$(Str$(xCoord)) & "  MouseY="+Trim$(Str$(yCoord))
          TextOut(hDC,0,0,szLine,Len(szLine))
          szLine="wWidth="+Trim$(Str$(wWidth)) & " wHeight="+Trim$(Str$(wHeight))
          TextOut(hDC,0,wCharHt,szLine,Len(szLine))
          TextOut(hDC,0,wCharHt*2,szText,Len(szText))
          If xPos<>0 And yPos<>0 Then
             szLine="WM_LBUTTONDOWN At (" & Trim$(Str$(xPos)) & "," & Trim$(Str$(yPos)) & ")"
             TextOut(hDC,xPos,yPos,szLine,Len(szLine))
             xPos=0 : yPos=0
          End If
          Call EndPaint(hWnd,ps)
          WndProc=0
          Exit Function
        Case %WM_DESTROY
          'Print #fp,"IN WM_DESTROY"
          'Close #fp
          Call PostQuitMessage(0)
          WndProc=0
          Exit Function
      End Select
      '
      WndProc=DefWindowProc(hWnd,wMsg,wParam,lParam)
    End Function
    '
    '
    Function WinMain(ByVal hIns As Long,ByVal hPrevIns As Long,ByVal lpCmdLine As Asciiz Ptr,ByVal iShow As Long) As Long
      Local szClassName As Asciiz*6
      Local hMainWnd As Dword
      Local wc As WndClassEx
      Local Msg As tagMsg
      '
      szClassName="Form2"
      wc.cbSize=SizeOf(wc)                               : wc.style=0
      wc.lpfnWndProc=CodePtr(WndProc)                    : wc.cbClsExtra=0
      wc.cbWndExtra=0                                    : wc.hInstance=hIns
      wc.hIcon=LoadIcon(%NULL,ByVal %IDI_APPLICATION)    : wc.hCursor=LoadCursor(%NULL,ByVal %IDC_ARROW)
      wc.hbrBackground=GetStockObject(%WHITE_BRUSH)      : wc.lpszMenuName=%NULL
      wc.lpszClassName=VarPtr(szClassName)               : wc.hIconSm=LoadIcon(%NULL,ByVal %IDI_APPLICATION)
      Call RegisterClassEx(wc)
      hMainWnd=CreateWindowEx(0,szClassName,"Form2",%WS_OVERLAPPEDWINDOW,200,100,325,300,%HWND_DESKTOP,0,hIns,ByVal 0)
      Call ShowWindow(hMainWnd,iShow)
      While GetMessage(Msg,%NULL,0,0)
        Call TranslateMessage(Msg)
        Call DispatchMessage(Msg)
      Wend
      '
      WinMain=msg.wParam
    End Function

    .....and here is the second version I promised showing a different architecture for the program
    taking advantage of modularization. You will note no globals and no piling of variables at
    the top of the window procedure. All variables are declared as needed in the various
    message handling procedures...


    Code:
    'Program Name Form2.bas
    #Compile Exe
    #Include "Win32api.inc"  'Equates, declares, types translated from Windows.h
    '
    Type WndEventArgs
      wParam  As Long
      lParam  As Long
      hWnd    As Dword
      hInst   As Dword
      wWidth  As Word
      wHeight As Word
      wX      As Word
      wY      As Word
      wCharHt As Word
      xPos    As Word
      yPos    As Word
      szText  As Asciiz*128
    End Type
    '
    '
    Function fnWndProc_OnCreate(wea As WndEventArgs) As Long
      Local tm As TEXTMETRIC
      Local hDC As DWord
      '
      hDC=GetDC(wea.hWnd)
      Call GetTextMetrics(hDC,tm)
      wea.wCharHt=tm.tmHeight
      Call ReleaseDC(wea.hWnd,hDC)
      MsgBox("wea.wCharHt=" & Trim$(Str$(tm.tmHeight))) 'If using Console Compiler remark this
      'Print "wea.wCharHt=" & Trim$(Str$(tm.tmHeight))  'line out and use Print instead!
      '
      fnWndProc_OnCreate=0
    End Function
    '
    '
    Function fnWndProc_OnMouseMove(wea As WndEventArgs) As Long
      wea.wX=LoWrd(wea.lParam) : wea.wY=HiWrd(wea.lParam)
      Call InvalidateRect(wea.hWnd,ByVal %NULL,%TRUE)
      '
      fnWndProc_OnMouseMove=0
    End Function
    '
    '
    Function fnWndProc_OnSize(wea As WndEventArgs) As Long
      wea.wWidth=LoWrd(wea.lParam) : wea.wHeight=HiWrd(wea.lParam)
      Call InvalidateRect(wea.hWnd,ByVal %NULL,%TRUE)
      '
      fnWndProc_OnSize=0
    End Function
    '
    '
    Function fnWndProc_OnChar(wea As WndEventArgs) As Long
      wea.szText=wea.szText+Chr$(wea.wParam)
      Call InvalidateRect(wea.hWnd,ByVal %NULL,%TRUE)
      '
      fnWndProc_OnChar=0
    End Function
    '
    '
    Function fnWndProc_OnLButtonDown(wea As WndEventArgs) As Long
      If wea.wParam=%MK_LBUTTON Then
         wea.xPos=LoWrd(wea.lParam) : wea.yPos=HiWrd(wea.lParam)
         Call InvalidateRect(wea.hWnd,ByVal 0,%TRUE)
      End If
      '
      fnWndProc_OnLButtonDown=0
    End Function
    '
    '
    Function fnWndProc_OnPaint(wea As WndEventArgs) As Long
      Local szLine As Asciiz*48
      Local ps As PAINTSTRUCT
      Local hDC As Long
      '
      hDC=BeginPaint(wea.hWnd,ps)
      szLine="MouseX="+Trim$(Str$(wea.wX)) & "  MouseY="+Trim$(Str$(wea.wY))
      TextOut(hDC,0,0,szLine,Len(szLine))
      szLine="wea.wWidth="+Trim$(Str$(wea.wWidth)) & " wea.wHeight=" + Trim$(Str$(wea.wHeight))
      TextOut(hDC,0,16,szLine,Len(szLine))
      TextOut(hDC,0,32,wea.szText,Len(wea.szText))
      If wea.xPos<>0 And wea.yPos<>0 Then
         szLine="WM_LBUTTONDOWN At (" & Trim$(Str$(wea.xPos)) & "," & Trim$(Str$(wea.yPos)) & ")"
         TextOut(hDC,wea.xPos,wea.yPos,szLine,Len(szLine))
         wea.xPos=0 : wea.yPos=0
      End If
      Call EndPaint(wea.hWnd,ps)
      '
      fnWndProc_OnPaint=0
    End Function
    '
    '
    Function WndProc(ByVal hWnd As Long,ByVal wMsg As Long,ByVal wParam As Long,ByVal lParam As Long) As Long
      Static wea As WndEventArgs
      '
      Select Case As Long wMsg
        Case %WM_CREATE
          wea.hWnd=hWnd : wea.lParam=lParam : wea.wParam=wParam
          WndProc=fnWndProc_OnCreate(wea)
          Exit Function
        Case %WM_MOUSEMOVE
          wea.hWnd=hWnd : wea.lParam=lParam : wea.wParam=wParam
          WndProc=fnWndProc_OnMouseMove(wea)
          Exit Function
        Case %WM_SIZE
          wea.hWnd=hWnd : wea.lParam=lParam : wea.wParam=wParam
          WndProc=fnWndProc_OnSize(wea)
          Exit Function
        Case %WM_CHAR
          wea.hWnd=hWnd : wea.lParam=lParam : wea.wParam=wParam
          WndProc=fnWndProc_OnChar(wea)
          Exit Function
        Case %WM_LBUTTONDOWN
          wea.hWnd=hWnd : wea.lParam=lParam : wea.wParam=wParam
          WndProc=fnWndProc_OnLButtonDown(wea)
          Exit Function
        Case %WM_PAINT
          wea.hWnd=hWnd : wea.lParam=lParam : wea.wParam=wParam
          WndProc=fnWndProc_OnPaint(wea)
          Exit Function
        Case %WM_DESTROY
          Call PostQuitMessage(0)
          WndProc=0
          Exit Function
      End Select
      '
      WndProc=DefWindowProc(hWnd,wMsg,wParam,lParam)
    End Function
    '
    '
    Function WinMain(ByVal hIns As Long,ByVal hPrevIns As Long,ByVal lpCmdLine As Asciiz Ptr,ByVal iShow As Long) As Long
      Local szClassName As Asciiz*6
      Local wc As WndClassEx
      Local hMainWnd As Dword
      Local Msg As tagMsg
      '
      szClassName="Form1"
      wc.cbSize=SizeOf(wc)                               : wc.style=0
      wc.lpfnWndProc=CodePtr(WndProc)                    : wc.cbClsExtra=0
      wc.cbWndExtra=0                                    : wc.hInstance=hIns
      wc.hIcon=LoadIcon(%NULL,ByVal %IDI_APPLICATION)    : wc.hCursor=LoadCursor(%NULL,ByVal %IDC_ARROW)
      wc.hbrBackground=GetStockObject(%WHITE_BRUSH)      : wc.lpszMenuName=%NULL
      wc.lpszClassName=VarPtr(szClassName)               : wc.hIconSm=LoadIcon(%NULL,ByVal %IDI_APPLICATION)
      Call RegisterClassEx(wc)
      hMainWnd=CreateWindowEx(0,szClassName,"Form1",%WS_OVERLAPPEDWINDOW,200,100,325,300,%HWND_DESKTOP,0,hIns,ByVal 0)
      Call ShowWindow(hMainWnd,iShow)
      While GetMessage(Msg,%NULL,0,0)
        Call TranslateMessage(Msg)
        Call DispatchMessage(Msg)
      Wend
      '
      WinMain=msg.wParam
    End Function


    [This message has been edited by Fred Harris (edited August 22, 2007).]
    Fred
    "fharris"+Chr$(64)+"evenlink"+Chr$(46)+"com"

    Comment


    • #3
      Form3.bas Compilation Tested With PBWin 8.01, 7.04, 6.11
      '*****************************************************************************************
      'Introduction
      '*****************************************************************************************
      The detractors of Sdk programming style will oftentimes cite the unwieldy WndProc()
      function with its massive Select Case construct that in bad cases can run on for
      thousands of lines of code. This is because that all important function is where
      every message deriving from user input will be channeled. In this program I created
      a TYPE WndEventArgs - Windows Event Arguements - that will be filled out in the Window
      procedure and passed to various message processing procedures created to process only
      one specific message. For example, when the main window is first created with a call
      to CreateWindowEx(), a %WM_CREATE message will be passed to fnWndProc. In that procedure
      the fields of WndEventArgs will be filled in and the procedure

      fnWndProc_OnCreate(wea As WndEventArgs)

      will be called. Likewise, %WM_COMMAND messages deriving from multitudes of sources, i.e.,
      menu selections, listboxes, comboboxes, buttons, etc., will be directed to a switchboard
      procedure fnWndProc_OnCommand() for further redirection of those messages. In that way
      the code can essentially be modularized in the exact manner Visual Basic did with its
      event procedures. See cmdButton1_Click() below.

      Also noteworthy in this program is a CreateWindowEx() call in response to a button click
      (cmdButton2_Click()) where an output screen window is created and displayed. Right above
      the window procedure that handles messages for this second window, fnOutputScreen(), I
      discuss at length the issues involved in translating and calling Win Api C functions from
      PowerBasic, particularly in regard to strings.

      Finally, in this program I broke out the Window Class initialization code from the
      WinMain() function to show how this modularization can be done. While I never do it
      myself, you will frequently see this done in the documentation. The purpose for it is
      to seperate application initialization code (filling out a Window Class type) from
      instance instantiation code (creating the window and falling into the message
      processing loop, and to abort the app if failures occur in either. This was somewhat
      more of an issue with 16 bit Windows, but may have merit again as modern computer
      users have grown accostomed to never closing programs, nor turning off their computers.
      It would be a good idea to compile and run this program before reading explanations below.
      '******************************************************************************************
      'End Introduction
      '******************************************************************************************

      Code:
      '
      #Compile Exe
      #Dim All
      #Include "Win32api.inc"
      Global hMainWnd As Dword         'Main Window Handle
      Global hOutputScreen As Dword    'Output Screen Window Handle
      %IDC_BUTTON1=1
      %IDC_BUTTON2=2
      '
      '******************************************************************************************
      'Three of these four variables are about the most important in windows programming, as they 
      'are the message passing mechanism between your hardware, Windows and your program.  I 
      'packaged them into a TYPE as it tends to keep the parameter list in procedures shorter.  
      'Further, if your program would benifit from it, you can add your own variables to it.
      '******************************************************************************************
      Type WndEventArgs       'Windows Event Arguements
        wParam As Long
        lParam As Long
        hWnd   As Dword
        hInst  As Dword
      End Type
      '
      '******************************************************************************************
      'Function Type:  Window Procedure  
      'Function Name:  fnOutputScreen 
      '******************************************************************************************
      'When we called RegisterClassEx() the second time down in AppInitialize(), we set the
      'window procedure for the OutputScreen class to the address of fnOutputScreen with this
      'statement...
      ' 
      '                     wcl.lpfnWndProc=CodePtr(fnOutputScreen).  
      '
      'For 'lpfnWndProc' read 'long pointer to function window procedure'.  The long pointer 
      'terminology is a hold over from 16 bit Windows.  So all messages to the OutputScreen window
      'are sent to fnOutputScreen.  The only functionality of the window is to print the words 
      '"This Is The Output Screen" shortly after the window is created during the WM_PAINT msg.  
      
      'Since using the Windows Api is something of a skill in itself, let me try to provide a little 
      'insight here.  Below is how the TextOut() function is defined in the Win32 Api docs:
      '
      'BOOL TextOut(  
      '  HDC  hdc,           // handle to device context   
      '  int  nXStart,       // x-coordinate of starting position   
      '  int  nYStart,       // y-coordinate of starting position   
      '  LPCTSTR  lpString,  // pointer to string              <<that ugly thing is a char pointer!
      '  int cbString        // number of characters in string
      ');
      '
      'Of course, this is a C definition of that function, and in that language there is a seemingly
      'infinite number of ways of describing a 32 bit variable because of the typedef keyword, which,
      'among other things, allows for the creation of synonyms for existing data types.  Every one
      'of the variables in the above function declaration are 32 bit integers. The PowerBasic Declare 
      'for that function is in Win32Api.inc and the contents of this file you will have to become 
      'somewhat familiar with to do this style of programming. Here is the PowerBasic declare:
      '
      'DECLARE FUNCTION TextOut LIB "GDI32.DLL" ALIAS "TextOutA" 
      '(
      ' BYVAL hdc AS DWORD, 
      ' BYVAL x AS LONG, 
      ' BYVAL y AS LONG, 
      ' lpString AS ASCIIZ,     'what needs to be passed here is the address of the string
      ' BYVAL nCount AS LONG
      ') AS LONG
      '
      'What I'm going to tell you now is important.  In C the default method of passing a function
      'parameter is to make a copy of the variable and push that copy on the stack for retrieval by 
      'the called function.  In this way the function will not and can not alter the value of the 
      'passed parameter back in the calling function.  In C, if one wants to be able to alter or 
      'directly work with the address and hence value of a function parameter, then a pointer variable 
      'is used. The address held in the pointer variable will be pushed on the stack at the time of the 
      'function call and the function will work directly with that address, so that if any changes to 
      'the variable are made in the called function, upon return from the function back to the calling 
      'function that variable's value will have been changed.  Note that in the C definition of 
      'TextOut() the fourth parameter is a pointer to a character string.  In that function the string 
      'won't be altered in any way, however, TextOut() needs to have the address of the string to 
      'output it to the screen.
      '
      'Now in PowerBasic, the default parameter passing mechanism is by reference, which means that
      'when you stick a variable in a parameter list to a function, that variables address will be
      'directly passed to the function via the stack - the reverse situation from C.  The implication
      'of all this is that in PowerBasic declares of Win32 Api functions you will see non-pointer 
      'parameters with the BYVAL keyword placed before them, and pointer variables oftentimes lacking
      'any parameter passing method keyword due to the fact that by default PowerBasic passes pointers
      'to called functions, i.e., Byref.  That is the situation above with the TextOut() function's 
      'fourth parameter.
      '
      'What TextOut() in GDI32.dll needs to see is the 32 bit address of the first character of the
      'string that needs to be output. In PowerBasic you get that address with the Strptr() function      
      'for dynamic variable length strings, and the Varptr() function for Asciiz strings.  However,
      'to pass that value to the function in PowerBasic you need to over ride the default Byref passing 
      'mechanism with the specific Byval override (see fnOutputScreen below).  
      '
      'Another way to get the function to work is to declare an Asciiz Ptr in the function and do the 
      'following:
      '
      '  Local pszText As Asciiz Ptr
      '
      '  pszText=Strptr(strText)
      '  Call TextOut(hDC,0,0,@pszText,Len(strText))
      '
      'I personally find that a somewhat obscure and obtuse way of doing something that in C is very 
      'straightforward.  Further, it requires an extra variable declaration and an extra statement on
      'top of being obscure and obtuse.  
      '
      'The reason I decided to go into some detail about this issue is that you'll need to understand it
      'to deal with passing strings to other Api functions, for example with comboboxes, listboxes, etc.       
      '******************************************************************************************
      '
      Function fnOutputScreen(ByVal hWnd As Long,ByVal wMsg As Long,ByVal wParam As Long,ByVal lParam As Long) Export As Long
        Local lpPaint As PAINTSTRUCT      'a type needing to be allocated in order to get a handle to a device context 
        Local strText As String           
        Local hDC As Long
      '    
        Select Case wMsg
          Case %WM_PAINT
            hDC=BeginPaint(hWnd,LpPaint)                 'gives you access to graphics drawing capabilities of OS
            strText="This Is The Output Screen!"                     'text to be output
            Call TextOut(hDC,0,0,ByVal StrPtr(strText),Len(strText)) 'windows version of Print statement
            Call EndPaint(hWnd,lpPaint)                  'paired with BeginPaint() >> Releases resources
            fnOutputScreen=0
            Exit Function
          Case %WM_DESTROY
            Call ShowWindow(hOutputScreen,%SW_HIDE)
            Call ShowWindow(hMainWnd,%SW_SHOWNORMAL)
            fnOutputScreen=0
            Exit Function
        End Select  
      '   
        fnOutputScreen=DefWindowProc(hWnd,wMsg,wParam,lParam)
      End Function
      '
      '*********************************************************************************************
      'Sub cmdButton1_Click()    Event Procedure
      '*********************************************************************************************
      'VB.NET refugees like myself should like this one!  In VB, if you start up a blank project, 
      'you'll get a default Form1 which can be run 'as is' by clicking the 'run' button.  If you 
      'double click a command button in the toolbox you'll get a command button added to your 
      'Form1, which, if double clicked on the form opens up a code window with...
      '
      'Private Sub Command1_Click()
      '
      'End Sub
      '
      'Well, below is the way to do it in PowerBasic
      '**********************************************************************************************
      Sub cmdButton1_Click()
        MsgBox("You Clicked Button #1!") 'For Console Compiler Use Print Instead.
        'Print "You Clicked Button #1!"
      End Sub
      
      '**********************************************************************************************
      'cmdButton2_Click(wea As WndEventArgs)   Event Procedure
      '**********************************************************************************************
      'cmdButton2_Click() will be called when the user clicks button #2.  Windows will then
      ''package up' a %WM_COMMAND message for fnWndProc, and it will set the low 16 bits of wParam
      'to the %IDC_BUTTON2, i.e., 2, and the high 16 bits of wParam to %BN_CLICKED, i.e., 0.  When
      'this message is received in fnWndProc, the Select Case logic will sort out the message to
      'a %WM_COMMAND case, fill out the WndEventArgs TYPE, then Call fnWndProc_OnCommand() as 
      'follows:
      '           Case %WM_COMMAND
      '             wea.wParam=wParam: wea.lParam=lParam: wea.hWnd=hWnd
      '             Call fnWndProc_OnCommand(wea)
      '
      'Once inside fnWndProc_OnCommand(), that procedure will use more Select Case logic to 
      'determine which control, i.e., button in our case, is the sender or source of the message.
      'It will do this by examining the low 16 bit value in wea.wParam to get the control ID set
      'in the CreateWindowEx() function when the control was created.  If you look back in 
      'fnWndProc_OnCreate() you'll see it was the tenth parameter in the call. When this procedure
      'determines that the source of the message was %IDC_BUTTON2, it calls cmdButton2_Click()
      'as seen just below. 
      '
      'Note the first function call is to Api function SystemParametersInfo().  Here is the definition
      'for that function from the Win32 Api help:
      '
      'BOOL SystemParametersInfo(  
      ' UINT  uiAction , // system parameter To query Or Set   
      ' UINT  uiParam ,  // depends On action To be taken   
      ' PVOID  pvParam , // depends On action To be taken   
      ' UINT  fWinIni   // User Profile update flag 
      ');
      '
      'See the help file for a full description of this function.  Note our simple use of it to get
      'the desktop dimensions in order to fill the whole screen with the output screen.  This function
      'should show you how easy it is to call Api functions.  They are not all as challenging as TextOut()
      'or DrawText(). Note further that the third parameter is a pointer to a Rect TYPE/structure.  In C 
      'you would Declare a Rect similiar to below, but In the Function Call you would put &rc instead of 
      'rc as seen below.  This Is because In C the '&' is the address of operator which signifies to the 
      'compiler to push the address of rc on the stack, which resolves to an acceptable pointer arguement 
      'for the Function.  In Powerbasic all you have to do is use rc itself as a parameter as Powerbasic 
      'will push the address of rc on the stack due to its default Byref parameter passing convention.
      '****************************************************************************************************
      Sub cmdButton2_Click(wea As WndEventArgs)
        Local rc As Rect
      '
        Call SystemParametersInfo(%SPI_GETWORKAREA,0,rc,0)  'this Api Fn will return the size of the desktop in
        hOutputScreen= _                                    'a Rect structure/type.  Note info returned in param
        CreateWindowEx( _
        0, _                    'no extended window styles, just plain jane window
        "OutputScreen", _       'this is important!  It is the class from which the window will be instantiated
        "Output Screen", _      'caption of window
        %WS_OVERLAPPEDWINDOW, _ 'window style - see CreateWindow or CreateWindowEx in Api docs
        0, _                    'X coord
        0, _                    'Y coord
        rc.nRight, _            'this info returned in SystemParametersInfo()
        rc.nBottom, _           'ditto
        wea.hWnd, _             'all CreateWindow calls require handle of parent window
        0, _
        wea.hInst, _
        Byval 0)
        Call ShowWindow(wea.hWnd,%SW_HIDE)
        Call ShowWindow(hOutputScreen,%SW_SHOWNORMAL)
        Call UpdateWindow(hOutputScreen)
      End Sub
      '
      Sub fnWndProc_OnCreate(wea As WndEventArgs)
        Local pCreateStruct As CREATESTRUCT Ptr
        Local strString() As String
        Local szCaption() As Asciiz * 10
        Local hButton() As Dword
        Register i As Long
      '  
        pCreateStruct=wea.lParam              'When Windows 'packages up' a WM_CREATE msg, it sets
        [email protected]    'the lParam variable equal to the address of a 
        Dim szCaption(1) As Asciiz*10         'CREATESTRUCT.  One can obtain the instance handle
        Dim hButton(1) As Dword               'in this way.  Yes, it is bizarre and complicated!
        For i=0 To 1
          szCaption(i)="Button #"
          szCaption(i)=szCaption(i)+Right$(Str$(i+1),1)
          hButton(i)= _                    'control handles can be globally defined
          CreateWindowEx _                 'CreateWindow() Api function call
          ( _             
           0, _                            'didn't use any extended styles
           "button", _                     'one of the more important predefined window classes
           szCaption(i), _                 'caption, in this case a button caption
           %WS_CHILD Or %WS_VISIBLE, _     'window style bit field
           93, _                           'X coordinate pixel position of top left corner of window
           30*i+50, _                      'Y coordinate pixel position of top of window (button)
           100, _                          'width of window (button)
           25, _                           'height of window (button)
           wea.hWnd, _                     'parent window handle
           i+1, _                          'control id! very important!
           wea.hInst, _                    'instance handle of app
           ByVal 0 _                       'pointer to window creation parameters.  Not used here.
          )
        Next i
      End Sub
      '
      Sub fnWndProc_OnCommand(wea As WndEventArgs)   'Our main window only has two controls on it
        Select Case LoWrd(wea.wParam)                'the wParam variable contains the control id of the
          Case %IDC_BUTTON1                          'window (button) sending the message.  We set the 
            Call cmdButton1_Click()                  'ids ourselves in the CreateWindow() call.
          Case %IDC_BUTTON2
            Call cmdButton2_Click(wea)
        End Select
      End Sub
      '
      Sub fnWndProc_OnClose(wea As WndEventArgs)
        Call PostQuitMessage(0)    'causes app to drop out of message loop and end
      End Sub
      '
      Function fnWndProc(ByVal hWnd As Long,ByVal wMsg As Long,ByVal wParam As Long,ByVal lParam As Long) Export As Long
        Static wea As WndEventArgs
      '
        Select Case wMsg
          Case %WM_CREATE
            wea.wParam=wParam: wea.lParam=lParam: wea.hWnd=hWnd
            Call fnWndProc_OnCreate(wea)
            fnWndProc=0
            Exit Function
          Case %WM_COMMAND
            wea.wParam=wParam: wea.lParam=lParam: wea.hWnd=hWnd
            Call fnWndProc_OnCommand(wea)
            fnWndProc=0
            Exit Function
          Case %WM_CLOSE
            wea.wParam=wParam: wea.lParam=lParam: wea.hWnd=hWnd
            Call fnWndProc_OnClose(wea)
            fnWndProc=0
            Exit Function
        End Select
      '
        fnWndProc=DefWindowProc(hWnd,wMsg,wParam,lParam)
      End Function
      '
      Function blnAppInitialize(wcl As WndClassEx,hIns As Long,szCls1 As Asciiz,szCls2 As Asciiz) As Dword
        Local szOutputScreen As Asciiz*16
        Local szAppName As Asciiz*6
      '
        'Fill out WndClassEx Type/structure for your main window
        wcl.cbSize=SizeOf(wcl)
        wcl.style=%CS_HREDRAW Or %CS_VREDRAW
        wcl.lpfnWndProc=CodePtr(fnWndProc)
        wcl.cbClsExtra=0
        wcl.cbWndExtra=0
        wcl.hInstance=hIns
        wcl.hIcon=LoadIcon(%NULL, ByVal %IDI_APPLICATION)
        wcl.hCursor=LoadCursor(%NULL, ByVal %IDC_ARROW)
        wcl.hbrBackground=GetStockObject(%LTGRAY_BRUSH)
        wcl.lpszMenuName=%NULL
        wcl.lpszClassName=VarPtr(szCls1)
        wcl.hIconSm=LoadIcon(%NULL,ByVal %IDI_APPLICATION)
        If IsFalse(RegisterClassEx(wcl)) Then                'Register main window class
           blnAppInitialize=%FALSE                           'Fail and exit proc & app on failure
           Exit Function
        End If
      '
        'Now modify several fields of type with unique
        'characteristics for OutputScreen
        wcl.lpszClassName=VarPtr(szCls2)                     'Output Screen Class Name
        wcl.lpfnWndProc=CodePtr(fnOutputScreen)              'Output Screen Window Proceedure
        wcl.hbrBackground=GetStockObject(%WHITE_BRUSH)       'Output Screen Background
        If IsFalse(RegisterClassEx(wcl)) Then
           blnAppInitialize=%FALSE
           Exit Function
        End If
      '
        Function=%TRUE
      End Function
      '
      Function WinMain(ByVal hIns As Long, ByVal hPrevIns As Long,ByVal lpCmdLine As Asciiz Ptr, ByVal iShow As Long) As Long
        Local szAppName As Asciiz*8,szOutputScreen As Asciiz*16
        Local winclass As WndClassEx
        Local dwStyle As Dword
        Local Msg As tagMsg
      '
        szAppName="Form3"                 'don't have to make these globals
        szOutputScreen="OutputScreen"     'once they are registered with windows the OS knows about them
        If blnAppInitialize(winclass,hIns,szAppName,szOutputScreen) Then
           dwStyle=%WS_OVERLAPPEDWINDOW  '1040384
           hMainWnd=CreateWindowEx(0,szAppName,"Form3",dwStyle,200,100,300,250,%HWND_DESKTOP,0,hIns,ByVal 0)
           ShowWindow hMainWnd,iShow
           UpdateWindow hMainWnd
           While GetMessage(Msg,%NULL,0,0)
             TranslateMessage Msg
             DispatchMessage Msg
           Wend
        Else
           MsgBox("Something Went South!")  'For Console Compiler Use Print Instead!
           'Print "Something Went South!"
        End If
      '
        Function=msg.wParam
      End Function
      ------------------
      Fred



      [This message has been edited by Fred Harris (edited August 22, 2007).]
      Fred
      "fharris"+Chr$(64)+"evenlink"+Chr$(46)+"com"

      Comment


      • #4
        Form4.bas

        The program below - Form4.bas - shows how to create a menu on a form or dialog using a resource file.
        Compiles with PB 8.01

        Form4.rc Directions For Creating Menu For Form4.bas

        An easy way to get a menu on a form/dialog is to create a
        resource file as seen below.

        1) Open new file in PowerBASIC and paste the following:

        Code:
        #include "resource.h"
        
        Form4 MENU
        {
         POPUP "&File"
         {
          MENUITEM "&Open...",               1000
          MENUITEM "&Save...",               1002
          MENUITEM SEPARATOR
          MENUITEM "E&xit",                  1004
         }
         POPUP "&Help"
         {
          MENUITEM "&Help...",               1006
          MENUITEM "&About Form6...",        1008
         }
        }
        2) Save file as Form4.rc;

        3) Compile within PBEdit by clicking little gear or Compile
        from menu. You'll quickly in succession be presented
        with two messages, the first informing you that Form4.res
        was successfully created, then that Form4.pbr was success-
        fully created;

        4) Copy Form4.bas to another PBEdit 'New' window, and save
        in same folder/directory as Form4.rc;

        5) Compile and run Form4.bas.

        Code:
          
        'Form4.bas
        #Compile Exe
        #Include "Win32api.inc"
        #Resource "Form4.pbr"
        Global app_hInst As Dword
        %IDM_OPEN  =  1000
        %IDM_SAVE  =  1002
        %IDM_EXIT  =  1004
        %IDM_HELP  =  1006
        %IDM_ABOUT =  1008
        '
        Function WndProc(ByVal hWnd As Long,ByVal wMsg As Long,ByVal wParam As Long,ByVal lParam As Long) Export As Long
          Local pCreateStruct As CREATESTRUCT Ptr
        '
          Select Case wMsg
            Case %WM_CREATE
              pCreateStruct=lParam
              [email protected]
              WndProc=0
              Exit Function
            Case %WM_COMMAND
              Select Case LoWrd(wParam)
                Case %IDM_OPEN
                  MsgBox("You Clicked File >> Open File!")
                Case %IDM_SAVE
                  MsgBox("You Clicked File >> Save File!")
                Case %IDM_EXIT
                  MsgBox("Send A Message To Windows To Close The Application.")
                  Call SendMessage(hWnd,%WM_CLOSE,0,0)
                Case %IDM_HELP
                  MsgBox("You Clicked Help >> Help!")
                Case %IDM_ABOUT
                  MsgBox("You Clicked Help >> About...")
              End Select
              WndProc=0
              Exit Function
            Case %WM_CLOSE
              MsgBox("This Is A Good Place To Prompt User If Changes Should" & Chr$(13) & _
              "Be Saved And To Clean Stuff Up, Memory, etc.")
              Call DestroyWindow(hWnd)
              WndProc=0
              Exit Function
            Case %WM_DESTROY
              Call PostQuitMessage(0)
              WndProc=0
              Exit Function
          End Select
        '
          WndProc=DefWindowProc(hWnd,wMsg,wParam,lParam)
        End Function
        '
        Function WinMain(ByVal hIns As Long, ByVal hPrev As Long,ByVal lpCL As Asciiz Ptr, ByVal Is As Long) As Long
          Local winclass As WndClassEx
          Local hMainWnd As Dword
          Local szAppName As Asciiz*8
          Local Msg As tagMsg
        '
          szAppName="Form4"
          winclass.cbSize=SizeOf(winclass)
          winclass.style=%CS_HREDRAW Or %CS_VREDRAW
          winclass.lpfnWndProc=CodePtr(WndProc)
          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=GetStockObject(%COLOR_BACKGROUND)
          winclass.lpszMenuName=VarPtr(szAppName) 'This is critical to get right! Menu name here must match
          winclass.lpszClassName=VarPtr(szAppName)'menu name in resource file!
          winclass.hIconSm=LoadIcon(hIns, ByVal %IDI_APPLICATION)
          RegisterClassEx winclass
          hMainWnd=CreateWindowEx(0,szAppName,"Form4",%WS_OVERLAPPEDWINDOW,200,100,325,275,0,0,hIns,ByVal 0)
          ShowWindow hMainWnd,Is
          UpdateWindow hMainWnd
          While GetMessage(Msg,%NULL,0,0)
            TranslateMessage Msg
            DispatchMessage Msg
          Wend
        '
          Function=msg.wParam
        End Function

        ------------------
        Fred



        [This message has been edited by Fred Harris (edited August 29, 2006).]
        Fred
        "fharris"+Chr$(64)+"evenlink"+Chr$(46)+"com"

        Comment


        • #5
          Form5.bas

          This program continues Form4.bas and implements the Common Dialog Box Library. Compiles
          with PB 8.01

          This program shows how to get the Common Dialog Box Library up and running. Note below
          in the Includes a file you havn't seen yet in the previous four Form template programs,
          that is, Comdlg32.inc. This file contains function declarations and equates necessary
          to link with Comdlg32.dll, which is one of the more important operating system
          components. On my Win XP laptop it is located in C:\Windows\System32\Comdlg32.dll, and
          it shows up as 271K. It is an example of how excellent software should work. Nobody
          even thinks about it as it almost always works flawlessly. I personally NEVER take it
          for granted as I used the creation of an Open File Dialog Box as an exercise to teach
          myself assembler and C programming in DOS way back in the nineties. I spent about six
          months working on that project and never came up with anything close to the Windows
          implementation. In essence, the implementation of the file open and file save dialog
          boxes involves a recursive directory serch algorithm whereby the file chosen by the user
          will finally end up being stored in the buffer pointed to by a pointer field in an
          OPENFILENAME structure (TYPE in Basic). Note the instantiation of this structure as ofn
          in OnOpen() and OnSave(). You would do well to open ComDlg32.inc in Notepad (it will be
          found in \WinApi in your PowerBASIC installation) and study the contents of that file.
          You should further study your Windows Api documentation for the OPENFILENAME structure
          as most of the myriads of configuration/customization details are found there.

          In this program, when the user selects File >> Open or File >> Save from the main menu,
          Windows will send a %WM_COMMAND message to fnWndProc() with the Lowrd of wParam equal
          to the control id of the menu item. You set the control ids yourself (see equates
          below) and in this program %IDM_OPEN is set to 1000 and %IDM_SAVE is set to 1002. Select
          Case logic tests the Wea.wParam variable in fnWndProc_OnCommand() and routes program
          execution to the proper event handler based on the value of the wParam variable. If
          Wea.wParam contains a 1000, then OnOpen() will be called. In that procedure local buffers
          are allocated, i.e., szFileName and szTitleName, and the addresses of these buffers are
          set to the proper fields in the OPENFILENAME (ofn in this case) type. Also note
          InitOpenFileDlg() where other fields of the OPENFILENAME type are filled in. This is
          a large and complex type. As an aside, many programmers declare the OPENFILENAME
          variable globally, as well as the file name buffer fields, and you may want to do this
          yourself. In that case, you may want to call InitOpenFileDlg() durring the %WM_CREATE
          message, and then in the OnOpen() or OnSave() event handlers you only need to call
          GetOpenFileName() or GetSaveFileName(). What you do depends on your aversion or lack
          thereof to global variables, and the lengths to which you are willing to go to eliminate
          them from your programs. In this particular program there are no globals (One static,
          however).

          See Form4.bas for directions on the handling of the Form5.rc file. Basically, you
          need to create a seperate source code file and use the PowerBASIC utilities to create
          your *.pbr file.

          Form5.rc
          Code:
          #include "resource.h"
          
          Form5 MENU
          {
           POPUP "&File"
           {
            MENUITEM "&Open...",               1000
            MENUITEM "&Save...",               1002
            MENUITEM SEPARATOR
            MENUITEM "E&xit",                  1004
           }
           POPUP "&Help"
           {
            MENUITEM "&Help...",               1006
            MENUITEM "&About Form6...",        1008
           }
          }
          '
          '
          Code:
          'Form5.bas
          #Compile Exe
          #Include "Win32api.inc"         'main windows header file
          #Include "Comdlg32.inc"         'common dialog boxes (open file, save file, etc. live here
          #Resource "Form5.pbr"           'PowerBASIC makes this file from Form5.rc >> Form5.res >> Form5.pbr
          %IDM_OPEN    =    1000          'control/menu ids set by programmer
          %IDM_SAVE    =    1002          '
          %IDM_EXIT    =    1004          '
          %IDM_HELP    =    1006          '
          %IDM_ABOUT   =    1008          '
          '
          Type WndEventArgs  'Windows Event Arguements  This is a convenient way to package Window Procedure
            wParam As Long   'parameters into one type
            lParam As Long
            hWnd   As Dword
            hInst  As Dword
          End Type
          '
          Sub InitOpenFileDlg(ByVal hWnd As Long,ofn As OPENFILENAME)
            Local strFilter As String
            Local strIniDir As String
          '
            strFilter= _    'Note - shows how to have multiple filters - use semicolons
            "bas files (*.bas), inc files (*.inc)"+Chr$(0)+"*.bas;*.inc"+Chr$(0)+ _
            "c files (*.c), cpp files (*.cpp), h files (*.h)"+Chr$(0)+"*.c;*.cpp;*.h"+Chr$(0)+ _
            "txt files (*.txt)"+Chr$(0)+"*.txt"+Chr$(0)+Chr$(0)
            strIniDir=CurDir$
            ofn.lStructSize=SizeOf(OPENFILENAME)
            ofn.hWndOwner=hWnd
            ofn.lpstrFilter=StrPtr(strFilter)    'C style functions and structures require address of
            ofn.nMaxFile=%MAX_PATH               'first character of string
            ofn.nMaxFileTitle=%MAX_PATH+%MAX_EXT 'buffer needs to be provided to return chosen file info
            ofn.lpstrInitialDir = StrPtr(strIniDir)
            ofn.lpstrDefExt=%NULL    'If you set this, it allows user type in filename without extension.
            ofn.Flags=%OFN_HIDEREADONLY Or %OFN_CREATEPROMPT   'You need to check out the docs on this, as
          End Sub                                              'there are many variations
          '
          Sub fnWndProc_OnCreate(wea As WndEventArgs)
            Local pCreateStruct As CREATESTRUCT Ptr
          '
            pCreateStruct=wea.lParam
            [email protected]
          End Sub
          '
          Sub OnOpen(wea As WndEventArgs)
            Local szFileName As Asciiz*%MAX_PATH
            Local szTitleName As Asciiz*(%MAX_FNAME+%MAX_EXT)
            Local ofn As OPENFILENAME
          '
            Call InitOpenFileDlg(wea.hWnd,ofn)
            ofn.lpstrFile=VarPtr(szFileName)
            ofn.lpstrFileTitle=VarPtr(szTitleName)
            Call GetOpenFileName(ofn)
            If szFileName<>"" Then
               MsgBox("You Chose " & szFileName)
               szFileName=""
            Else
               MsgBox("You Must Have Cancelled!")
            End If
          End Sub
          '
          Sub OnSave(wea As WndEventArgs)
            Local szFileName As Asciiz*%MAX_PATH
            Local szTitleName As Asciiz*(%MAX_FNAME+%MAX_EXT)
            Local ofn As OPENFILENAME
          '
            Call InitOpenFileDlg(wea.hWnd,ofn)
            ofn.lpstrFile=VarPtr(szFileName)
            ofn.lpstrFileTitle=VarPtr(szTitleName)
            Call GetSaveFileName(ofn)
            If szFileName<>"" Then
               MsgBox("You Chose " & szFileName)
               szFileName=""
            Else
               MsgBox("You Must Have Cancelled!")
            End If
          End Sub
          '
          Sub OnExit(wea As WndEventArgs)
            MsgBox("Send A Message To Windows To Close The Application.")
            Call SendMessage(wea.hWnd,%WM_CLOSE,0,0)
          End Sub
          '
          Sub OnHelp(wea As WndEventArgs)
            MsgBox("You Clicked Help >> Help!")
          End Sub
          '
          Sub OnAbout(wea As WndEventArgs)
            MsgBox("You Clicked Help >> About...")
          End Sub
          '
          Sub fnWndProc_OnCommand(wea As WndEventArgs)
            Select Case LoWrd(wea.wParam)
              Case %IDM_OPEN       '1000
                Call OnOpen(wea)
              Case %IDM_SAVE       '1002
                Call OnSave(wea)
              Case %IDM_EXIT       '1004
                Call OnExit(wea)
              Case %IDM_HELP       '1006
                Call OnHelp(wea)
              Case %IDM_ABOUT      '1008
                Call OnAbout(wea)
            End Select
          End Sub
          '
          Sub fnWndProc_OnClose(wea As WndEventArgs)
            MsgBox("fnWndProc_OnClose() Is A Good Place To Prompt User If Changes" & Chr$(13) & Chr$(10) & _
            "Should Be Saved And To Clean Stuff Up.")
            Call DestroyWindow(wea.hWnd)
          End Sub
          '
          Function fnWndProc(ByVal hWnd As Long,ByVal wMsg As Long,ByVal wParam As Long,ByVal lParam As Long) Export As Long
            Static wea As WndEventArgs
          '
            Select Case wMsg
              Case %WM_CREATE
                wea.wParam=wParam: wea.lParam=lParam: wea.hWnd=hWnd
                Call fnWndProc_OnCreate(wea)
                fnWndProc=0
                Exit Function
              Case %WM_COMMAND
                wea.wParam=wParam: wea.lParam=lParam: wea.hWnd=hWnd
                Call fnWndProc_OnCommand(wea)
                fnWndProc=0
                Exit Function
              Case %WM_CLOSE
                wea.wParam=wParam: wea.lParam=lParam: wea.hWnd=hWnd
                Call fnWndProc_OnClose(wea)
                fnWndProc=0
                Exit Function
              Case %WM_DESTROY
                wea.wParam=wParam: wea.lParam=lParam: wea.hWnd=hWnd
                Call PostQuitMessage(0)
                fnWndProc=0
                Exit Function
            End Select
          '
            fnWndProc=DefWindowProc(hWnd,wMsg,wParam,lParam)
          End Function
          '
          Function WinMain(ByVal hIns As Long,ByVal hPrevIns As Long,ByVal lpCmdLn As Asciiz Ptr,ByVal iShow As Long) As Long
            Local winclass As WndClassEx
            Local hMainWnd As Dword
            Local szAppName As Asciiz*8
            Local Msg As tagMsg
          '
            szAppName="Form5"
            winclass.cbSize=SizeOf(winclass)
            winclass.style=%CS_HREDRAW Or %CS_VREDRAW
            winclass.lpfnWndProc=CodePtr(fnWndProc)
            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=GetStockObject(%LTGRAY_BRUSH)
            winclass.lpszMenuName=VarPtr(szAppName)
            winclass.lpszClassName=VarPtr(szAppName)
            winclass.hIconSm=LoadIcon(hIns, ByVal %IDI_APPLICATION)
            RegisterClassEx winclass
            hMainWnd=CreateWindowEx(0,szAppName,"Form5",%WS_OVERLAPPEDWINDOW,200,100,325,275,0,0,hIns,ByVal 0)
            ShowWindow hMainWnd,iShow
            UpdateWindow hMainWnd
            While GetMessage(Msg,%NULL,0,0)
              TranslateMessage Msg
              DispatchMessage Msg
            Wend
          '
            Function=msg.wParam
          End Function
          ------------------
          Fred



          [This message has been edited by Fred Harris (edited August 29, 2006).]
          Fred
          "fharris"+Chr$(64)+"evenlink"+Chr$(46)+"com"

          Comment


          • #6
            '******************************************************************************************
            'File Name: ShowData.bas Compilers 8.03, 7.04. Won't work with 6.xx series (no Parse(), ParseCount()).
            '******************************************************************************************

            '******************************************************************************************
            'This app shows how to display data in a text box and a list box using Api function calls.
            'It differs from the first five 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.
            '
            '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 monotony. 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:
            '******************************************************************************************
            Code:
            #Compile Exe
            #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.
            '******************************************************************************************
            Code:
            Function frmMainWindow_OnCreate(wea As WndEventArgs) As Long 'if from VB this is Form_Load()
              Local sz1 As Asciiz*64,sz2 As Asciiz*64
              Local pCreateStruct As CREATESTRUCT Ptr
              Local hBut As Dword,dwStyle As Dword
              Local winclass As WndClassEx
              '
              pCreateStruct=wea.lParam               'When Windows sends a WM_CREATE message the lParam
              [email protected]      '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,wea.hIns,0)
              hBut=CreateWindow("button",sz2,dwStyle,50,60,325,25,wea.hWnd,%IDC_BUTTON_2,wea.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=GetStockObject(%LTGRAY_BRUSH)
              winclass.cbSize=SizeOf(winclass)
              winclass.style=%CS_HREDRAW Or %CS_VREDRAW 
              winclass.cbClsExtra=0  : winclass.cbWndExtra=0
              winclass.hInstance=wea.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=GetStockObject(%LTGRAY_BRUSH)
              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 button 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 fourth
            'parameter of the CreateWindowEx() 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 CreateWindowEx() 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 sense, 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 CreateWindowEx() 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.
            '******************************************************************************************
            Code:
            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 Xor %WS_THICKFRAME 'Max and sizing won't help.
                  'The Following Call Creates The Text Box Display Form.
                  hForm=CreateWindowEx(0,szTextBoxDisplay,szTextBoxDisplay,dwStyle,200,100,375,550,0,0,wea.hIns,Byval 0)
                  Call ShowWindow(hForm,%SW_SHOWNORMAL)
                Case %IDC_BUTTON_2
                  Call ShowWindow(wea.hWnd,%SW_HIDE)  'Hide MainWindow
                  dwStyle=%WS_OVERLAPPEDWINDOW Xor %WS_MAXIMIZEBOX Xor %WS_THICKFRAME 'Max and sizing won't help.
                  'The Following Call Creates The List Box Display Form.
                  hForm=CreateWindowEx(0,szListBoxDisplay,szListBoxDisplay,dwStyle,150,100,525,475,0,0,wea.hIns,Byval 0)
                  Call ShowWindow(hForm,%SW_SHOWNORMAL)
              End Select
              '
              frmMainWindow_OnCommand=0
            End Function
            '******************************************************************************************
            'Function Name: frmMainWindow_OnClose() Handles WM_CLOSE Message For Main Window
            '******************************************************************************************
            Code:
            Function frmMainWindow_OnClose(wea As WndEventArgs) As Long
              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().
            '*********************************************************************************************************
            Code:
            Function frmMainWindow(ByVal hWnd As Long,ByVal wMsg As Long,ByVal wParam As Long,ByVal lParam As Long) As Long
              Static 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.
            '***********************************************************************************************
            Code:
            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=GetStockObject(%LTGRAY_BRUSH)
              winclass.lpszMenuName=%NULL
              winclass.lpszClassName=VarPtr(szClassName)
              winclass.hIconSm=LoadIcon(hIns, ByVal %IDI_APPLICATION)
              Call RegisterClassEx(winclass)
              dwStyle=%WS_OVERLAPPEDWINDOW Xor %WS_MAXIMIZEBOX Xor %WS_THICKFRAME 
              hMainWnd=CreateWindowEx(0,szClassName,szAppName,dwStyle,350,250,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


            [This message has been edited by Fred Harris (edited January 08, 2007).]
            Fred
            "fharris"+Chr$(64)+"evenlink"+Chr$(46)+"com"

            Comment


            • #7
              '*******************************************
              'file name: showdata.inc (2nd of 4)
              '*******************************************

              '**************************************************************************************************
              'this is essentially the main include file for the application showdata.bas. 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 the
              'only two global variables, 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...
              '**************************************************************************************************
              Code:
              global sztextboxdisplay as asciiz*16  'holds the class name string 'frmtextbox'
              global szlistboxdisplay as asciiz*16  'holds the class name string 'frmlistbox'
              %idc_button_1=1001 'ctrl id for button on frmmainwindow.  goes in 9th parameter of createwindow() call. 
              %idc_button_2=1002 'ctrl id for button on frmmainwindow.  goes in 9th parameter of createwindow() call.
              %idc_edit_box=1003 'control id for text/edit box
              %idc_list_box=1004 'control id for listbox
              %idc_lb_load =1005 'control id for button at bottom of frmlistbox
              
              type wndeventargs  'type holds parameters for window procedure
                hins as dword
                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). 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...
              '**************************************************************************************************
              Code:
              '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
              '**************************************************************************************************
              'now the situation with these odbc equates and declares is this. if by some chance you might be
              'interested in learning to use the odbc api functions to work with databases, then you really should
              'go to...
              '
              ' http://www.powerbasic.com/files/pub/...e/pbodbc30.zip
              '
              'and get the three necessary odbc includes for your work. the three necessary includes are
              'sqltypes.inc, sql32.inc and sqlext32.inc. if you get those files from the pb downloads then you can
              'modify this app to use them instead of the 41 lines of code listed above. in that case just delete
              'those eight equates and four function declarations and change the includes at the top of showdata.bas
              'like so...

              '************************
              'file name: showdata.bas
              '************************
              '
              '******************************************************************************************
              '#compile exe
              '#dim all
              '#include "win32api.inc" '<<this file is courtesy of powerbasic and you should be thankful for it
              '#include "sqltypes.inc" '<<odbc include !!!!!
              '#include "sql32.inc" '<<odbc include !!!!!
              '#include "sqlext32.inc"' '<<odbc include !!!!!
              '#include "showdata.inc" '<<my main include for app equates, declares and functions
              '#include "frmtextbox.inc" '<<my include that contains code for the text/edit box form
              '#include "frmlistbox.inc" '<<my include that contains code for the list box form
              '******************************************************************************************
              'if you check back in showdata.bas you'll see those three odbc includes are missing, but this
              'app should work fine without them as i copied the necessary equates and declares directly
              'into the code here. but if you don't get those includes from powerbasic don't delete those
              'lines or this app won't even comple much less run!
              '******************************************************************************************



              '*****************************************************************************************
              '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
              '******************************************************************************************
              'the purpose of this application is just to show you how to load data into a text box or
              'list box using sdk style code and calling windows api functions. i mentioned in
              'frmtextbox.inc that for that purpose you could consider what goes on in this particular
              'function as an inscrutable 'black box' out of which data flows faithfully if not somewhat
              'mysteriously. but if you want a very detailed blow by blow description of how this function
              'works, see the following post in source code:
              '
              'sqldrivers() demo http://www.powerbasic.com/support/pb...ad.php?t=25068
              '
              '******************************************************************************************
              Code:
              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                    'convert one based count of bytes to zero basing for pointer use.
                     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. 'the last byte at ilen2 was a null that 
                     next i                        'got turned into a comma, so lets remove it. storing a null byte
                     @ptrbyte[ilen2]=0             'there will also decrease length of string by one.
                     redim strarr(parsecount(szdriverattr)-1)    'parsecount returns a one based count, but parse
                     parse szdriverattr,strarr()                 'starts storing parsed strings into element zero
                     for i=0 to ubound(strarr,1)                 'of strarr(), so it is best to use parsecount - 1.
                       strtext=strtext & strarr(i) & chr$(13,10) 'add crlf after each string to get them into seperate
                     next i                                      'lines.
                     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



              [this message has been edited by fred harris (edited january 08, 2007).]
              Fred
              "fharris"+Chr$(64)+"evenlink"+Chr$(46)+"com"

              Comment


              • #8
                '******************************************************************************************
                'File Name: frmListBox.inc (3 of 4) Contains code for the listbox form in ShowData.bas
                '******************************************************************************************

                '******************************************************************************************
                'Function Name: LoadListBox Purpose: Loads list box with 100 meaningless strings
                '******************************************************************************************
                'When a %WM_COMMAND message reaches frmListBox_OnCommand(), if the Lowrd(wea.wParam) is equal
                'to %IDC_LB_LOAD, i.e., the control id of the button to load the listbox, this function gets
                'called, and passed to it as a parameter is the handle of the listbox that needs filling. What
                'this function does at that point is send one hundred messages to the listbox through the
                'SendMessage() function, a function whose parameter list, i.e., its 'signature', is exactly
                'the same as that of the all important window procedure. The first parameter is the window
                'handle of the window to which the message is to be sent. The second parameter is the message
                'itself. The third and fourth parameters are the ubiquitous wParam and lParam parameters that
                'we have been looking at everywhere we go in the world of Windows graphical user interface
                'programming. Note that these messages we are sending here are not going to be received in
                'any of the several window procedures we have created in this app, but rather in the window
                'procedure of the listbox control, which will be inside one of the dlls referenced in the
                'Win32Api.inc. When the message being sent to a listbox is %LB_ADDSTRING, the lParam parameter
                'needs to be what the Api documentation refers to as the 'address of string to add'. This
                'is exactly what the PowerBASIC documentation states that StrPtr() returns for dynamic
                'strings, and so you see that as its last parameter. Note that there are many list box
                'messages (search for 'List Box Controls', and 'List Box Messages' in your Api documentation),
                'and the setup of the SendMessage() call varies considerably for each particular message
                'being sent. You'll find more info on SendMessage() below in my discussion of
                'frmListBox_OnCommand().
                '******************************************************************************************
                Code:
                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_OnCreate Handles %WM_CREATE message for frmListBox
                '******************************************************************************************
                'Before the CreateWindowEx() 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() calls. 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 the dwStyle variable holding the
                'desired styles.
                '******************************************************************************************
                Code:
                Function frmListBox_OnCreate(wea As WndEventArgs) As Long
                  Local hLBox As Dword,dwStyle As Dword,hBut As Dword
                  Local pCreateStruct As CREATESTRUCT Ptr
                  Local szStr As Asciiz*64
                  Local rc As RECT
                  '
                  pCreateStruct=wea.lParam
                  [email protected]
                  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,wea.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,wea.hIns,0)
                  '
                  frmListBox_OnCreate=0
                End Function

                '*********************************************************************************************
                '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 messaging business. But to be absolutely clear, that
                'listbox is on this form, but to communicate with it we send it a message. It is something like
                'sending yourself an email at noon not to forget to go to lunch, then when the email pops up in
                'your inbox getting up to get a bite to eat. 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. However, that
                'the listbox exists and that you can double click on an entry in it is proof that Windows itself
                'is aware of it and has its handle saftly tucked away somewhere. Windows actually has a number
                'of functions that find things if you have some item of information that you can give to Windows
                'to form the basis of a search. And in this case we have the listbox's control id. Somewhere
                'here I stated that these control ids were of global scope in the application and you can find
                'the listbox's control id listed with the equates right near the top of this app's main include
                'file - ShowData.inc. As you can see below, the Api function GetDlgItem() can be used to
                'retrieve a child window's handle if you pass to it the parent window handle and the child's
                'control id. You might legitimately ask why one would make life so hard for him/herself when
                'one could make all the window handles global, and the answer to that is that global variables
                'should be avoided whenever possible (this is a controversial issue). In this case here calling
                'such a simple function as GetDlgItem() is superior to making all window handles global.
                '
                'The next item that needs comment is the fourth parameter in the SendMessage() call that
                'retrieves the text string upon a double click. What the listbox needs sent to it in that
                'particular function call is the address of a buffer owned by this application into which the
                'sought after string of characters can be copied. Note that a PowerBASIC dynamic string won't
                'do here. Dynamic strings don't have any memory allocated to them until a string assignment is
                'made, and that Api call won't make it. The memory needs to be pre-allocated before the function
                'executes, and the easiest way to do that is by declaring a fixed length Asciiz string of
                'sufficient length. For a more detailed discussion of this important and somewhat difficult
                'topic see my SQLDrivers.bas program in the Source Code Forum. That is also where the code is
                'explained in some detail that loads the multi-line text box in frmTextBox.
                '
                'Finally, the listbox is loaded with strings when the button along the bottom of the form is
                'clicked. That causes the LoadListBox() function to be called (above).
                '*********************************************************************************************
                Code:
                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 frmListBox_OnCommand() For Extracting Strings From Listbox"
                      End If
                  End Select
                  '
                  frmListBox_OnCommand=0
                End Function

                '******************************************************************************************
                'Function Name: frmListBox_OnDestroy Handles %WM_DESTROY Message
                '******************************************************************************************
                'When the 'x' button on the frmListBox form is clicked the window procedure frmListBox()
                'will receive a %WM_DESTROY message and this message handling function will be called.
                'What you are seeing as the first line of code in the function is a nested procedure call.
                'This style of coding is commonly used, but it doesn't make the code easy to follow or
                'understand. The first parameter in the Api function ShowWindow() is the handle of the
                'window that is to be shown. Instead of placing a variable there containing the handle of
                'the window we want shown (the frmMainWindow), there is another function call that returns
                'that handle. In this case that would be the Main Window. We really don't know that
                'window handle because we did not save it in any variables. In our determination to
                'eliminate global variables at all cost, we don't have that particular window handle at our
                'disposal. In a certain sense in this program, when Windows doled out to us a window
                'handle through the various CreateWindowEx() calls we made, we threw them away as fast as we
                'got them. Now we find ourselves in the hapless plight of being stuck in this boring
                'frmListBox form with no way of returning to the Main Window and ending the program.
                'Almost. It so happens that Windows has a FindWindowEx() function that, according to the
                'docs, will laboriously search through all the windows that it is keeping track of till it
                'finds your window, if you can only tell it the class name of the window and the caption.
                'Its a good thing I have a good memory because I remember I made that window 'frmMainWindow'
                'and made the caption 'ShowData'. So now I can get out of this miserable loop, end the
                'program, and post this code (by the way, the second parameter to ShowWindow() is an equate,
                'and if we put %SW_SHOWNORMAL in there, when and if Windows comes up with the Window handle
                'for us, we'll be able to see it and close the program!).
                '******************************************************************************************
                Code:
                Function frmListBox_OnDestroy(wea As WndEventArgs) As Long
                  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=1002. 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
                '******************************************************************************************
                Code:
                Function frmListBox(ByVal hWnd As Long,ByVal wMsg As Long,ByVal wParam As Long,ByVal lParam As Long) As Long
                  Static 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



                [This message has been edited by Fred Harris (edited January 08, 2007).]
                Fred
                "fharris"+Chr$(64)+"evenlink"+Chr$(46)+"com"

                Comment


                • #9
                  '**********************************************************************************************
                  'file name: frmtextbox.inc (4 of 4) contains code for textbox form in showdata.bas
                  '**********************************************************************************************

                  '**********************************************************************************************
                  '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, frmmainwindow(), will sort out which button was pressed, and
                  'an instance of a frmtextbox window will be instantiated by a createwindowex() call in
                  'frmmainwindow_oncommand(). that createwindow() call will cause the window procedure associated
                  'with the textbox 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 createwindowex() 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.
                  '
                  'then the blngetsqldriverdata() function is called, and that function i put in 'showdata.inc'.
                  'the single parameter to that function is the handle of the text box, hedit, that we just created.
                  'in 'showdata.inc' you will find some information on blngetsqldriverdata(), but my main explanation
                  'of how that function works is in a seperate post in a seperate thread at...
                  '
                  http://www.powerbasic.com/support/pb...ad.php?t=25068
                  '
                  'if for some reason that function fails for you, (and blnsqldriverdata() returns %false), the
                  'text box we just created should be filled up with a hundred useless strings.
                  Code:
                  function frmtextbox_oncreate(wea as wndeventargs) as long
                    local hedit as dword,dwstyle 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
                    [email protected]   '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,wea.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
                  '**********************************************************************************************
                  Code:
                  function frmtextbox_ondestroy(wea as wndeventargs) as long
                    call showwindow(findwindowex(byval 0,byval 0,"frmmainwindow","showdata"),%sw_shownormal)
                    frmtextbox_ondestroy=0
                  end function
                  '**********************************************************************************************
                  'function name: frmtextbox window procedure for frmtextbox window
                  '**********************************************************************************************
                  Code:
                  function frmtextbox(byval hwnd as long,byval wmsg as long,byval wparam as long,byval lparam as long) as long
                    static 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



                  [this message has been edited by fred harris (edited january 08, 2007).]
                  Fred
                  "fharris"+Chr$(64)+"evenlink"+Chr$(46)+"com"

                  Comment


                  • #10
                    This is the first and simplest of a series of programs that demonstrate the concepts involved
                    in scrolling in Windows. Scrolling is actually somewhat involved, and the programs in this series
                    will hopefully further improve your understanding of the messaging system in Windows, and our
                    discussions and examples will touch on other more advanced topics such as arrays of pointers.

                    The program below, Simplest.bas, is about the simplest example I could conjure up that will
                    actually scroll a window vertically. There are only six functions, and, subtracting WinMain() and
                    the Window Procedure (fnWndProc() -- which in most of my programs is just a shell that acts as a
                    main switchboard and calls the actual functions that do the real work), there are only four that
                    constitute the core of the program's functionality. Before I continue, compile and run the program,
                    as that will help you understand my further explanations.

                    If you ran the program you may have noted the following: First, there are sixteen lines of
                    text but only about eleven are visible at a time. To see all sixteen lines one must use the
                    vertical scroll bar. Note the equate on line four of the program -- %LAST_LINE = 15. That equate
                    is used to dimension a string array as the first line of code in fnWndProc_OnCreate(). Remember
                    that the very first message Windows sends to a window on creation is WM_CREATE. That gives an app
                    the opportunity to run initialization code.

                    Second, if you tried to resize or maximize the window you found that you couldn't. All you
                    could do was move it around on the screen or minimize/restore it. That is because in WinMain() I
                    didn't include any window styles in the CreateWindow() function call that would enable Window
                    sizing. The topic of Window Styles in particular and bits in general will have to await another
                    tutorial after scrolling. The reason I did this is because I wanted to create the simplest possible
                    program that demonstrates scrolling, and window resizing code somewhat complicates that endeavor.

                    Speaking in very high level and general terms, scrolling will involve an interplay between the
                    processing of two messages 1) WM_VSCROLL (or WM_HSCROLL which I won't cover here, but the general
                    technique is the same) and WM_PAINT. For this reason it is necessary to create several variables
                    that are necessary to scroll logic that will maintain their values between function calls. One
                    poor way of doing this is to create global variables. The duration of a global variable is the
                    entire lifetime of a program (while it is in memory), and the scope of a global variable is the
                    entire program where it is defined. Our needs here are considerably less, and a more restrictive
                    scope will suit our needs in scrolling and that scope is termed static. A variable defined as
                    static has program lifetime duration but its scope is restricted to the procedure where it is
                    defined and procedures to which it is passed. Note in fnWndProc() the variable declaration:

                    Static wea As WndEventArgs

                    It is within the fields of this Type that our necessary variables to support scrolling will
                    be passed between our procedure to handle scroll logic and our procedure to handle window painting.

                    So lets begin. Unlike DOS, Windows is a totally graphics system, even where text is
                    concerned. There is no distinction as with DOS between text modes and graphics modes. Everything
                    is graphics and pixels. To set up vertical scroll logic one must determine the vertical height
                    of a character at the font presently in force on a system, and then the vertical height of the
                    window in which lines of text at a specific font will be scrolled. Let us begin with the first (I
                    know this sounds hard, but it isn't).

                    Let's start by slightly modifying the fnWndProc_OnCreate() message handler below to produce
                    some output for us to study. It is critical to know the values your variables are taking on as a
                    program runs. Some people use and like debuggers for that purpose. I have always personally
                    preferred an output log to log my program's execution. Modify that procedure as shown below, or
                    simply copy and paste this code into the program then comment out the original fnWndProc_OnCreate(),
                    then rerun the program.

                    Code:
                    Function fnWndProc_OnCreate(wea As WndEventArgs) As Long
                      Local lpMinPos,lpMaxPos As Long
                      Local tm As TEXTMETRIC
                      Register i As Long
                      Local hDC As Long
                      Local rc As RECT
                      Local fp As Integer
                      '
                      fp=FreeFile
                      Open "Output.txt" For Output As #fp
                      ReDim strLines(%LAST_LINE) As String
                      For i = 0 To %LAST_LINE
                        strLines(i)=Str$(i) & "  " & _
                        "PowerBASIC Scrolling Demonstration (Simplest)"
                      Next i
                      hDC=GetDC(wea.hWnd)               'In Windows, you need something called a 'Device Context' to get
                      Call GetTextMetrics(hDC,tm)       'info on how things such as text will be displayed.  To find out
                      wea.cyChar=tm.tmHeight            'how many lines of text will be visible on any screen size chosen
                      Call ReleaseDC(wea.hWnd,hDC)      'by a user, we need the height of a character, 'wea.cyChar'.
                      Call GetClientRect(wea.hWnd,rc)   'wea.hWnd is an 'input' parameter, rc is an 'output' parameter
                      wea.iBegin=0  : wea.iEnd=rc.nBottom\wea.cyChar-1  'and its members will contain size of client area
                      wea.iCtTotLns=%LAST_LINE+1                        'of window.
                      wea.iCtVisLns=rc.nBottom\wea.cyChar
                      Call SetScrollRange(wea.hWnd,%SB_VERT,0,wea.iCtTotLns-wea.iCtVisLns,%FALSE)
                      Call GetScrollRange(wea.hWnd,%SB_VERT,lpMinPos,lpMaxPos)
                      Print #fp,"wea.cyChar                       = "wea.cyChar
                      Print #fp,"rc.nTop                          = "rc.nTop
                      Print #fp,"rc.nBottom                       = "rc.nBottom
                      Print #fp,"rc.nBottom/wea.cyChar            = "rc.nBottom/wea.cyChar
                      Print #fp,"rc.nBottom\wea.cyChar            = "rc.nBottom\wea.cyChar
                      Print #fp,"wea.iCtTotLns                    = "wea.iCtTotLns
                      Print #fp,"wea.iCtVisLns                    = "wea.iCtVisLns
                      Print #fp,"lpMinPos                         = "lpMinPos
                      Print #fp,"lpMaxPos                         = "lpMaxPos
                      Close #fp
                      '
                      fnWndProc_OnCreate=0
                    End Function
                    Note that output.txt will be opened in the program's folder. On my machine it produced the
                    following results on a program run:
                    Code:
                    wea.cyChar                       =  16
                    rc.nTop                          =  0
                    rc.nBottom                       =  181
                    rc.nBottom/wea.cyChar            =  11.3125
                    rc.nBottom\wea.cyChar            =  11
                    wea.iCtTotLns                    =  16
                    wea.iCtVisLns                    =  11
                    lpMinPos                         =  0
                    lpMaxPos                         =  5
                    Perhaps I should back up and explain the whole procedure in detail from the beginning. The first
                    two lines in the modified procedure obtain a file handle and open a text file. The next statement
                    ( Redim ) ultimately causes a memory buffer to be allocated when the program runs into which
                    %LAST_LINE + 1 lines of text will be copied. The For Loop places the %LAST_LINE + 1 lines of text into
                    the allocated storage.

                    In order to obtain access to Windows internal storage mechanism containing details of a graphics
                    nature (for example, the vertical height of the font presently being used) you obtain a handle to a
                    device context with either the GetDC() or BeginPaint() Api functions. BeginPaint() can only be used
                    during the processing of a %WM_PAINT message, so in fnWndProc_OnCreate() we must use GetDC(). The only
                    parameter to that function is the window handle which in our case is stored in wea.hWnd (it was put
                    there in fnWndProc() -- check it out!). Having obtained an hDC we can call 'GetTextMetrics() to obtain
                    info on the height in pixels of a character.

                    Back in Form3 I attempted to provide something of an explanation of using and converting Windows
                    Api functions for use in PowerBASIC programs, and here I'll try to elaborate a bit further.
                    GetTextMetrics() is a pretty simple function (most of them are) and all you need to do is pass the proper
                    parameters. The first parameter is an 'input' parameter and that means the function needs input from us
                    to do its job. It wants to have a handle to a device context and this we just obtained in the previous
                    statement, so we just 'plug it in'. The second parameter is what is termed an 'output' parameter, and
                    it is called this because rather than returning the result of the function through a return value, the
                    result of the function call is returned through a function parameter, and through a fairly complicated
                    TYPE at that. At this point you should open up Win32Api.inc in either Notepad or the programming editor
                    you are using to study the TEXTMETRIC type. Win32Api.inc can be found in the \Win32Api directory of
                    your PowerBASIC installation. When open do a search for TEXTMETRIC by typing in

                    Type TEXTMETRIC

                    I'm telling you this not because I think you should memorize the 20 members or the TEXTMETRIC TYPE,
                    or even know what they all are, but rather to get you in the habit of searching through the Win32Api.inc
                    file to learn the definitions of types, equates, and functions with which you are not familiar. If you
                    have examined this type in Win32Api.inc you will have noticed that the first member is tmHeight. On my
                    machine a value of 16 pixels was returned. This value is saved in the statically defined wea type because
                    it is one of the variables that will be used many times throughout the program's duration. The cyChar
                    can be interpreted as count of pixels along the vertical y axis.

                    GetClientRect() is then called to obtain the height in pixels of the client rectangle, i.e., that
                    part of the window in which painting occurs. As with GetTextMetrics() an output parameter is used to
                    extract the information from Windows through a type, in this case a RECT type. A pattern should be
                    becoming obvious to you by now in terms of dealing with the Windows API. Variables of a specific type
                    predefined for you in the Windows include files are declared in procedures you write and you both feed
                    information to the Api through these variables and extract information from Windows through them. In
                    the case above a value of 181 pixels was returned through the rc.nBottom member of the RECT type. Again,
                    you should search for Type RECT in the Win32Api.inc file. This type is so commonly used I'd actually
                    consider memorizing it.

                    So at this point we know that a character takes up about 16 pixels, and we have 181 pixels 'up and
                    'down' in the window. A division yields 11.3125 lines, but since I decided to not display partial lines
                    I used integer division to truncate that value to 11, which value the program assigns to wea.iCtVisLns,
                    'iCountVisibleLines', and that of course is the count of the number of full lines visible at any one
                    time in the window. Shortly after that the program sets the value of wea.iCtTotLns which is the one
                    based count of the total numbers of lines in the memory buffer. Since the Redim statement set the buffer
                    like so...

                    ReDim strLines(%LAST_LINE) As String

                    ...and %LAST_LINE = 15, then the count stored in wea.iCtTotLns will be one more than %LAST_LINE
                    because we need to count strLines(0) too. The final critical ingredient in fnWndProc_OnCreate() is the
                    following Api call...

                    Call SetScrollRange(wea.hWnd, %SB_VERT, 0, wea.iCtTotLns - wea.iCtVisLns, %FALSE)

                    It is defined as follows in MSDN...

                    Code:
                         BOOL SetScrollRange
                         (
                          HWND hWnd,    // handle to window with scroll bar
                          int nBar,     // scroll bar flag
                          int nMinPos,  // minimum scrolling position
                          int nMaxPos,  // maximum scrolling position
                          BOOL bRedraw  // redraw flag
                         );
                    The first parameter should be self-explanatory at this point. The second parameter is an equate
                    that informs Windows of whether the call applies to a vertical scroll bar, a horizontal scroll bar, or
                    a scroll bar control. In this case we have a vertical scroll bar that is part of the window as opposed
                    to being a separate control, as would be the case with a scroll bar control. The next two parameters
                    are critical and are where the 'rubber meets the road' with scrolling. Note that immediately after
                    calling SetScrollRange() I called the complementary Api function GetScrollRange() so as to print out
                    the nMinPos and nMaxPos variables. Below are the results again...

                    Code:
                         wea.iCtTotLns                    =  16
                         wea.iCtVisLns                    =  11
                         lpMinPos                         =  0
                         lpMaxPos                         =  5
                    In interpreting this information it is worthwhile to use the concept of 'state'. If there are
                    sixteen lines of text and only eleven are visible, then the program will have to allow for the existence
                    of six distinct 'states' where the following lines of text from the text buffer will be made visible
                    through scrolling...

                    Code:
                                     wea.iBegin             wea.iEnd
                    
                        state     top line visible     bottom line visible
                        ==================================================
                         0              0                      10
                         1              1                      11
                         2              2                      12
                         3              3                      13
                         4              4                      14
                         5              5                      15
                    Note that when the program first starts it is in state '0', that is, lines zero ( strLines(0) )
                    through ten ( strLines(10) ) are visible (eleven lines, right?). Also, of course, the scroll bar thumb is
                    right against the upper boundary of the scroll bar track. By clicking the down button on the scroll bar
                    track one should be able to cause the program to jump to state '1' where lines 1 through 11 are visible,
                    and so on and so forth until the maximum state is reached, that is state '5' where lines 5 through 15
                    are visible. Remember several paragraphs up I spewed forth the broad generalization that scroll bars
                    are at base a graphical user interface device for picking an integer number from a range of values? Well,
                    the states shown above, i.e., 0 through 5 are just that!

                    Unfortunately, none of what we have covered so far will cause text to scroll in a window. However,
                    we have built the solid foundation on which scrolling is based, and that is setting up the integral range
                    of legal values or states in which the program can exist. What is left to do is understand what occurs
                    when a mouse click happens to either the up or down scroll bar arrow. But before we do that lets take a
                    look at how the initial eleven lines of text numbered zero through ten are initially shown when the
                    program first becomes visible on screen.

                    The first Windows message this particular program responds to following the WM_CREATE message discussed
                    at length above is the WM_PAINT message. Please note two members of the WndEventArgs Type that I haven't
                    discussed yet - iBegin and iEnd. These are actually set back in WM_CREATE and are simply the integer
                    values representing the array index of the lines first visible when the window is first painted. When the
                    window is in state zero, wea.iBegin is set to 0 and wea.iEnd is set to 10. Now note why this type is
                    declared as static in fnWndProc(). When we set the beginning and end lines through which a For construct
                    must loop back in WM_CREATE when we determined the size of the window and the number of lines visible,
                    those values must persist through function calls and most particularly after the WM_CREATE message
                    terminates. If you care to see the devastating impact declaring wea as local instead of static back in
                    fnWndProc() would have on this program just try that simple exercise and rerun the program. When the
                    window is first made visible by the operating system a WM_PAINT message will be sent to the window procedure
                    fnWndProc(). At that time fnWndProc() will call fnWndProc_OnPaint() and all the necessary prerequisites for
                    displaying those first 11 lines of text and for supporting further scrolling will have already have been set
                    up during the processing of the initial WM_CREATE message. Specifically, by examining fnWndProc_OnPaint()
                    you should see that a For loop makes lines 0 through 10 visible. Further note that the painting of the
                    lines is at a spacing of wea.cyChar apart.

                    Hopefully the output to the Output.txt file was of some use to you in understanding the processing of
                    the WM_CREATE message, so lets do the same with the WM_PAINT message. Comment out the fnWndProc_OnPaint()
                    in the program below and replace it with the following:

                    Code:
                    Function fnWndProc_OnPaint(wea As WndEventArgs) As Long
                      Local hDC As Long,y As Long
                      Local LpPaint As PAINTSTRUCT
                      Local fp As Integer
                      Register i As Long
                      '
                      fp=Freefile
                      Open "Output.txt" For Append As #fp
                      hDC = BeginPaint(wea.hWnd, LpPaint)
                      Print #fp,
                      Print #fp,"Top Of fnWndProc_OnPaint()"
                      Print #fp,
                      Print #fp,"wea.iBegin = "wea.iBegin
                      Print #fp,"wea.iEnd   = "wea.iEnd
                      Print #fp,
                      Print #fp, " i             y        y * wea.cyChar  "
                      Print #fp, "========================================"
                      For i = wea.iBegin To wea.iEnd
                        Print #fp,i,y,y*wea.cyChar
                        Call TextOut(hDC,0,y*wea.cyChar,ByVal StrPtr(strLines(i)),Len(strLines(i)))
                        Incr y
                       Next i
                       Call EndPaint(wea.hWnd,LpPaint)
                       Print #fp,
                       Print #fp,"Leaving fnWndProc_OnPaint()"
                       Close #fp
                       '
                       fnWndProc_OnPaint=0
                    End Function
                    Below is the output generated by the processing of the first WM_PAINT message. If you scroll the
                    window several times and examine the output you'll clearly see how the incrementing and decrementing of
                    wea.iBegin and wea.iEnd affect the output file and the lines of text visible in the window.

                    Code:
                       Top Of fnWndProc_OnPaint()
                    
                       wea.iBegin =  0
                       wea.iEnd   =  10
                    
                        i             y        y * wea.cyChar
                       ========================================
                        0             0             0
                        1             1             16
                        2             2             32
                        3             3             48
                        4             4             64
                        5             5             80
                        6             6             96
                        7             7             112
                        8             8             128
                        9             9             144
                        10            10            160
                    Leaving fnWndProc_OnPaint()

                    The TextOut() Api function in fnWndProc_OnPaint() is somewhat complex, so I'll spend some time on
                    explaining it. TextOut() is defined at MSDN as follows:

                    Code:
                    BOOL TextOut
                    (
                     HDC hdc,           // handle to device context
                     int nXStart,       // x-coordinate of starting position
                     int nYStart,       // y-coordinate of starting position
                     LPCTSTR lpString,  // pointer to string
                     int cbString       // number of characters in string
                    );
                    The tricky part for a PowerBASIC programmer is the fourth parameter. What this function needs to see
                    is the address of the first character of the string to be displayed. While the inner workings of this
                    function are unknown to us - and in that sense it is something of a 'black box' - we can count on the fact
                    that if we can get the address of the first character of a string to this function, it will gladly do its
                    job and display the string for us. Further, it will do this for us irregardless of what programming
                    language we are using to call it, whether it be C, assembler, fortran running on a PC, Cobol, or anything
                    else. Now in the Win32Api.inc file we have the following PowerBASIC declaration of TextOut():

                    Code:
                    DECLARE FUNCTION TextOut LIB "GDI32.DLL" ALIAS "TextOutA" _
                    ( _
                      BYVAL hdc AS DWORD, _
                      BYVAL x AS LONG, _
                      BYVAL y AS LONG, _
                      lpString AS ASCIIZ, _
                      BYVAL nCount AS LONG _
                    ) AS LONG
                    The address of the first character of each string in the dynamic string array strLines() can be
                    returned by the StrPtr function (if you are in doubt, read about it in your PowerBASIC help), and so that is
                    how I came by the string pointer needed by this function. Because I chose to obtain the address of a
                    dynamic string through the StrPtr function I need to bypass Basic's default By Reference parameter passing
                    convention and pass that address by value. That is why you see 'Byval StrPtr(strLines(i)) in my function
                    call in fnWndProc_OnPaint(). Had I defined the string array as an array of Asciiz strings, say -
                    szStrings(), then my function call would have been more in keeping with PowerBASIC's declare and would have
                    looked like this...

                    Call TextOut(hDC,0,y*wea.cyChar,szStrings(i),Len(szStrings(i))

                    In that case the PowerBASIC compiler's construction of the function call mechanism would have passed
                    the address of each string to the underlying Api function. I didn't want to make this too easy for you by
                    doing this, and in any case dynamic strings are usually easier to work with.

                    Play with this some and examine that Output.txt log file and eventually you'll come to understand the
                    deterministic inevitability of the whole thing. All the mystery that may be left for you at this point is
                    that strange fnWndProc_OnVScroll() function.

                    That function will be called from fnWndProc() when the latter receives a WM_VSCROLL message. Packaged
                    along with the message in the wParam parameter will be a numeric constant (an equate in Win32Api.inc)
                    specifying which particular scroll message is being sent. That is why the Select Case logic in
                    fnWndProc_OnVScroll() checks the lower two bytes of wParam looking for either SB_LINEUP or SB_LINEDOWN. If
                    SB_LINEDOWN then wea.iBegin and wea.iEnd are incremented and conversely if SB_LINEUP then these variables
                    are decremented. Checks have to be put in place to prevent either of these variables from assuming values
                    outside of the allocated storage, or otherwise GPFs would occur. Note the Api function call...

                    Call SetScrollPos(wea.hWnd,%SB_VERT,wea.iBegin,%TRUE)

                    which call is necessary to position correctly the scroll bar thumb after each position change. The first
                    parameter is the window handle of the window with the scroll bar. The second parameter is an equate specifying
                    whether the call applies to a vertical or horizontal scroll bar. The third parameter specifies an integer
                    within the scroll bar range specified in the SetScrollRange() function call in fnWndProc_OnCreate(). The
                    fourth specifies whether we want the scroll bar redrawn. Finally, the motor that makes the whole outrageous
                    thing work is that InvalidateRect() call in each of the case statements. Don't forget that the For loop that
                    actually paints the screen with a different set of lines of text is not in fnWndProc_OnVScroll() but in
                    fnWndProc_OnPaint()! So neither incrementing or decrementing iBegin and iEnd, nor repositioning the scroll
                    bar thumb will in itself cause the screen to scroll. What is needed after these events occur is a WM_PAINT
                    message to be generated, and this can be precipitated by an InvalidateRect() call. Windows keeps track
                    internally of regions that are invalid and in need of repainting, and by placing that %NULL in the second
                    parameter of the function call, it informs Windows that the entire client area is invalid. I'd recommend
                    you search your MSDN or Win Api help file for InvalidateRect and related documents.

                    One final point I'd like to make. In examining the overall architecture of the application it might occur
                    to you that the For loop that repaints the window with incremented or decremented lines of text could be
                    placed in fnWndProc_OnVScroll() instead of in the WM_PAINT message handler. After all, I did manage to get
                    a handle to a device context in fnWndProc_OnCreate() so as to get font info, and this very same device
                    context handle is the key needed to make TextOut() work. Well, this is all very true, but before you head
                    down this road let me warn you that you won't be satisfied with the results, because the text you paint on
                    the window won't persist through window resizing, or in cases where your window is uncovered by other
                    windows the user may move about on the screen.

                    If you are serious about learning SDK style Windows programming I'd recommend you spend a fair amount
                    of time playing with this app, and even try to start from scratch in developing your own version of it.
                    In my next tutorial we'll complicate 'Simplest.bas' considerably by using the ScrollWindow() Api function
                    in conjunction with the SCROLLINFO Type, and by adding the functionality to respond to more scroll messages.
                    Code:
                    #Compile Exe  '''Simplest.bas
                    #Include "Win32api.inc"
                    Global strLines() As String   'Array to hold lines to be scrolled.
                    %LAST_LINE  = 15              'Highest zero based array position holding text data.
                    '
                    Type WndEventArgs             'This type will be declared as static in the window procedure
                      hIns As Dword               'and will be passed to the various message handling functions.
                      hWnd As Dword               'In that way several variables critical to the development of
                      wParam As Dword             'scroll logic such as the minimum and maximum scroll bar
                      lParam As Dword             'positions will maintain their values between function calls.
                      cyChar As Long
                      iBegin As Long
                      iEnd As Long
                      iCtVisLns As Long
                      iCtTotLns As Long
                    End Type
                    '
                    Function fnWndProc_OnCreate(wea As WndEventArgs) As Long
                      Local tm As TEXTMETRIC
                      Register i As Long
                      Local hDC As Long
                      Local rc As RECT
                      ' 
                      ReDim strLines(%LAST_LINE) As String
                      For i = 0 To %LAST_LINE
                        strLines(i)=Str$(i) & "  " & _
                        "PowerBASIC Scrolling Demonstration (Simplest)"
                      Next i
                      hDC=GetDC(wea.hWnd)               'In Windows, you need something called a 'Device Context' to get
                      Call GetTextMetrics(hDC,tm)       'info on how things such as text will be displayed.  To find out
                      wea.cyChar=tm.tmHeight            'how many lines of text will be visible on any screen size chosen
                      Call ReleaseDC(wea.hWnd,hDC)      'by a user, we need the height of a character, 'wea.cyChar'.
                      Call GetClientRect(wea.hWnd,rc)   'wea.hWnd is an 'input' parameter, rc is an 'output' parameter
                      wea.iBegin=0                      'and its members will contain size of client area of window.
                      wea.iEnd=rc.nBottom\wea.cyChar-1  'Subtract 1 from wea.iCtVisLns to account for zero based strLines()
                      wea.iCtTotLns=%LAST_LINE+1        'array
                      wea.iCtVisLns=rc.nBottom\wea.cyChar
                      Call SetScrollRange(wea.hWnd,%SB_VERT,0,wea.iCtTotLns-wea.iCtVisLns,%FALSE)
                      '
                      fnWndProc_OnCreate=0
                    End Function
                    '
                    Function fnWndProc_OnVScroll(wea As WndEventArgs) As Long
                      Select Case LoWrd(wea.wParam)
                        Case %SB_LINEUP
                          Decr wea.iBegin
                          If wea.iBegin < 0 Then
                             wea.iBegin = 0
                          End If
                          wea.iEnd=wea.iBegin+wea.iCtVisLns-1
                          Call SetScrollPos(wea.hWnd,%SB_VERT,wea.iBegin,%TRUE)
                          Call InvalidateRect(wea.hWnd,ByVal %NULL,%TRUE)
                        Case %SB_LINEDOWN
                          Incr wea.iBegin
                          If wea.iBegin > wea.iCtTotLns-wea.iCtVisLns Then
                            wea.iBegin=wea.iCtTotLns-wea.iCtVisLns
                          End If
                          wea.iEnd=wea.iBegin+wea.iCtVisLns-1
                          Call SetScrollPos(wea.hWnd,%SB_VERT,wea.iBegin,%TRUE)
                          Call InvalidateRect(wea.hWnd,ByVal %NULL,%FALSE)
                      End Select
                      '
                      fnWndProc_OnVScroll = 0
                    End Function
                    '
                    Function fnWndProc_OnPaint(wea As WndEventArgs) As Long
                      Local hDC As Long,y As Long
                      Local LpPaint As PAINTSTRUCT
                      Register i As Long
                      '
                      hDC = BeginPaint(wea.hWnd, LpPaint)
                      For i = wea.iBegin To wea.iEnd
                        Call TextOut(hDC,0,y*16,ByVal StrPtr(strLines(i)),Len(strLines(i)))
                        Incr y
                      Next i
                      Call EndPaint(wea.hWnd,LpPaint)
                      '
                      fnWndProc_OnPaint=0
                    End Function
                    '
                    Function fnWndProc_OnClose(wea As WndEventArgs) As Long
                      Erase strLines()
                      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
                      Static wea As WndEventArgs
                      '
                      Select Case wMsg
                        Case %WM_CREATE
                          wea.hWnd=hWnd : wea.wParam=wParam : wea.lParam=lParam
                          fnWndProc = fnWndProc_OnCreate(wea)
                          Exit Function
                        Case %WM_VSCROLL
                          wea.hWnd=hWnd : wea.wParam=wParam : wea.lParam=lParam
                          fnWndProc=fnWndProc_OnVScroll(wea)
                          Exit Function
                        Case %WM_PAINT
                          wea.hWnd=hWnd : wea.wParam=wParam : wea.lParam=lParam
                          fnWndProc=fnWndProc_OnPaint(wea)
                          Exit Function
                        Case %WM_CLOSE
                          wea.hWnd=hWnd : wea.wParam=wParam : wea.lParam=lParam
                          fnWndProc=fnWndProc_OnClose(wea)
                          Exit Function
                      End Select
                      '
                      fnWndProc=DefWindowProc(hWnd, wMsg, wParam, lParam)
                    End Function
                    '
                    Function WinMain(ByVal hIns As Long, ByVal hPrev As Long,ByVal lpCL As Asciiz Ptr, ByVal Is As Long) As Long
                      Local hMainWnd As Dword,dwStyle As Dword
                      Local winclass As WndClassEx
                      Local szAppName As Asciiz*16
                      Local Msg As tagMsg
                      '
                      szAppName="Simplest.bas"
                      winclass.cbSize=SizeOf(winclass)                       :winclass.style=%CS_HREDRAW Or %CS_VREDRAW
                      winclass.lpfnWndProc=CodePtr(fnWndProc)                :winclass.cbClsExtra=0
                      winclass.cbWndExtra=0                                  :winclass.hCursor=LoadCursor(%NULL, ByVal %IDC_ARROW)
                      winclass.hIcon=LoadIcon(%NULL, ByVal %IDI_APPLICATION) :winclass.hInstance=hIns
                      winclass.hbrBackground=GetStockObject(%WHITE_BRUSH)    :winclass.lpszMenuName=%NULL
                      winclass.lpszClassName=VarPtr(szAppName)               :winclass.hIconSm=LoadIcon(hIns,ByVal %IDI_APPLICATION)
                      Call RegisterClassEx(winclass)
                      dwStyle=%WS_CAPTION Or %WS_MINIMIZEBOX Or %WS_VISIBLE Or %WS_VSCROLL Or %WS_SYSMENU
                      hMainWnd=CreateWindow(szAppName,szAppName,dwStyle,200,100,375,206,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
                    ------------------
                    Fred
                    "fharris"+Chr$(64)+"evenlink"+Chr$(46)+"com"

                    [This message has been edited by Fred Harris (edited December 21, 2006).]

                    ------------------
                    Fred
                    "fharris"+Chr$(64)+"evenlink"+Chr$(46)+"com"
                    Fred
                    "fharris"+Chr$(64)+"evenlink"+Chr$(46)+"com"

                    Comment


                    • #11
                      ScrollWindow.bas

                      This next program in my Scrolling lessons ( ScrollWindow.bas ) is considerably more complicated than Simplest.bas, so
                      I want you to be forewarned! Simplest.bas uses what I might term a 'brute force' scrolling method which, while fairly easy
                      to understand and get working, isn't really efficient or elegant. It uses a for loop with incrementing or decrementing
                      beginning and end points to write successive lines of text to the display. The inefficiency is that to see just one more
                      line of text in either direction, the display has to laboriously run through a loop and refresh the whole screen. Due to
                      the ineficient nature of refreshing a display using such high level language constructs as for loops with TextOut(), it
                      is not unusual for screen flickering and shimmer to result from this technique.

                      A more optimized technique is to use a special Windows Api call named logically ScrollWindow(). With this technique
                      the Windows Graphical Device Interface ( GDI ) internally shifts everything on the screen either up or down a programmer
                      specified amount. Since the data is moved internally (probably with highly optimized assembler string primitives) in a very
                      rapid manner, it tends to be much smoother than the brute force method we used in Simplest.bas. The main catch is that
                      the technique leaves a blank line either at the top or bottom of the window (or the edges) which the programmer must write
                      him/herself. To explain this lets take another look at a PAINTSTRUCT Type like we used in every WM_PAINT message handler
                      so far in this series...

                      Code:
                      TYPE PAINTSTRUCT
                        hDC AS DWORD
                        fErase AS LONG
                        rcPaint AS RECT
                        fRestore AS LONG
                        fIncUpdate AS LONG
                        rgbReserved(0 TO 31) AS BYTE
                      END TYPE
                      There is a member variable of this Type which I havn't covered yet in this tutorial but which figures very
                      prominently in the use of the ScrollWindow() call and that variable is rcPaint As RECT. When a call is made to
                      ScrollWindow() or ScrollWindowEx(), Windows invalidates only that part of the window which has been uncovered by the
                      scrolling operation, and if for example, the screen is only shifted up or down a distance of one line of text, that
                      invalid region (a rectangle structure) may be only a small part of the window. In other words, instead of the
                      programmer having to write code to repaint the whole window, he/she only needs to read the coordinates of that
                      smaller invalid region, and TextOut() or DrawText() only those one or two missing lines. An example is certainly
                      in order here to demonstrate the concept. Below is ScrollWindow1.bas. Please copy it to a code editor, compile
                      and run the program. When you do, click the scroll down button 5 or 6 times to see what happens. After your
                      initial dismay at the results, please close the program, and open up the Output.txt file that would have been
                      created in the directory where you ran the program. We'll examine it directly.

                      Code:
                      #Compile Exe                           'Uses ScrollWindow() in conjunction with SetScrollInfo().
                      #Dim All                               'Don't even think about not using this!
                      #Include "Win32api.inc"                'You ought to spend some considerable time examining this file with Notepad.
                      Global strLine() As String             'Dynamic strings are nice.  Unfortunately, you need to be a C programmer to
                      %LAST_LINE=25                          'really appreciate them!
                      Global fp As Long
                      '
                      Type WndEventArgs                      'Package frequently needed parameters
                        hIns As Dword
                        hWnd As Dword
                        wParam As Dword
                        lParam As Dword
                        cyChar As Dword                      'Verticle Size of a Character
                        iBegin As Long                       'Offset into array of first line (array holds lines of text to scroll)
                        iScrollRange As Long                 'iBegin to iEnd, as long as these values are in iScrollRange
                        iLinesVisible As Long                'One based count of lines visible on screen, adjusted as user resizes screen.
                        lpsi As SCROLLINFO                   'Type / structure Microsoft now wishes us to use to set scroll position and
                      End Type                               'scroll range
                      '
                      '
                      Function fnWndProc_OnCreate(wea As WndEventArgs) As Long
                        Local ps As PAINTSTRUCT
                        Local tm As TEXTMETRIC
                        Register i As Dword
                        Local hDC As Dword
                        Local rc As RECT
                        '
                        fp=Freefile
                        Open "Output.txt" For Output As #fp
                        Print #fp, "Entering fnWndProc_OnCreate()"
                        ReDim strLine(%LAST_LINE)            'Allocate memory buffer for lines of text to scroll
                        For i = 0 To UBound(strLine,1)       'Fill buffers with lines of text
                          strLine(i)=Str$(i)+"  "+ _
                          "PowerBASIC Scrolling Demo!"
                        Next i
                        hDC=GetDC(wea.hWnd)                  'In Windows, you need something called a 'Device Context' to get
                        Call GetTextMetrics(hDC,tm)          'info on how things such as text will be displayed.  To find out
                        wea.cyChar=tm.tmHeight               'how many lines of text will be visible on any screen size chosen
                        Call ReleaseDC(wea.hWnd,hDC)         'by a user, we need the height of a character, 'wea.cyChar'.
                        wea.lpsi.cbSize=SizeOf(wea.lpsi)     'Api Help wants us to specify size of SCROLLINFO structure.
                        Call GetClientRect(wea.hWnd,rc)
                        wea.iBegin=0
                        wea.iLinesVisible=Fix(rc.nBottom/wea.cyChar)            '200 / 16 = 12.5 but 200 \ 16 = 12
                        Print #fp,"wea.iLinesVisible="wea.iLinesVisible
                        wea.iScrollRange=%LAST_LINE-wea.iLinesVisible+1         '25 - 12 + 1 = 14
                        Print #fp, "wea.iScrollRange="wea.iScrollRange
                        wea.lpsi.fMask=%SIF_ALL
                        wea.lpsi.nMin=0 : wea.lpsi.nMax=wea.iScrollRange
                        wea.lpsi.nPos=wea.iBegin
                        Call SetScrollInfo(wea.hWnd,%SB_VERT,wea.lpsi,%TRUE)
                        Print #fp, "wea.cyChar = "wea.cyChar
                        Print #fp, "Leaving fnWndProc_OnCreate()"
                        Print #fp, : Print #fp,
                        '
                        fnWndProc_OnCreate=0
                      End Function
                      '
                      '
                      Function fnWndProc_OnVScroll(wea As WndEventArgs) As Long
                        Select Case LoWrd(wea.wParam)
                          Case %SB_LINEUP
                            If wea.iBegin Then
                               Decr wea.iBegin
                               Call ScrollWindow(wea.hWnd,0,wea.cyChar,ByVal 0,ByVal 0)
                               wea.lpsi.fMask=%SIF_POS
                               wea.lpsi.nPos=wea.iBegin
                               Call SetScrollInfo(wea.hWnd,%SB_VERT,wea.lpsi,%TRUE)
                            End If
                          Case %SB_LINEDOWN
                            If wea.iBegin+wea.iLinesVisible<=%LAST_LINE Then
                               Incr wea.iBegin
                               Call ScrollWindow(wea.hWnd,0,-wea.cyChar,ByVal 0,ByVal 0)
                               wea.lpsi.fMask=%SIF_POS
                               wea.lpsi.nPos=wea.iBegin
                               Call SetScrollInfo(wea.hWnd,%SB_VERT,wea.lpsi,%TRUE)
                            End If
                        End Select
                        '
                        fnWndProc_OnVScroll=0
                      End Function
                      '
                      '
                      Function fnWndProc_OnPaint(wea As WndEventArgs) As Long
                        Local iStart As Long, iFinish As Long,iLine As Long
                        Static blnPainted As Long
                        Local ps As PAINTSTRUCT
                        Register i As Dword
                        Local hDC As Dword
                        '
                        Print #fp, "Entering fnWndProc_OnPaint()"
                        hDC=BeginPaint(wea.hWnd,ps)
                        Print #fp, "wea.iBegin          = "wea.iBegin
                        Print #fp, "wea.iScrollRange    = "wea.iScrollRange
                        Print #fp, "wea.iLinesVisible   = "wea.iLinesVisible
                        If blnPainted<3 Then
                           iStart=ps.rcPaint.nTop\wea.cyChar
                           iFinish=Ceil(ps.rcPaint.nBottom/wea.cyChar)-1
                           Print #fp, "ps.rcPaint.nTop     = "ps.rcPaint.nTop
                           Print #fp, "ps.rcPaint.nBottom  = "ps.rcPaint.nBottom
                           Print #fp, "iStart              = "iStart
                           Print #fp, "iFinish             = "iFinish
                           Print #fp,
                           Print #fp, "i              i*wea.cyChar  iLine         strLine(iLine)"
                            Print #fp, "======================================================================"
                           For i=iStart To iFinish
                             iLine=wea.iBegin+i
                             If iLine<=%LAST_LINE Then
                                Print #fp, i,i*wea.cyChar,iLine,strLine(iLine)
                                Call TextOut(hDC,0,i*wea.cyChar,ByVal StrPtr(strLine(iLine)),Len(strLine(iLine)))
                             End If
                           Next i
                           Incr blnPainted
                        End If
                        Call EndPaint(wea.hWnd,ps)
                        Print #fp,
                        Print #fp, "Leaving fnWndProc_OnPaint()"
                        Print #fp, : Print #fp, : Print #fp,
                       '
                        fnWndProc_OnPaint=0
                      End Function      
                      '
                      '
                      Function fnWndProc_OnClose(wea As WndEventArgs) As Long
                        Close #fp
                        Erase strLine
                        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
                        Static wea As WndEventArgs
                        '
                        Select Case wMsg
                          Case %WM_CREATE
                            wea.hWnd=hWnd: wea.wParam=wParam: wea.lParam=lParam
                            fnWndProc=fnWndProc_OnCreate(wea)
                            Exit Function
                          Case %WM_VSCROLL
                            wea.hWnd=hWnd: wea.wParam=wParam: wea.lParam=lParam
                            fnWndProc=fnWndProc_OnVScroll(wea)
                            Exit Function
                          Case %WM_PAINT
                            wea.hWnd=hWnd: wea.wParam=wParam: wea.lParam=lParam
                            fnWndProc=fnWndProc_OnPaint(wea)
                            Exit Function
                          Case %WM_CLOSE
                            wea.hWnd=hWnd: wea.wParam=wParam: wea.lParam=lParam
                            fnWndProc=fnWndProc_OnClose(wea)
                            Exit Function
                        End Select
                        '
                        fnWndProc=DefWindowProc(hWnd,wMsg,wParam,lParam)
                      End Function
                      '
                      '
                      Function WinMain(ByVal hIns As Long, ByVal hPrevIns As Long,ByVal lpCmdLine As Asciiz Ptr, ByVal iShow As Long) As Long
                        Local hMainWnd As Dword,dwStyle As Dword
                        Local winclass As WndClassEx
                        Local szAppName As Asciiz*32
                        Local Msg As tagMsg
                        '
                        szAppName="Scrolling"
                        winclass.cbSize=SizeOf(winclass)                       :winclass.style=%CS_HREDRAW Or %CS_VREDRAW
                        winclass.lpfnWndProc=CodePtr(fnWndProc)                :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=GetStockObject(%WHITE_BRUSH)    :winclass.lpszMenuName=%NULL
                        winclass.lpszClassName=VarPtr(szAppName)               :winclass.hIconSm=LoadIcon(hIns, ByVal %IDI_APPLICATION)
                        Call RegisterClassEx(winclass)
                        dwStyle=%WS_MINIMIZEBOX Or %WS_VISIBLE Or %WS_VSCROLL Or %WS_SYSMENU
                        hMainWnd=CreateWindowEx(0,szAppName,szAppName,dwStyle,200,100,300,225,%HWND_DESKTOP,0,hIns,ByVal 0)
                        ShowWindow hMainWnd,iShow
                        UpdateWindow hMainWnd
                        While GetMessage(Msg,%NULL,0,0)
                          TranslateMessage Msg
                          DispatchMessage Msg
                        Wend
                        '
                        Function=msg.wParam
                      End Function
                      On my system the program revealed twelve full lines of text numbered 0 through 11 and about half of line # 12
                      was visible. Below is the Output.txt file created after clicking the Down arrow scroll bar button five times, and
                      then closing the program...

                      Code:
                      Entering fnWndProc_OnCreate()
                      wea.iLinesVisible= 12 
                      wea.iScrollRange= 14 
                      wea.cyChar =  16 
                      Leaving fnWndProc_OnCreate()
                      '
                      '
                      Entering fnWndProc_OnPaint()
                      wea.iBegin          =  0 
                      wea.iScrollRange    =  14 
                      wea.iLinesVisible   =  12 
                      ps.rcPaint.nTop     =  0 
                      ps.rcPaint.nBottom  =  200 
                      iStart              =  0 
                      iFinish             =  12 
                      '
                      i              i*wea.cyChar  iLine         strLine(iLine)
                      ======================================================================
                       0             0             0             0  PowerBASIC Scrolling Demo!
                       1             16            1             1  PowerBASIC Scrolling Demo!
                       2             32            2             2  PowerBASIC Scrolling Demo!
                       3             48            3             3  PowerBASIC Scrolling Demo!
                       4             64            4             4  PowerBASIC Scrolling Demo!
                       5             80            5             5  PowerBASIC Scrolling Demo!
                       6             96            6             6  PowerBASIC Scrolling Demo!
                       7             112           7             7  PowerBASIC Scrolling Demo!
                       8             128           8             8  PowerBASIC Scrolling Demo!
                       9             144           9             9  PowerBASIC Scrolling Demo!
                       10            160           10            10  PowerBASIC Scrolling Demo!
                       11            176           11            11  PowerBASIC Scrolling Demo!
                       12            192           12            12  PowerBASIC Scrolling Demo!
                      '
                      Leaving fnWndProc_OnPaint()
                      '
                      '
                      Entering fnWndProc_OnPaint()
                      wea.iBegin          =  1 
                      wea.iScrollRange    =  14 
                      wea.iLinesVisible   =  12 
                      ps.rcPaint.nTop     =  184 
                      ps.rcPaint.nBottom  =  200 
                      iStart              =  11 
                      iFinish             =  12 
                      '
                      i              i*wea.cyChar  iLine         strLine(iLine)
                      ======================================================================
                       11            176           12            12  PowerBASIC Scrolling Demo!
                       12            192           13            13  PowerBASIC Scrolling Demo!
                      '
                      Leaving fnWndProc_OnPaint()
                      '
                      '
                      '
                      Entering fnWndProc_OnPaint()
                      wea.iBegin          =  2 
                      wea.iScrollRange    =  14 
                      wea.iLinesVisible   =  12 
                      ps.rcPaint.nTop     =  184 
                      ps.rcPaint.nBottom  =  200 
                      iStart              =  11 
                      iFinish             =  12 
                      '
                      i              i*wea.cyChar  iLine         strLine(iLine)
                      ======================================================================
                       11            176           13            13  PowerBASIC Scrolling Demo!
                       12            192           14            14  PowerBASIC Scrolling Demo!
                      '
                      Leaving fnWndProc_OnPaint()
                      '
                      '
                      '
                      Entering fnWndProc_OnPaint()
                      wea.iBegin          =  3 
                      wea.iScrollRange    =  14 
                      wea.iLinesVisible   =  12 
                      '
                      Leaving fnWndProc_OnPaint()
                      '
                      '
                      '
                      Entering fnWndProc_OnPaint()
                      wea.iBegin          =  4 
                      wea.iScrollRange    =  14 
                      wea.iLinesVisible   =  12 
                      '
                      Leaving fnWndProc_OnPaint()
                      '
                      '
                      In the WM_CREATE message handler I determined that the height of a character in pixels was 16. Then we called
                      GetClientRect() passing it the address of a RECT type. On my system the number 200 was extracted from the rc.nBottom
                      member of the RECT variable rc. That number divided by the character height of 16 yields 12.5, but, being as I wanted
                      to base my calculations on full lines, I used the PowerBASIC Fix() function to truncate the result of the division to
                      12. So this means that the integer 12 will be assigned to the wea.iLinesVisible variable. Note that the wea variable
                      of type WndEventArgs is declared as static in fnWndProc() because many of its members must retain their values across
                      function calls. The other calculations in the WM_CREATE event handler should be familiar to you from Simplest.bas. If
                      not, you definitely need to reveiw that program. However, the wea.lpsi variable won't be familiar to you because we
                      didn't use it in Simplest.bas. In terms of that variable, Microsoft amalgamated several of the variables related to
                      scrolling into a type named SCROLLINFO in much the same manner as I amalgameted the Window Procedure parameters into
                      the WndEventArgs Type. You would do well to open your Api documentation and famaliarize yourself with the SCROLLINFO
                      type, as I use it in this program.

                      What I want to discuss now though is what you would have seen if you clicked the scroll down button several
                      times as I suggested, and then opened that text file. What you should have seen is that for the first three clicks
                      the window would have scrolled normally. Then disaster struck! With each click after that third the upper part of
                      the screen scrolled up normally, but the last line at the bottom wasn't replaced by the next line in order 'beneath'
                      the bottom edge of the window. Each click made it worse and expanded the vacant area. Continued clicks eventually
                      cleared the whole window as it was 'scrolled away'. I did this on purpose to help you see what is going on, and
                      shortly below when we get to fnWndProc_OnPaint() we'll fix it.

                      Take a look now at the first WM_PAINT message output in the Output.txt file above. Specifically, this...
                      Code:
                      Entering fnWndProc_OnPaint()
                      wea.iBegin          =  0 
                      wea.iScrollRange    =  14 
                      wea.iLinesVisible   =  12 
                      ps.rcPaint.nTop     =  0 
                      ps.rcPaint.nBottom  =  200 
                      iStart              =  0 
                      iFinish             =  12 
                      '
                      i              i*wea.cyChar  iLine         strLine(iLine)
                      ======================================================================
                       0             0             0             0  PowerBASIC Scrolling Demo!
                       1             16            1             1  PowerBASIC Scrolling Demo!
                       2             32            2             2  PowerBASIC Scrolling Demo!
                       3             48            3             3  PowerBASIC Scrolling Demo!
                       4             64            4             4  PowerBASIC Scrolling Demo!
                       5             80            5             5  PowerBASIC Scrolling Demo!
                       6             96            6             6  PowerBASIC Scrolling Demo!
                       7             112           7             7  PowerBASIC Scrolling Demo!
                       8             128           8             8  PowerBASIC Scrolling Demo!
                       9             144           9             9  PowerBASIC Scrolling Demo!
                       10            160           10            10  PowerBASIC Scrolling Demo!
                       11            176           11            11  PowerBASIC Scrolling Demo!
                       12            192           12            12  PowerBASIC Scrolling Demo!
                      '
                      
                      Leaving fnWndProc_OnPaint()
                      This paint message was received right after the WM_CREATE message, and it was as a result of the processing within
                      this message that the window became visible, showing, on my screen, 12 lines of text. Note that the PAINTSTRUCT variable
                      ps has that RECT structure contained within it that contains the top and bottom coordinates of the invalid rectangle
                      that needs to be painted. In our case the two numbers, i.e., 0 and 200, coincide exactly with the top and bottom of
                      the client area. At this point this is no different from the initial situation with Simplest.bas. The whole window is
                      invalid and the for loop will start drawing lines from the top to the bottom of the window.

                      Now take a look at the next call to fnWndProc_OnPaint(). This call would have been precipitated by your first click
                      of the scroll down button....

                      Code:
                      Entering fnWndProc_OnPaint()
                      wea.iBegin          =  1 
                      wea.iScrollRange    =  14 
                      wea.iLinesVisible   =  12 
                      ps.rcPaint.nTop     =  184 
                      ps.rcPaint.nBottom  =  200 
                      iStart              =  11 
                      iFinish             =  12 
                      '
                      i              i*wea.cyChar  iLine         strLine(iLine)
                      ======================================================================
                       11            176           12            12  PowerBASIC Scrolling Demo!
                       12            192           13            13  PowerBASIC Scrolling Demo!
                      '
                      Leaving fnWndProc_OnPaint()
                      When you clicked that button for the first time, a WM_VSCROLL message would have been sent to the window procedure,
                      and it would have showed up in fnWndProc_OnVScroll(). Since it would have contained an SB_LINEDOWN in its
                      Lowrd(wea.wParam), the following call would have been made...

                      Call ScrollWindow(wea.hWnd,0,-wea.cyChar,ByVal 0,ByVal 0)

                      ...and the text would have been scrolled up -cyChar or 16 pixels (on my system), which is the height of a line of text.
                      That would have uncovered cyChar pixels at the bottom of the window, and so it would only be those 16 pixels from 184 to
                      200 that would have been invalid. In the output above you can see this is indeed what Windows reports to us in that
                      rcPaint.nTop and rcPaint.nBottom variables. Please compare that with the first WM_PAINT output where the whole 200 pixel
                      expanse of the window was invalid and in need of painting. If one were to try to describe in words just what Windows is
                      doing with that ScrollWindow() call it would go something like this "When you call ScrollWindow(), that function will shift
                      up or down, left or right, by the amount specified in its 2nd and 3rd parameters, whatever is presently visible on the
                      screen. Anything shifted off the screen is lost, and any areas newly uncovered remain blank and 'invalid'. After that first
                      scroll bar click, the bottom 16 pixels of the window from pixel 184 to 200 became invalid.

                      One other point I might make is that I purposely created the window so that the size of the client area was not an even
                      multiple of the character height of wea.cyChar (16 on my system). So the last line visible is only half visible - the bottom
                      half being off the display. That is why even though we only scrolled a distance of wea.cyChar, that distance involved the
                      bottom half of one line and the top half of another. Since we can't TextOut() half lines, it was necessary to to print two
                      whole lines. This will usually be the case. While you may initially set up a window all nice and proper with even multiples
                      of line heights, diabolical users who aren't satisfied with your choices will resize the window and thereby create uneven
                      configurations. So that's what's going on there.

                      Now lets take a look at fnWndProc_OnPaint(), which function is certainly implicated in all this...

                      Code:
                      Function fnWndProc_OnPaint(wea As WndEventArgs) As Long
                        Local iStart As Long, iFinish As Long,iLine As Long
                        Static blnPainted As Long
                        Local ps As PAINTSTRUCT
                        Register i As Dword
                        Local hDC As Dword
                        '
                        Print #fp, "Entering fnWndProc_OnPaint()"
                        hDC=BeginPaint(wea.hWnd,ps)
                        Print #fp, "wea.iBegin          = "wea.iBegin
                        Print #fp, "wea.iScrollRange    = "wea.iScrollRange
                        Print #fp, "wea.iLinesVisible   = "wea.iLinesVisible
                        If blnPainted<3 Then
                           iStart=ps.rcPaint.nTop\wea.cyChar
                           iFinish=Ceil(ps.rcPaint.nBottom/wea.cyChar)-1
                           Print #fp, "ps.rcPaint.nTop     = "ps.rcPaint.nTop
                           Print #fp, "ps.rcPaint.nBottom  = "ps.rcPaint.nBottom
                           Print #fp, "iStart              = "iStart
                           Print #fp, "iFinish             = "iFinish
                           Print #fp,
                           Print #fp, "i              i*wea.cyChar  iLine         strLine(iLine)"
                            Print #fp, "======================================================================"
                           For i=iStart To iFinish
                             iLine=wea.iBegin+i
                             If iLine<=%LAST_LINE Then
                                Print #fp, i,i*wea.cyChar,iLine,strLine(iLine)
                                Call TextOut(hDC,0,i*wea.cyChar,ByVal StrPtr(strLine(iLine)),Len(strLine(iLine)))
                             End If
                           Next i
                           Incr blnPainted
                        End If
                        Call EndPaint(wea.hWnd,ps)
                        Print #fp,
                        Print #fp, "Leaving fnWndProc_OnPaint()"
                        Print #fp, : Print #fp, : Print #fp,
                        '
                        fnWndProc_OnPaint=0
                      End Function
                      The critical variables and code would be the iStart and iFinish variables, how they are initialized, and the for loop.
                      If you recall back in Simplest.bas ( the brute force program ), if the Window contained 12 lines and you wanted to scroll
                      down one line to see line 13, iStart would be incremented to two and iFinish to thirteen, an InvalidateRect() call would be
                      made to force a repaint of the entire window, and the WM_PAINT handler would run a for loop for lines 2 through 13 so as to
                      make line thirteen visible. The only time that happens here is on the initial painting of the window right after the WM_CREATE
                      and initial WM_SIZE message. That is proved by the first set of output above where pixels 0 through 200 were invalid. In
                      typical scrolling situations what happens is better described by the second WM_PAINT output above where only 16 pixels rather
                      than 200 pixels in a verticle direction were invalidated. What happened in that case is that windows executed an internal and
                      optimized GDI 'Raster Blaster' operation which shifted the upper part of the screen up by 16 pixels. iStart was initialized
                      to the result of an integer division of ps.rcPaint.nTop against wea.cyChar and iFinish was handled similiarly with regard to
                      the bottom of the invalid region and further corrected by some code wizzardry to nudge the number up to account for odd Window
                      sizes and so forth. The end result of that code was that iStart was set to 11 and iFinish to 12. When the for loop executed
                      these numbers multiplied by 16 to account for the size of a line in height, the end result was to get two lines at the bottom
                      of the display rather than one line drawn. In actual practice this is usually how the numbers work out due to the fact that
                      window sizes are seldom an exact multiple of character heights, and a small amount of extra drawing may be necessary, but it
                      is certainly more efficient than redrawing the entire screen!

                      In terms of how I created the screw up where increasingly large areas of the screen became blank (so as to help you see
                      how ScrollWindow() works), check out the iPainted variable in fnWndProc_OnPaint() above, which I declared as static.
                      PowerBASIC would have set it to zero at program start, but each call to the WM_PAINT function would have incremented it by one.
                      By looking at the If statement you can see that processing only reached the for loop that repainted that 16 pixel area at the
                      bottom of the window for the first three scroll down messages. After that the ScrollWindow() call in fnWndProc_OnVScroll()
                      indeed shifted the Window's contents up by a line height after each scroll message, but processing was hindered from entering
                      the logic within the If statement in the WM_PAINT handler which calculates the necessary lines to repaint in terms of the
                      invalid region sent to the procedure.

                      For your own understanding of the processes involved here I would recommend you comment out the four lines which involve
                      iPainted in the above fnWndProc_OnPaint() procedure, particelarly the If and End If statements, and rerun, study and
                      experiment with the code. This particular example is particularly good because it is as close as we can get to Simplest.bas
                      using this new high powered ScrollWindow() call in conjunction with SCROLLINFO. The reason I'm saying this is that the next
                      iteration of this program below incorporates full scrolling functionality with resizeable borders, page up and page down
                      processing, thumbtrack processing, keyboard processing, and so on. It is not really more difficult; just more involved. So
                      I'd recommend you do your best to understand this simpler one before we move on.

                      One further point I might make. This material isn't easy. Don't expect to sit down and bang out your own perfectly
                      working version of this in a few hours. A few days might be more like it. Maybe longer. To learn this material I'd
                      guesstimate you'll need to generate 250 to 500 GPFs in your endeavors. If you really want to learn this material though, I'd
                      recommend you try just that however. Start with a Hello, World template and start building the 'ediface' of a scrollable
                      window from scratch, looking occasionally at these examples when you get stuck, until you begin to fully understand the issues
                      involved.

                      So, having said that lets augment ScrollWindow1.bas to ScrollWindow.bas (decrementing it actually) and incorporate
                      these neat extra features I alluded to above. Here is ScrollWindow.bas...

                      Code:
                      #Compile Exe                        'Uses ScrollWindow() in conjunction with SetScrollInfo().
                      #Dim All                            'Don't even think about not using this!
                      #Include "Win32api.inc"             'You ought to spend some considerable time examining this file with Notepad.
                      Global strLine() As String          'Dynamic strings are nice.  Unfortunately, you need to be a C programmer to
                      %LAST_LINE  =    100                'really appreciate them!
                      %DEBUG      =    %TRUE
                      #If %DEBUG
                          Global fp    As Long
                      #EndIf
                      '
                      '
                      Type WndEventArgs                   'Package frequently needed parameters
                        hIns As Dword                     'Several of these variables need to be statics so as to retain Scroll
                        hWnd As Dword                     'information between WndProc() Calls.
                        wParam As Dword
                        lParam As Dword
                        cyChar As Dword                   'Verticle Size of a Character
                        iBegin As Long                    'Offset into array of first line (array holds lines of text to scroll)
                        iScrollRange As Long              'iBegin to iEnd, as long as these values are in iScrollRange
                        iLinesVisible As Long             'One based count of lines visible on screen, adjusted as user resizes screen.
                        lpsi As SCROLLINFO                'Type / structure Microsoft now wishes us to use to set scroll position and
                      End Type                            'scroll range
                      '
                      '
                      Function fnWndProc_OnCreate(wea As WndEventArgs) As Long
                        Local ps As PAINTSTRUCT           'Examine this in Win32Api.inc!!
                        Local tm As TEXTMETRIC            'Likewise with this!
                        Register i As Dword
                        Local hDC As Dword
                        '
                        #If %DEBUG
                            fp=Freefile
                            Open "Output.txt" For Output As #fp
                            Print #fp, "Entering fnWndProc_OnCreate()"
                        #EndIf
                        ReDim strLine(%LAST_LINE)         'Allocate memory buffer for lines of text to scroll
                        For i = 0 To UBound(strLine,1)    'Fill buffers with lines of text
                          strLine(i)=Str$(i)+"  "+ _
                          "PowerBASIC Scrolling Demo!"
                        Next i
                        hDC=GetDC(wea.hWnd)               'In Windows, you need something called a 'Device Context' to get
                        Call GetTextMetrics(hDC,tm)       'info on how things such as text will be displayed.  To find out
                        wea.cyChar=tm.tmHeight            'how many lines of text will be visible on any screen size chosen
                        Call ReleaseDC(wea.hWnd,hDC)      'by a user, we need the height of a character, 'wea.cyChar'.
                        wea.lpsi.cbSize=SizeOf(wea.lpsi)  'Api Help wants us to specify size of SCROLLINFO structure.
                        #If %DEBUG
                            Print #fp, "wea.iBegin        = "wea.iBegin
                            Print #fp, "wea.iScrollRange  = "wea.iScrollRange
                            Print #fp, "wea.iLinesVisible = "wea.iLinesVisible
                            Print #fp, "Leaving fnWndProc_OnCreate()"
                            Print #fp,
                        #EndIf
                        '
                        fnWndProc_OnCreate=0
                      End Function
                      '
                      '
                      Function fnWndProc_OnSize(wea As WndEventArgs) As Long
                        #If %DEBUG
                            Print #fp, "Entering fnWndProc_OnSize()"
                        #EndIf
                        wea.iLinesVisible=Fix(HiWrd(wea.lParam)/wea.cyChar)      'wea.lParam holds height of client area in pixels
                        wea.iScrollRange=%LAST_LINE-wea.iLinesVisible+1          'Last Line is #100, but that's a zero based count,
                        wea.lpsi.fMask=%SIF_ALL                                  'so add one to it (iLinesVisible is one based).
                        wea.lpsi.nMin=0 : wea.lpsi.nMax=wea.iScrollRange         'Set mins and maxes in lpsi (SCROLLINFO type)
                        wea.lpsi.nPos=wea.iBegin                                 'iBegin is zero based number of first line visible
                        Call SetScrollInfo(wea.hWnd,%SB_VERT,wea.lpsi,%TRUE)     'in window.
                        #If %DEBUG
                            Print #fp, "wea.iBegin        = "wea.iBegin
                            Print #fp, "wea.iScrollRange  = "wea.iScrollRange
                            Print #fp, "wea.iLinesVisible = "wea.iLinesVisible
                            Print #fp, "Leaving fnWndProc_OnSize()"
                            Print #fp,
                        #EndIf
                        '
                        fnWndProc_OnSize=0
                      End Function
                      '
                      '
                      Function fnWndProc_OnKeyDown(wea As WndEventArgs) As Long  'We already have code in place to handle scroll
                        Select Case wea.wParam                                   'line up, scroll line down, scroll page up, etc.
                          Case %VK_PGUP                                          'That being the case, there's no sense in
                            Call SendMessage(wea.hWnd,%WM_VSCROLL,%SB_PAGEUP,0)  'duplicating that code here.  Since proper
                          Case %VK_UP                                            'message processing logic exists for these scroll
                            Call SendMessage(wea.hWnd,%WM_VSCROLL,%SB_LINEUP,0)  'messages, when keyboard messages are received
                          Case %VK_PGDN                                          'in here, we'll just send the corresponding
                            Call SendMessage(wea.hWnd,%WM_VSCROLL,%SB_PAGEDOWN,0)'scroll message to the Window Procedure directly.
                          Case %VK_DOWN
                            Call SendMessage(wea.hWnd,%WM_VSCROLL,%SB_LINEDOWN,0)
                        End Select
                        '
                        fnWndProc_OnKeyDown=0
                      End Function
                      '
                      '
                      Function fnWndProc_OnVScroll(wea As WndEventArgs) As Long
                        #If %DEBUG
                            Print #fp, "Entering fnWndProc_OnVScroll()"
                        #EndIf
                        Select Case LoWrd(wea.wParam)
                          Case %SB_LINEUP
                            If wea.iBegin Then                                                 'Check to make sure not already at
                               Decr wea.iBegin                                                 'strLine(0)
                               Call ScrollWindow(wea.hWnd,0,wea.cyChar,ByVal 0,ByVal 0)        'Bump everything 16 pixels (cyChar) down.
                               wea.lpsi.fMask=%SIF_POS
                               wea.lpsi.nPos=wea.iBegin
                               Call SetScrollInfo(wea.hWnd,%SB_VERT,wea.lpsi,%TRUE)
                            End If
                            #If %DEBUG
                                Print #fp, "Lowrd(wea.wParam) =SB_LINEUP"
                            #EndIf
                          Case %SB_PAGEUP
                            If wea.iBegin-wea.iLinesVisible >= 0 Then
                               wea.iBegin = wea.iBegin - wea.iLinesVisible
                               Call ScrollWindow(wea.hWnd,0,wea.iLinesVisible*wea.cyChar,ByVal 0,ByVal 0)
                               wea.lpsi.fMask=%SIF_POS                                 'The fMask member of lpsi prepares for the SetScrollInfo
                               wea.lpsi.nPos=wea.iBegin                                'call and tells the function which members we are setting
                               Call SetScrollInfo(wea.hWnd,%SB_VERT,wea.lpsi,%TRUE)
                            Else
                               Call ScrollWindow(wea.hWnd,0,wea.iBegin*wea.cyChar,ByVal 0,ByVal 0)
                               wea.iBegin=0
                               wea.lpsi.fMask=%SIF_POS
                               wea.lpsi.nPos=wea.iBegin
                               Call SetScrollInfo(wea.hWnd,%SB_VERT,wea.lpsi,%TRUE)
                            End If
                            #If %DEBUG
                                Print #fp, "Lowrd(wea.wParam) =SB_PAGEDOWN"
                            #EndIf
                          Case %SB_LINEDOWN
                            If wea.iBegin+wea.iLinesVisible<=%LAST_LINE Then
                               Incr wea.iBegin
                               Call ScrollWindow(wea.hWnd,0,-wea.cyChar,ByVal 0,ByVal 0)
                               wea.lpsi.fMask=%SIF_POS
                               wea.lpsi.nPos=wea.iBegin
                               Call SetScrollInfo(wea.hWnd,%SB_VERT,wea.lpsi,%TRUE)
                            End If
                            #If %DEBUG
                                Print #fp, "Lowrd(wea.wParam) =SB_LINEDOWN"
                            #EndIf
                          Case %SB_PAGEDOWN
                            If wea.iBegin+wea.iLinesVisible<=%LAST_LINE Then
                               wea.iBegin = wea.iBegin + wea.iLinesVisible
                               Call ScrollWindow(wea.hWnd,0,-wea.iLinesVisible*wea.cyChar,ByVal 0,ByVal 0)
                               wea.lpsi.fMask=%SIF_POS
                               wea.lpsi.nPos=wea.iBegin
                               Call SetScrollInfo(wea.hWnd,%SB_VERT,wea.lpsi,%TRUE)
                            End If
                            #If %DEBUG
                                Print #fp, "Lowrd(wea.wParam) =SB_PAGEDOWN"
                            #EndIf
                          Case %SB_THUMBTRACK
                            wea.lpsi.fMask=%SIF_TRACKPOS
                            Call GetScrollInfo(wea.hWnd,%SB_VERT,wea.lpsi)
                            wea.iBegin=wea.lpsi.nTrackPos
                            Call InvalidateRect(wea.hWnd,ByVal 0,%TRUE)
                            wea.lpsi.fMask=%SIF_POS
                            wea.lpsi.nPos=wea.lpsi.nTrackPos
                            Call SetScrollInfo(wea.hWnd,%SB_VERT,wea.lpsi,%TRUE)
                            #If %DEBUG
                                Print #fp, "Lowrd(wea.wParam) =SB_THUMBTRACK"
                            #EndIf
                        End Select
                        #If %DEBUG
                            Print #fp, "wea.iBegin        = "wea.iBegin
                            Print #fp, "wea.iScrollRange  = "wea.iScrollRange
                            Print #fp, "wea.iLinesVisible = "wea.iLinesVisible
                            Print #fp, "Leaving fnWndProc_OnVScroll()"
                            Print #fp,
                        #EndIf
                        '
                        fnWndProc_OnVScroll=0
                      End Function
                      '
                      '
                      Function fnWndProc_OnPaint(wea As WndEventArgs) As Long
                        Local iStart As Long, iFinish As Long,iLine As Long
                        Local ps As PAINTSTRUCT
                        Register i As Dword
                        Local hDC As Dword
                        '
                        #If %DEBUG
                            Print #fp, "Entering fnWndProc_OnPaint()"
                        #EndIf
                        hDC=BeginPaint(wea.hWnd,ps)
                        #If %DEBUG
                            Print #fp, "wea.iBegin        = "wea.iBegin
                            Print #fp, "wea.iScrollRange  = "wea.iScrollRange
                            Print #fp, "wea.iLinesVisible = "wea.iLinesVisible
                        #EndIf
                        iStart=ps.rcPaint.nTop\wea.cyChar
                        iFinish=Ceil(ps.rcPaint.nBottom/wea.cyChar)-1
                        #If %DEBUG
                            Print #fp, "iStart            = "iStart
                            Print #fp, "iFinish           = "iFinish
                            Print #fp,
                            Print #fp, "i              i*wea.cyChar  iLine         strLine(iLine)"
                            Print #fp, "======================================================================"
                        #EndIf
                        For i=iStart To iFinish
                          iLine=wea.iBegin+i
                          If iLine<=%LAST_LINE Then
                             #If %DEBUG
                                 Print #fp, i,i*wea.cyChar,iLine,strLine(iLine)
                             #EndIf
                             Call TextOut(hDC,0,i*wea.cyChar,ByVal StrPtr(strLine(iLine)),Len(strLine(iLine)))
                          End If
                        Next i
                        Call EndPaint(wea.hWnd,ps)
                        #If %DEBUG
                            Print #fp,
                            Print #fp, "Leaving fnWndProc_OnPaint()"
                            Print #fp, : Print #fp, : Print #fp,
                        #EndIf
                        '
                        fnWndProc_OnPaint=0
                      End Function                 
                      '
                      '
                      Function fnWndProc_OnClose(wea As WndEventArgs) As Long
                        #If %DEBUG
                            Close #fp
                        #EndIf
                        Erase strLine
                        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
                        Static wea As WndEventArgs
                        '
                        Select Case wMsg
                          Case %WM_CREATE
                            wea.hWnd=hWnd: wea.wParam=wParam: wea.lParam=lParam
                            fnWndProc=fnWndProc_OnCreate(wea)
                            Exit Function
                          Case %WM_SIZE
                            wea.hWnd=hWnd: wea.wParam=wParam: wea.lParam=lParam
                            fnWndProc=fnWndProc_OnSize(wea)
                            Exit Function
                          Case %WM_KEYDOWN
                            wea.hWnd=hWnd: wea.wParam=wParam: wea.lParam=lParam
                            fnWndProc=fnWndProc_OnKeyDown(wea)
                            Exit Function
                          Case %WM_VSCROLL
                            wea.hWnd=hWnd: wea.wParam=wParam: wea.lParam=lParam
                            fnWndProc=fnWndProc_OnVScroll(wea)
                            Exit Function
                          Case %WM_PAINT
                            wea.hWnd=hWnd: wea.wParam=wParam: wea.lParam=lParam
                            fnWndProc=fnWndProc_OnPaint(wea)
                            Exit Function
                          Case %WM_CLOSE
                            wea.hWnd=hWnd: wea.wParam=wParam: wea.lParam=lParam
                            fnWndProc=fnWndProc_OnClose(wea)
                            Exit Function
                        End Select
                        '
                        fnWndProc=DefWindowProc(hWnd,wMsg,wParam,lParam)
                      End Function
                      '
                      '
                      Function WinMain(ByVal hIns As Long, ByVal hPrev As Long, ByVal lpCmdLine As Asciiz Ptr, ByVal iShow As Long) As Long
                        Local hMainWnd As Dword,dwStyle As Dword
                        Local winclass As WndClassEx
                        Local szAppName As Asciiz*16
                        Local Msg As tagMsg
                        '
                        szAppName="Scroll Window"
                        winclass.cbSize=SizeOf(winclass)                       :winclass.style=%CS_HREDRAW Or %CS_VREDRAW
                        winclass.lpfnWndProc=CodePtr(fnWndProc)                :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=GetStockObject(%WHITE_BRUSH)    :winclass.lpszMenuName=%NULL
                        winclass.lpszClassName=VarPtr(szAppName)               :winclass.hIconSm=LoadIcon(hIns, ByVal %IDI_APPLICATION)
                        Call RegisterClassEx(winclass)
                        dwStyle=%WS_THICKFRAME Or %WS_MINIMIZEBOX Or %WS_VISIBLE Or %WS_VSCROLL Or %WS_SYSMENU
                        hMainWnd=CreateWindowEx(0,szAppName,szAppName,dwStyle,200,100,300,228,%HWND_DESKTOP,0,hIns,ByVal 0)
                        Call ShowWindow(hMainWnd,iShow)
                        Call UpdateWindow(hMainWnd)
                        While GetMessage(Msg,%NULL,0,0)
                          Call TranslateMessage(Msg)
                          Call DispatchMessage(Msg)
                        Wend
                        '
                        Function=msg.wParam
                      End Function
                      You will note that a #DEBUG conditional compilation statement exists near the top of the file. If %DEBUG is defined
                      as %TRUE an Output.txt log file will be created for you in the directory in which the program is running. If it is %FALSE
                      there will be no file created. If you havn't run this program yet do so now. The extra features incorporated into this
                      program are as follows:

                      1) Window is resizable through dragging window borders or minimize and maximize button;

                      2) Scroll Page Up and Scroll Page Down were implemented in the typical way by clicking in the scroll bar track;

                      3) Thumbtrack processing was enabled for dragging the scroll bar thumb;

                      4) A keyboard interface using the cursor up, cursor down, Page Up, and Page Down keys was implemented.

                      The key to many of these features was the addition of a %WM_SIZE message handler - fnWndProc_OnSize. If you examine
                      the %WM_CREATE processing in the first program - ScrollWindow1.bas, you will see that in that program the size of the
                      program's window was determined by a call to GetClientRect(). The value returned by that call remained constant for the
                      duration of that program's run time life because when the CreateWindow() call was made in WinMain() that created the window,
                      I used the following style variable in the call...

                      dwStyle=%WS_MINIMIZEBOX Or %WS_VISIBLE Or %WS_VSCROLL Or %WS_SYSMENU

                      This style is such that it will create a window that can't be resized. In the latter program - ScrollWindow.bas I
                      used this combination of styles...

                      dwStyle=%WS_THICKFRAME Or %WS_MINIMIZEBOX Or %WS_VISIBLE Or %WS_VSCROLL Or %WS_SYSMENU

                      You may note the addition of the %WS_THICKFRAME window style. This will cause the window to have a resizable border.
                      To understand the effect resizing has on the scrolling of a window you have to consider how the values returned by that
                      GetClientRect() call were used. We needed them to determine the count of visible lines on the screen. This count is of course
                      necessary because when the user wishes to scroll up or down, we need to know how many lines need to be scrolled. Here is
                      the pertinent line of code from that WM_CREATE handler of ScrollWindow1.bas...

                      Local rc As RECT

                      Call GetClientRect(wea.hWnd,rc)
                      wea.iLinesVisible=Fix(rc.nBottom/wea.cyChar)

                      Clearly, we divided the value obtained from the bottom member of the rc structure/type with the character height to get
                      the number of lines of text visible on the screen. Since the window couldn't be resized, once that value was set upon window
                      creation, it remained constant for the duration of the program.

                      Well, if we are going to allow the user to resize the window, that number isn't going to be a constant anymore. So how
                      do you determine it?

                      If you recall back in the second post in this Api tutorial there is a program named Form2.bas. That program had
                      resizable borders and displayed in the client area of the window the window height and width as the borders were resized. Do
                      you remember how we did that? What we did was put a %WM_SIZE message handler in the program and obtained in that the
                      dimensions of the client window through the parameters passed to us in the window procedure. Specifically, the window's client
                      area height is obtained through the high order 16 bit word of the 32 bit lParam parameter, i.e.,

                      wea.iLinesVisible = Fix( Hiwrd(wea.lParam) / wea.cyChar ).

                      Once you have obtained the new size of the window, all that needs to be done then is recalculate a new Scroll Range like
                      so...

                      wea.iScrollRange=%LAST_LINE-wea.iLinesVisible+1

                      If you are having any trouble with the concept of 'Scroll Range', perhaps thinking about it like as follows will help.
                      Say for example you have a hundred lines of text numbered 1 to 100. Further, say you have a window that shows only 10 lines
                      of text, which, upon program start up are visible as lines 1 through 10. How many 'clicks' of the scroll bar are needed then
                      to see the additional 90 lines of text in a buffer containing 100 lines? Surely not 100 if you can already see ten of them!
                      The answer is of course 90. That is all that is really going on with the concept of Scroll Range. The only other factor is
                      that the minimum scroll position and the maximum scroll position must be set, and it isn't hard at all to manage to be off by
                      one due to 'fence post' errors.

                      In ScrollWindow.bas the last subscript of the array strLine() is 100. Since the zeroeth element was used that makes for
                      101 elements. Since iLinesVisible is a 1 based count we add 1 to the difference to come up with the zero based scroll range.
                      This is then set in the SCROLLINFO TYPE with SetScrollInfo(). Don't worry too much about the numbers. Sometimes they have
                      to be 'juggled' to make it work right!

                      That's really just about it! I'd recommend highly that you experiment with the program by doing a very limited number
                      of things to it, then opening the Output.txt debug log file to study the results of your actions. I'm saying to do very few
                      things because of the furious activity generated by nearly anything you do, especially sizing, page up - page -down, or
                      thumbtrack events. Just a few seconds of any of those activities will result in an output file over a megabyte - and that
                      is far too much information to understand or sort out. Good Luck!

                      ------------------
                      Fred
                      "fharris"+Chr$(64)+"evenlink"+Chr$(46)+"com"
                      Fred
                      "fharris"+Chr$(64)+"evenlink"+Chr$(46)+"com"

                      Comment


                      • #12
                        Scrolling Controls Instead of Text

                        Scrolling Windows Controls Instead of Text

                        The past two 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()…

                        Code:
                        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…

                        Code:
                        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.

                        Code:
                        #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 
                          [email protected]        '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
                        Last edited by Fred Harris; 6 Aug 2008, 03:38 PM.
                        Fred
                        "fharris"+Chr$(64)+"evenlink"+Chr$(46)+"com"

                        Comment

                        Working...
                        X