Author Topic: Fred's Tutorial #5: Windows API Tutorial: The Open File Dialog Box  (Read 16346 times)

0 Members and 1 Guest are viewing this topic.

Offline Frederick J. Harris

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 1005
  • Gender: Male
    • Frederick J. Harris
Form5.bas (For versions Of PB Win before v10.  See last post in this thread for a v10 translation)

    This is the fifth tutorial in a sequence of tutorials I have started to help introduce programmers to using the original Windows Application Programming Interface to code Windows programs.  The first is at…

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

and an index and links to the others can be found within the introduction in that one.  This tutorial is primarily about the Common Dialog Box Library  and  secondarily shows how to put a simple dialog box in a program through a resource script.  A zip file - Form5.zip - is attached at the end of this document.  It contains both source code files (Form5.bas and Form5.rc) and this document in .doc format.

     The Common Dialog Box Library, ComDlg32.dll, is something that you are going to have to take very seriously as a programmer.  It is an important component of Windows, and the standard interfaces it provides to users in terms of enabling them to access their files and resources has contributed greatly to the ascendency of graphical user interfaces in general and of the Windows operating system in particular.  In this tutorial I want to get you started with the Open File Dialog Box, which is certainly one of the most important ones.  Most of the others are just variations on this one.  If you followed along with my Tutorial #1, that is, Form1.bas, you'll find that you are working with the very same concepts here.  Specifically, back in Form1 we started out by trying to get the size of the desktop work area with the SystemParametersInfo() function, and in doing that we had to create a RECT variable rc, and pass that variable to the function by reference (a pointer, in other words) in order for the function to return the size of the desktop to us in that TYPE RECT variable.  

     Towards the end of Form1.bas we encountered a more complicated type, that is a WNDCLASSEX type, and we saw how we could fill out the various fields of this type, and pass the address of a variable of this type to the RegisterClassEx() function, and in that way create a general template for a class from which we could instantiate a real window through a CreateWindowEx() function call.

     In this tutorial we'll be doing just about the exact same thing, but we'll be dealing with different objects.  Instead of a simple RECT struct/type that only contains four fields, ie., nTop, nBottom, etc., we'll be working with the OPENFILENAME struct/type, and that entity contains 20 fields.  Its pretty big.  However, the underlying principles are exactly the same.  We'll pass the runtime address of a variable of the OPENFILENAME type to a function named GetOpenFileName().  When that function gets called it will present an Open File Dialog Box to the user.  This open file dialog box, as well as all the others, are a part of Windows.  We do not have to create them ourselves.  When we call that GetOpenFileName() function Windows will display the dialog box to the user.  When the user selects a file the function will return and indicate to us in a TRUE/FALSE return value whether it has succeeded in obtaining a file name from the user, and if it has that file name will be waiting for us in a buffer pointed to by one of the asciiz pointer members of the struct/type we passed to the function.  Whew!  That was a mouthfull.  I better slow down and fill in the details, right?

     We'll start with the API documentation on the GetOpenFileName() function, which is as follows...

GetOpenFileName

The GetOpenFileName function creates an Open common dialog box that lets the user specify the drive, directory,
and the name of a file or set of files to open.

BOOL GetOpenFileName
(
  LPOPENFILENAME lpofn   // address of structure with initialization data
);
 
Parameters

lpofn           Pointer to an OPENFILENAME structure that contains information used to initialize the dialog
                 box. When GetOpenFileName returns, this structure contains information about the user's file
                 selection.
Return Values

If the user specifies a filename and clicks the OK button, the return value is nonzero. The buffer pointed to by
the lpstrFile member of the OPENFILENAME structure contains the full path and filename specified by the user.

If the user cancels or closes the Open dialog box or an error occurs, the return value is zero. To get extended
error information, call the CommDlgExtendedError function, which can return one of the following values...

     That looks pretty easy, doesn't it?  Well, it does until you look at the LPOPENFILENAME variable that is suppossed to be passed to the function.  Here it is reproduced below and I've done something you've probably never seen, and that is place the PowerBASIC TYPE declaration right beside the C declaration, for comparison purposes.
Code: [Select]
 C struct OPENFILENAME                       PowerBASIC TYPE OPENFILENAME

typedef struct tagOFN {                     TYPE OPENFILENAME
 DWORD         lStructSize;                   lStructSize       AS DWORD
 HWND          hwndOwner;                     hWndOwner         AS DWORD
 HINSTANCE     hInstance;                     hInstance         AS DWORD
 LPCTSTR       lpstrFilter;                   lpstrFilter       AS ASCIIZ PTR
 LPTSTR        lpstrCustomFilter;             lpstrCustomFilter AS ASCIIZ PTR
 DWORD         nMaxCustFilter;                nMaxCustFilter    AS DWORD
 DWORD         nFilterIndex;                  nFilterIndex      AS DWORD
 LPTSTR        lpstrFile;                     lpstrFile         AS ASCIIZ PTR
 DWORD         nMaxFile;                      nMaxFile          AS DWORD
 LPTSTR        lpstrFileTitle;                lpstrFileTitle    AS ASCIIZ PTR
 DWORD         nMaxFileTitle;                 nMaxFileTitle     AS DWORD
 LPCTSTR       lpstrInitialDir;               lpstrInitialDir   AS ASCIIZ PTR
 LPCTSTR       lpstrTitle;                    lpstrTitle        AS ASCIIZ PTR
 DWORD         Flags;                         Flags             AS DWORD
 WORD          nFileOffset;                   nFileOffset       AS WORD
 WORD          nFileExtension;                nFileExtension    AS WORD
 LPCTSTR       lpstrDefExt;                   lpstrDefExt       AS ASCIIZ PTR
 DWORD         lCustData;                     lCustData         AS LONG
 LPOFNHOOKPROC lpfnHook;                      lpfnHook          AS DWORD
 LPCTSTR       lpTemplateName;                lpTemplateName    AS ASCIIZ PTR
}OPENFILENAME;                              END TYPE
    Taking a high level view I think you would have to say they are pretty similiar.  In C the variable type comes first, then the name of the variable.  In PowerBASIC that's reversed.  In terms of the variable types themselves, some of them are even named the same.  Note the DWORD and WORD types.  Actually, the HWND and HINSTANCE types in Api are just redefinitions of more fundamental C data types such as unsigned int, which is four bytes or 32 bits on most present Windows systems.  These are further redefined  through what is known as a typedef in C, so the types are even more similiar than at first sight.  The major differences seem to be with the LPCTSTR and LPTSTR data types.  These too are data type redefinitions of character pointers, which, if you recall, are the way C represents strings, ie., as arrays of characters.  PowerBASIC represents this 'low-level' idea of a string as an Asciiz Ptr.  Interestingly enough, even though one might hope to somehow obtain what we in BASIC think of as a 'string' from this complicated contrivance, there really aren't any 'strings' in it.  There certainly are piles of pointers to strings but nowhere to be found is a good old fashioned BASIC string.  Actually, they are all either 32 bit or 16 bit integers (mostly 32 bit).  And don't think for a minute that pointers are strings.  They most definitely are not.  They hold (in 32 bit Windows) the 32 bit address of something else.  And it is that 'something else' - a file name string - that we want.

     So how does a type containing only 32 bit integers help us in obtaining a string of characters that represents a file name string?  The answer lies in what those 32 bit Asciiz Ptr or LPTSTR variables are pointing at.  If they can be safely pointed at memory that we have obtained somehow that has enough space to hold the number of characters in a string a user might select with the dialog box, then perhaps we can somehow get at that 'string' and use it in our program.  Providing space for string data is certainly something you have done before in whatever programming you have done to get you this far, but if that programming was in some version of BASIC using BASIC dynamic strings, it may not strictly apply very well to the situation that confronts us here in finding 'room' for a string the user has selected with the dialog box.  Bear with me while I elaborate.

     Suppose you define a variable like so...

     Dim strFileName As String

     By doing that havn't you provided 'space' where a string might be assigned?  Actually not.  Dim is an executable statement that will cause the PowerBASIC compiler to obtain a string handle from Windows that can later be used to allocate storage for a string if an assignment statement later assigns a string to the variable.  But as such, the above Dim statement won't provide any 'space' containing any 'room' for a string.  A dynamic string can only obtain memory space for a string as a result of an assignment statement where the right operand of the statement is a string to be assigned.  For example, if the following statement...

     strFileName = "C:\Code\TASM\bin\"

is encountered in a program, it will cause the compiler to generate code which will call OLE String Engine memory allocation functions that will request that at least 17 bytes of memory be allocated into which the text can be moved and stored.  What these functions will return is a pointer to the memory where the string can be placed. If later the program encounters another program line that causes the string to grow, such as...

     strFileName = strFileName & "Hello.asm"

then the compiler will generate more code to request of Windows that more memory be allocated for the extra bytes.  This further allocation may necessitate releasing the original memory for the first string, and the movement of both strings to a new memory address.  The reason I'm telling you this is that if you looked at the OPENFILENAME type above you might have noticed a particularly auspicious looking variable about midway down in the type like so...

     lpstrFile         AS ASCIIZ PTR

...that certainly looks like it might be a place where we could get a file name string from the GetOpenFileName() function after it returns.  This may lead you to think that somehow the file name string will be stored 'in' that variable, or that a dynamic string you create with a Dim statement can be assigned to that variable in the type like so...

     lpstrFile = strFileName

...and that somehow you can 'pry' a file name string out of it after the function returns.  Well, none of that will work.  If you don't provide memory to store the file name string before you call the GetOpenFileName() function, the program will compile but either crash or corrupt memory somewhere as soon as it tries to store a string of bytes at a wild pointer.  In the second case, if you try to set lpstrFile to strFileName and then compile that, you'll find it won't even compile.  You'll get this compile time error from PowerBASIC...

     The compiler has found a string operand in a position where a numeric operand should be, or a type
     mismatch has been detected.

     This should make it clear in your mind that a pointer to a string isn't a string.  So you may be wondering, "How are we going to use this thing (OPENFILENAME Type) to get a file name if all it contains are numbers?"  Well, if you refer back to the WNDCLASSEX type in Form1.bas you'll see that we were actually confronted with a similiar problem there.  One of the fields of that type was as follows...

     lpszClassName As Asciiz Ptr

     ...and what we did there was define a variable like so...

     Local szClassName As Asciiz*6

     ...and we assigned the name "Form1" to the asciiz string like this...

     szClassName = "Form1"

     ...which then allowed us to assign the address of the string to the Asciiz Ptr member of the type like so...

     wc.lpszClassName = VarPtr(szClassName) 'Simple, but very important!

     You may wish to review this material in Form1.bas if you need to, because it has to be clear in your mind what is happening here if you wish to continue with Api coding techniques.  Due to the importance of these concepts I spent considerable time with it in Form1.bas, even providing C and PowerBASIC versions of console programs showing memory allocation/addressing details.
      
     What we need to do then in preparation for a call to the GetOpenFileName() function is declare a number of asciiz strings of definite predefined lengths that by their very declaration will cause memory to be allocated into which the GetOpenFileName() function can safely place strings of characters without overwriting memory belonging to this or other processes.  And while you won't be any more successful in assigning these asciiz strings to the Asciiz Ptr OPENFILENAME members requiring addresses than you were with assigning dynamic strings to them, you most certainly will have no difficulties assigning the addresses of these string buffers to the members using the VarPtr() function, which function returns addresses of variables, not their contents.

     Now the amount of space required to store a file name string can't be absolutely known beforehand because you have no control over where the user of your program might navigate to in his/her quest for a file to open.  The only thing that can be done in terms of determining the size of the Asciiz string to declare is to make sure it is large enough to accomodate any string that may exist on the system.  Equates have been defined in the Win32Api.inc file for PowerBASIC and in Windows.h header file for C and they are generally used in specifying the size of Asciiz character string buffers to declare.  Here are the applicable equates from the PowerBASIC Include file...

Code: [Select]
%MAX_PATH  = 260  ' max. length of full pathname
%MAX_DRIVE = 3    ' max. length of drive component
%MAX_DIR   = 256  ' max. length of path component
%MAX_FNAME = 256  ' max. length of file name component
%MAX_EXT   = 256  ' max. length of extension component

     So the idea is to dimension an Asciiz string buffer like so to hold the file name...

     Local szFileName As Asciiz * %MAX_PATH

     ...and then assign the address of this buffer to the ofn.lpstrFile member like so...

     ofn.lpstrFile=VarPtr(szFileName).   ''''Note the VarPtr() function!!!

     Not wanting to keep you in suspense much longer, reproduced below is the procedure OnOpen(), which routine does the necessary preliminary work to set up the call to GetOpenFileName(), and finally places the chosen file name into a text box on the form (if the user selected a filename and didn't cancel)...

Code: [Select]
Sub OnOpen(wea As WndEventArgs)     'Handles File >> Open... From WM_COMMAND
  Local szTitleName As Asciiz*(%MAX_FNAME+%MAX_EXT)
  Local szFileName As Asciiz*%MAX_PATH
  Local szIniDir As Asciiz*256
  Local strFilter As String
  Local ofn As OPENFILENAME
  
  strFilter= _   'Dynamic strings work best here with PowerBASIC as you can embed NULLs.
  "bas files (*.bas), inc files (*.inc), rc files (*.rc)"+Chr$(0)+"*.bas;*.inc;*.rc"+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)
  szIniDir=CurDir$                                     'Initial directory
  ofn.lStructSize=SizeOf(OPENFILENAME)                 'Size of structure/type
  ofn.hWndOwner=Wea.hWnd                               'Owned by Main Program Window
  ofn.hInstance=GetWindowLong(wea.hWnd,%GWL_HINSTANCE) 'The instance handle can be gotten many ways
  ofn.lpstrFilter=StrPtr(strFilter)                    'Filters file extensions shown in dialog box
  ofn.nMaxFile=%MAX_PATH                                
  ofn.nMaxFileTitle=%MAX_FNAME+%MAX_EXT
  ofn.lpstrInitialDir = VarPtr(szIniDir)
  ofn.lpstrDefExt=%NULL
  ofn.Flags=%OFN_HIDEREADONLY Or %OFN_CREATEPROMPT
  ofn.lpstrFile=VarPtr(szFileName)                     'This is finally the file name string you want
  ofn.lpstrFileTitle=VarPtr(szTitleName)               'This is as above, but without the full path
  ofn.lpstrTitle=%NULL
  If GetOpenFileName(ofn) Then                         'Function returns non-zero if valid file chosen
     Call SetWindowText(GetDlgItem(Wea.hWnd,%IDC_FILENAME),szFileName)
  Else
     MsgBox("You Must Have Cancelled!")
  End If
End Sub

     Now that I've gone to such great lengths to impress upon you the necessity of using Asciiz strings of predefined lengths so as to provide buffers into which the GetOpenFileName() function can copy strings, I'd better do some fast explaining about strFilter in the above procedure.  You are no doubt aware of how the filters work in the 'Files of Type' ComboBox in the Open File Dialog Box.  The way they are actually stored in preparation to assignment in the ofn.lpstrFilter member of the OPENFILENAME type is as two dimensional arrays of null terminated strings, with a double null terminator at the end.  In the above example you can think of it something like this...
Code: [Select]
    "bas files (*.bas), inc files (*.inc), rc files (*.rc)" + Chr$(0) ,       "*.bas;*.inc;*.rc" + 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)
    Using PowerBASIC I don't seem to have success in storing these things in Asciiz string buffers because PowerBASIC ends the assignment to the buffer upon encountering the first null byte after the 'rc files (*.rc)' above in the first line. The end result is that the Open File Dialog Box works OK except there is no file filter and all the files are displayed.  Perhaps there may be other ways to solve this but what I do is assign the filter strings to a dynamic string (which can hold null bytes) and use the StrPtr() function to assign the address of that string buffer to the lpstrFilter pointer member of the Type.  This is satisfactory because the assignment of the strings to the dynamic string variable strFilter triggers a memory allocation into which the strings can comfortably be stored.  And the StrPtr() function will return the address of that string.  I seem to vaguely recall using another technique with PowerBASIC years ago that somehow involved the Remove$ statement, but the details now escape me.  In any case, this technique I am showing you is workable and easy.

     As you can see above one of the fields of the Type is the initial directory that is to be shown when the dialog box opens.  That can be set to the directory in which the program is first run by using the PowerBASIC CurDir$ keyword.

     As you can see toward the bottom of the function, if the GetOpenFileName() function returns a non zero number the file name string is copied to the text box in the middle of the Form.  The SetWindowText() function has two parameters, the first being the handle of the window into which the text is to be 'Set', and the second is a pointer to a buffer containing the desired text.  They don't let me use global variables around here so when I created the text box in fnWndProc_OnCreate() the handle of the text box was stored in a local variable which got thrown away and lost just as soon as the text box was created and the function exited.  So the only way we can get the handle of the text box back is to call GetDlgItem() and pass to it the handle to the text box's parent and the control identifier of the text box, which luckily we do know (there's more than one way to skin a cat!).  So all is not lost.  With the handle to the text box reclaimed we can set the file name in it.
        
     One final point I might make is that those buffers we painstakingly created above to create space for the GetOpenFileName() function to place strings into won't last very long.  They are all local variables stored on the stack, so as soon as the Open File Dialog closes and OnOpen() exits they become totally invalid and are gone.  However, at the very, very last moment of their existance their content is copied into the buffer provided by the text box - and so the string lives on!  It will safely exist there in the memory provided by the text box until you replace it with something else or you end the program.  Although I can't do it here without censure, it is not uncommon in real life applications to dimension the OPENFILENAME variable globally, or at least statically, so that avoids these problems.

     Here is the full text of Form5.bas.  First is the resource file, Form5.rc, then the source code file for Form5.bas.  If you need assistance compiling the resource file please refer to directions at the top of Form4.bas at      

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

In a nut shell though, all you have to do is paste Form5.rc into a blank PBEdit window and compile it.  The compiler will first generate a Form5.res and then a Form5.pbr file.  The Form5.pbr file is then compiled into the executable when you compile Form5.bas.  All these files are attached.

Code: [Select]
//Form5.rc
#include "resource.h"
#define IDM_OPEN        1000
#define IDM_SAVE        1002
#define IDM_EXIT        1004
#define IDM_SHOWDLG     2000
#define IDC_NAME        2100
#define IDM_HELP        3000

Form5 MENU
{
 POPUP "&File"
 {
  MENUITEM "&Open...",               IDM_OPEN
  MENUITEM "&Save...",               IDM_SAVE
  MENUITEM SEPARATOR
  MENUITEM "E&xit",                  IDM_EXIT
 }
 POPUP "O&ptions"
 {
  MENUITEM "Sho&w Dialog",           IDM_SHOWDLG
 }
 POPUP "&Help"
 {
  MENUITEM "&Help...",               IDM_HELP
 }
}

dlgBox DIALOG DISCARDABLE 30,90,340,40
STYLE WS_POPUP | WS_CAPTION | WS_DLGFRAME
CAPTION "What Do You Want To Do With Your Life?"
FONT 8, "MS Sans Serif"
BEGIN
  DEFPUSHBUTTON  "OK",IDOK,200,20,40,14
  PUSHBUTTON     "Cancel",IDCANCEL,145,20,40,14
  LTEXT          "In 80 Characters Or Less",IDC_STATIC,3,6,110,8
  EDITTEXT       IDC_NAME,85,4,250,12,ES_AUTOHSCROLL
END



And here is Form5.bas...

Code: [Select]
#Compile Exe
#Include "Win32api.inc"     'Main Windows Include File
#Include "Comdlg32.inc"     'Include file for the Common Dialog Box Library
#Resource "Form5.pbr"       'PowerBASIC resource file
%IDM_OPEN      =    1000    'Equate for File    >> Open...
%IDM_SAVE      =    1002    '                   >> Save...
%IDM_EXIT      =    1004    '                   >> Exit
%IDM_SHOWDLG   =    2000    'Equate for Options >> Show Dialog
%IDM_HELP      =    3000    'Equate for Help    >> Help...          
%IDC_FILENAME  =    4000    'Equate for Control ID of main text box on center of Form
%IDC_EXIT      =    4002    'Equate for Exit Button on Form
%IDC_NAME      =    2100    'Equate for Textbox on Dialog Box


Type WndEventArgs           'Type for passing window procedure parameters.  Allows me
  wParam As Long            'to shorten parameter list.  .NET does this all the time.
  lParam As Long            'See, for example pea for PaintEventArgs in the OnPaint()
  hWnd   As Dword           'Message Handler.
End Type


Function fnWndProc_OnCreate(wea As WndEventArgs) As Long   'Handles WM_CREATE message
  Local hCtrl As Dword

  'Text Box For Filename or Dialog Box Text
  hCtrl= _
  CreateWindowEx _
  ( _
    %WS_EX_CLIENTEDGE, _     'This gives a text box a nice 3D look
    "Edit", _                'Predefined 'edit' window class
    "", _                    'start out blank
    %WS_CHILD Or %WS_VISIBLE Or %WS_BORDER Or %ES_AUTOHSCROLL, _
    5,20,505,26, _           'edit controls have piles of styles
    Wea.hWnd, _              'parent of edit control, that is main window
    %IDC_FILENAME, _         'equate for control identifier
    GetWindowLong(wea.hWnd,%GWL_HINSTANCE), _
    Byval 0 _                'lpCreateParams -- we're not using any here
  )

  'Exit Button
  hCtrl=_
  CreateWindowEx _
  ( _
    0, _                     'Plain old button will work for me
    "Button", _              'Predefined 'edit' window class
    "Exit", _                'Let's make an exit button
    %WS_CHILD Or %WS_VISIBLE, _     'This works for simple buttons
    205,60,110,26, _                'coordinates & sizes of button
    Wea.hWnd, _                     'Parent of button
    %IDC_Exit, _                    'Equate for button (ctrl id)
    GetWindowLong(wea.hWnd,%GWL_HINSTANCE), _   'Get instance handle
    Byval 0 _                       'No Creation Data
  )

  fnWndProc_OnCreate=0              'Return 0 to Windows
End Function


Sub OnOpen(wea As WndEventArgs)     'Handles File >> Open... From WM_COMMAND
  Local szTitleName As Asciiz*(%MAX_FNAME+%MAX_EXT)
  Local szFileName As Asciiz*%MAX_PATH
  Local szIniDir As Asciiz*256
  Local strFilter As String
  Local ofn As OPENFILENAME
  
  strFilter= _   'Dynamic strings work best here with PowerBASIC as you can embed NULLs.
  "bas files (*.bas), inc files (*.inc), rc files (*.rc)"+Chr$(0)+"*.bas;*.inc;*.rc"+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)
  szIniDir=CurDir$                                     'Initial directory
  ofn.lStructSize=SizeOf(OPENFILENAME)                 'Size of structure/type
  ofn.hWndOwner=Wea.hWnd                               'Owned by Main Program Window
  ofn.hInstance=GetWindowLong(wea.hWnd,%GWL_HINSTANCE) 'The instance handle can be gotten many ways
  ofn.lpstrFilter=StrPtr(strFilter)                    'Filters file extensions shown in dialog box
  ofn.nMaxFile=%MAX_PATH                                
  ofn.nMaxFileTitle=%MAX_FNAME+%MAX_EXT
  ofn.lpstrInitialDir = VarPtr(szIniDir)
  ofn.lpstrDefExt=%NULL
  ofn.Flags=%OFN_HIDEREADONLY Or %OFN_CREATEPROMPT
  ofn.lpstrFile=VarPtr(szFileName)                     'This is finally the file name string you want
  ofn.lpstrFileTitle=VarPtr(szTitleName)               'This is as above, but without the full path
  ofn.lpstrTitle=%NULL
  If GetOpenFileName(ofn) Then                         'Function returns non-zero if valid file chosen
     Call SetWindowText(GetDlgItem(Wea.hWnd,%IDC_FILENAME),szFileName)
  Else
     MsgBox("You Must Have Cancelled!")
  End If
End Sub


Sub OnSave(wea As WndEventArgs)                         'Handles File >> Save... From WM_COMMAND
  MsgBox("You Clicked File >> Save...")
End Sub


Sub OnExit(wea As WndEventArgs)                         'Handles File >> Exit From WM_COMMAND
  MsgBox("Send A Message To Windows To Close The Application.")
  Call SendMessage(wea.hWnd,%WM_CLOSE,0,0)
End Sub


Function ModalDlgProc(ByVal hwndDlg As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
  Local szLocalBuffer As Asciiz*80
  Local hwndEdit As Dword

  Select Case As Long wMsg
    Case %WM_INITDIALOG
      hwndEdit=GetDlgItem(hwndDlg,%IDC_NAME)
      Call SetFocus(hwndEdit)                                 'Set focus to text box when
      Function=%FALSE                                         'dialog box opens
    Case %WM_COMMAND
      Select Case Lowrd(wParam)
        Case %IDOK
          hwndEdit=GetDlgItem(hwndDlg,%IDC_NAME)              'GetDlgItem() is a really useful function that plays
          Call GetWindowText(hwndEdit,szLocalBuffer,80)       'a big part in allowing you to eliminate globals
          Call SetWindowText _
          ( _
            GetDlgItem(GetParent(hwndDlg),%IDC_FILENAME), _   'Get parent of dialog box, then
            szLocalBuffer _                                   'control id of text box on main
          )                                                   'form into which to place text.
          Call EndDialog(hwndDlg,%TRUE)                       'EndDialog() ends the dialog box.  If %TRUE is
          Function=%TRUE                                      'returned the user hit the [ENTER] button or clicked
          Exit Function                                       'OK.  If the user cancels or x's out return %FALSE
        Case %IDCANCEL                                        'as the 2nd parameter of EndDialog().
          Call EndDialog(hwndDlg,%FALSE)
          Function=%TRUE
          Exit Function
      End Select
  End Select

  Function=%FALSE
End Function


Sub OnShowDialog(wea As WndEventArgs)
  If DialogBox(GetWindowLong(wea.hWnd,%GWL_HINSTANCE),"dlgBox",wea.hWnd,CodePtr(ModalDlgProc)) Then
     MsgBox _                                                     'DialogBox() is the function that
     ( _                                                          'causes a dialog box to be shown.
       "You Clicked The 'OK' Button Or Pressed The [ENTER] Key!" _'The Function takes four parameters.
     )                                                            'The first (retrieved here by
  Else                                                            'GetWindowLong() Is the program
     MsgBox _                                                     'instance handle.  The second Is
     ( _                                                          'the String which names the Dialog
       "You Cancelled Out!" _                                     'Box In Form5.rc. The third is the
     )                                                            'parent window handle, and the
  End If                                                          'fourth is a Pointer To the Dialog
End Sub                                                           'Box procedure.


Sub OnHelp(wea As WndEventArgs)
  MsgBox("There Is No Help!")
End Sub


Function fnWndProc_OnCommand(wea As WndEventArgs) As Long     'Handles WM_COMMAND message
  Select Case LoWrd(wea.wParam)
    Case %IDM_OPEN
      Call OnOpen(wea)
    Case %IDM_SAVE
      Call OnSave(wea)
    Case %IDM_EXIT
      Call OnExit(wea)
    Case %IDC_EXIT
      Call OnExit(wea)
    Case %IDM_SHOWDLG
      Call OnShowDialog(wea)
    Case %IDM_HELP
      Call OnHelp(wea)
  End Select

  fnWndProc_OnCommand=0
End Function


Function fnWndProc_OnClose(wea As WndEventArgs) As Long       'Handles WM_CLOSE message
  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)

  fnWndProc_OnClose=0
End Function


Function fnWndProc_OnDestroy(wea As WndEventArgs) As Long     'Handles WM_DESTROY message
  Call PostQuitMessage(0)
  fnWndProc_OnDestroy=0
End Function


Function fnWndProc(ByVal hWnd As Long,ByVal wMsg As Long,ByVal wParam As Long,ByVal lParam As Long) As Long
  Local wea As WndEventArgs

  Select Case wMsg
    Case %WM_CREATE
      wea.wParam=wParam: wea.lParam=lParam: wea.hWnd=hWnd
      fnWndProc=fnWndProc_OnCreate(wea)
      Exit Function
    Case %WM_COMMAND
      wea.wParam=wParam: wea.lParam=lParam: wea.hWnd=hWnd
      fnWndProc=fnWndProc_OnCommand(wea)
      Exit Function
    Case %WM_CLOSE
      wea.wParam=wParam: wea.lParam=lParam: wea.hWnd=hWnd
      fnWndProc=fnWndProc_OnClose(wea)
      Exit Function
    Case %WM_DESTROY
      wea.wParam=wParam: wea.lParam=lParam: wea.hWnd=hWnd
      fnWndProc=fnWndProc_OnDestroy(wea)
      Exit Function
  End Select

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


Function WinMain(ByVal hIns As Long, ByVal hPrev As Long, ByVal lpCmdLn As Asciiz Ptr, ByVal iShow As Long) As Long
  Local winclass As WndClassEx
  Local szAppName As Asciiz*8
  Local hMainWnd As Dword
  Local Msg As tagMsg

  szAppName="Form5"                                       : winclass.cbSize=SizeOf(winclass)
  winclass.lpszClassName=VarPtr(szAppName)                : winclass.lpfnWndProc=CodePtr(fnWndProc)
  winclass.style=%CS_HREDRAW Or %CS_VREDRAW               : winclass.hInstance=hIns
  winclass.cbClsExtra=0                                   : winclass.cbWndExtra=0
  winclass.hIcon=LoadIcon(%NULL, ByVal %IDI_APPLICATION)  : winclass.hCursor=LoadCursor(%NULL, ByVal %IDC_ARROW)
  winclass.hbrBackground=%COLOR_BTNFACE+1                 : winclass.lpszMenuName=VarPtr(szAppName)
  Call RegisterClassEx(winclass)
  hMainWnd=CreateWindowEx(0,szAppName,"Form5",%WS_OVERLAPPEDWINDOW,350,300,525,155,0,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
« Last Edit: April 07, 2011, 10:26:38 PM by Frederick J. Harris »

Offline Kent Sarikaya

  • Sr. Member
  • ****
  • Posts: 368
Re: Fred's Tutorial #5: Windows API Tutorial: The Open File Dialog Box
« Reply #1 on: December 16, 2007, 03:42:17 AM »
Fred thanks you for these really fine articles. Now that I am getting a little familiar with PowerBasic and able to dive deeper into areas, I am finding your articles along with all the other great content on this forum to be invaluable. Thanks!

Offline Frederick J. Harris

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 1005
  • Gender: Male
    • Frederick J. Harris
Re: Fred's Tutorial #5: Windows API Tutorial: The Open File Dialog Box
« Reply #2 on: December 20, 2007, 03:45:02 PM »
Your welcome Kent.  Glad you're finding them useful!

Offline Paul Breen

  • Jr. Member
  • **
  • Posts: 65
Re: Fred's Tutorial #5: Windows API Tutorial: The Open File Dialog Box
« Reply #3 on: April 12, 2008, 08:48:17 AM »
Very nicely done, and even though I've been able to get the open file dialog to work, I still learned something. Any chance of showing how one "hooks" into this common dialog to customize it?
It has taken me a long time to come around, but the straight sdk mode is the best way to go especially with help like this. Showing the c and the basic declaration code was a nice touch.

Offline Frederick J. Harris

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 1005
  • Gender: Male
    • Frederick J. Harris
Re: Fred's Tutorial #5: Windows API Tutorial: The Open File Dialog Box
« Reply #4 on: April 13, 2008, 09:59:50 PM »
I don't have anything written up yet on hooking the dialog boxes.  Perhaps that would be a good topic.  However, over the years I've seen that topic come up occasionally on the PB Forum.  I expect a search there would bring up some posts/demos of that.

Online Patrice Terrier

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 2370
  • Gender: Male
    • www.zapsolution.com
Re: Fred's Tutorial #5: Windows API Tutorial: The Open File Dialog Box
« Reply #5 on: April 14, 2008, 06:29:29 AM »
Here is a quick cut and paste from my GDImage package, hooking the common open/save dialog file

Code: [Select]
TYPE OPENFILENAMEEX
    lStructSize       AS DWORD
    hWndOwner         AS DWORD
    hInstance         AS DWORD
    lpstrFilter       AS ASCIIZ PTR
    lpstrCustomFilter AS ASCIIZ PTR
    nMaxCustFilter    AS DWORD
    nFilterIndex      AS DWORD
    lpstrFile         AS ASCIIZ PTR
    nMaxFile          AS DWORD
    lpstrFileTitle    AS ASCIIZ PTR
    nMaxFileTitle     AS DWORD
    lpstrInitialDir   AS ASCIIZ PTR
    lpstrTitle        AS ASCIIZ PTR
    Flags             AS DWORD
    nFileOffset       AS WORD
    nFileExtension    AS WORD
    lpstrDefExt       AS ASCIIZ PTR
    lCustData         AS LONG
    lpfnHook          AS DWORD
    lpTemplateName    AS ASCIIZ PTR
    '--- new Windows 2000 structure members ---
    pvReserved        AS LONG
    dwReserved        AS DWORD
    FlagsEx           AS DWORD
END TYPE

FUNCTION zOpenFileDialog (BYVAL hWnd AS DWORD, _           ' parent window
                          BYVAL Caption AS STRING, _       ' caption
                          Filespec AS STRING, _            ' filename
                          BYVAL InitialDir AS STRING, _    ' start directory
                          BYVAL Filter AS STRING, _        ' filename filter
                          BYVAL DefExtension AS STRING, _  ' default extension
                          Flags AS DWORD ) AS LONG         ' flags

    LOCAL Ofn AS OPENFILENAMEEX
    LOCAL zFile       AS ASCIIZ * %MAX_PATH
    LOCAL zFileTitle  AS ASCIIZ * %MAX_PATH
    LOCAL zInitialDir AS ASCIIZ * %MAX_PATH
    LOCAL zTitle      AS ASCIIZ * %MAX_PATH
    LOCAL zDefExt     AS ASCIIZ * %MAX_PATH

    REPLACE $zLim WITH $NUL IN Filter
    Filter = Filter + $NUL

    IF LEN(InitialDir) = 0 THEN InitialDir = CURDIR$

    zInitialDir = InitialDir   + $NUL
    zFile       = Filespec     + $NUL
    zDefExt     = DefExtension + $NUL
    zTitle      = Caption      + $NUL

    IF zOsVersion < 500 THEN ' IF 95/98/ME
       ofn.lStructSize       = SIZEOF(ofn) - 12
    ELSE
       ofn.lStructSize       = SIZEOF(ofn)
    END IF
    ofn.hWndOwner         = hWnd
    ofn.lpstrFilter       = STRPTR(Filter)
    ofn.lpstrFile         = VARPTR(zFile)
    ofn.nMaxFile          = SIZEOF(zFile)
    ofn.lpstrFileTitle    = VARPTR(zFileTitle)
    ofn.nMaxFileTitle     = SIZEOF(zFileTitle)
    ofn.lpstrInitialDir   = VARPTR(zInitialDir)
    IF LEN(zTitle) THEN
       ofn.lpstrTitle     = VARPTR(zTitle)
    END IF
    ofn.Flags             = Flags
    ofn.lpfnHook          = CODEPTR(zDlgHookProc)
    ofn.lpstrDefExt       = VARPTR(zDefExt)

    ofn.FlagsEx           = %OFN_EX_NOPLACESBAR

    'Note: following Select Case table must be adjusted to match used Filter string
    '      (also remember to do the same in zDlgHookProc procedure..)
    UperExt$ = UCASE$(DefExtension)
    IF UperExt$     = PARSE$(cfgStr$(40), 1) THEN
                      ofn.nFilterIndex = 1 ' PNG nFilterIndex decides Filytype combo's listitem
    ELSEIF UperExt$ = PARSE$(cfgStr$(40), 2) THEN
                      ofn.nFilterIndex = 2 ' JPG
    ELSEIF UperExt$ = PARSE$(cfgStr$(40), 3) THEN
                      ofn.nFilterIndex = 3 ' BMP
    ELSEIF UperExt$ = PARSE$(cfgStr$(40), 4) THEN
                      ofn.nFilterIndex = 4 ' TIF
    ELSE
                      ofn.nFilterIndex = 8 ' *.*
    END IF

    FUNCTION = GetOpenFilename(BYVAL VARPTR(ofn))

    Filespec = zFile
    Flags    = ofn.Flags

END FUNCTION

FUNCTION zSaveFileDialog (BYVAL hWnd AS DWORD, _          ' parent window
                          BYVAL Caption AS STRING, _      ' caption
                          Filespec AS STRING, _           ' filename
                          BYVAL InitialDir AS STRING, _   ' start directory
                          BYVAL Filter AS STRING, _       ' filename filter
                          BYVAL DefExtension AS STRING, _ ' default extension
                          Flags AS DWORD ) AS LONG        ' flags

    LOCAL Ofn         AS OPENFILENAMEEX
    LOCAL zFile       AS ASCIIZ * %MAX_PATH
    LOCAL zFileTitle  AS ASCIIZ * %MAX_PATH
    LOCAL zInitialDir AS ASCIIZ * %MAX_PATH
    LOCAL zTitle      AS ASCIIZ * %MAX_PATH
    LOCAL zDefExt     AS ASCIIZ * %MAX_PATH

    REPLACE $zLim WITH $NUL IN Filter
    Filter = Filter + $NUL

    IF LEN(InitialDir) = 0 THEN InitialDir = CURDIR$

    zInitialDir = InitialDir   + $NUL
    zFile       = Filespec     + $NUL
    zDefExt     = DefExtension + $NUL
    zTitle      = Caption      + $NUL

    IF zOsVersion < 500 THEN
       ofn.lStructSize       = SIZEOF(ofn) - 12
    ELSE
       ofn.lStructSize       = SIZEOF(ofn)
    END IF
    ofn.hWndOwner         = hWnd
    ofn.lpstrFilter       = STRPTR(Filter)
    ofn.lpstrFile         = VARPTR(zFile)
    ofn.nMaxFile          = SIZEOF(zFile)
    ofn.lpstrFileTitle    = VARPTR(zFileTitle)
    ofn.nMaxFileTitle     = SIZEOF(zFileTitle)
    ofn.lpstrInitialDir   = VARPTR(zInitialDir)
    IF LEN(zTitle) THEN
        ofn.lpstrTitle    = VARPTR(zTitle)
    END IF
    ofn.Flags             = Flags
    ofn.lpfnHook          = CODEPTR(zDlgHookProc)
    ofn.lpstrDefExt       = VARPTR(zDefExt)

    ofn.FlagsEx           = %OFN_EX_NOPLACESBAR

    'Note: following Select Case table must be adjusted to match used Filter string
    '      (also remember to do the same in zDlgHookProc procedure..)
    UperExt$ = UCASE$(DefExtension)
    IF UperExt$     = PARSE$(cfgStr$(40), 1) THEN
                      ofn.nFilterIndex = 1 ' PNG nFilterIndex decides Filytype combo's listitem
    ELSEIF UperExt$ = PARSE$(cfgStr$(40), 2) THEN
                      ofn.nFilterIndex = 2 ' JPG
    ELSEIF UperExt$ = PARSE$(cfgStr$(40), 3) THEN
                      ofn.nFilterIndex = 3 ' BMP
    ELSEIF UperExt$ = PARSE$(cfgStr$(40), 4) THEN
                      ofn.nFilterIndex = 4 ' TIF
    ELSE
                      ofn.nFilterIndex = 5
    END IF

    FUNCTION = GetSaveFilename(BYVAL VARPTR(ofn))

    Filespec = RTRIM$(InitialDir, $Anti) + $Anti + Filespec

    Filespec = zFile
    Flags = ofn.Flags

END FUNCTION

The important member is:
ofn.lpfnHook = CODEPTR(zDlgHookProc)

and here is the matching code

Code: [Select]
' Dialog hook procedure (centering the dialog based on the parent size, and increasing height of filetype combo)
FUNCTION zDlgHookProc(BYVAL hWnd&, BYVAL Msg&, BYVAL wParam&, BYVAL lParam&) AS LONG

    LOCAL rw AS RECT, rc AS RECT, ofnPtr AS OFNOTIFY PTR, zTxt AS ASCIIZ * %MAX_PATH
    LOCAL lp    AS POINTAPI

    STATIC hWndPreview&, SquareSize&

    SELECT CASE LONG Msg&

    CASE %WM_NOTIFY
         ofnPtr = lParam&
         SELECT CASE LONG @ofnPtr.hdr.Code

         CASE %CDN_INITDONE

              hParent& = GetParent(hWnd&)

              hSysListView& = GetDlgItem(hParent&, %lst1)

              CALL GetWindowRect(hSysListView&, rw)
              lp.X = rw.nLeft: lp.Y = rw.nTop: CALL ScreenToClient(hParent&, lp) ' 11-03-2003
              yPos& = lp.Y
              SquareSize& = rw.nBottom - rw.nTop
              IF GetSystemMetrics(%SM_CXSCREEN) < 801 THEN
                 SquareSize& = MIN&(150, SquareSize&)
              END IF

              CALL GetClientRect(hParent&, rc)       ' Get dialog pos. and size
              xPos& = rc.nRight                      ' New width

              Style& = %WS_CHILD OR %WS_VISIBLE ' OR %WS_BORDER
              StyleEx& = %WS_EX_STATICEDGE
              hWndPreview& = CreateWindowEx(StyleEx&, $GDImageClassName, _
                                 "", _               ' Optional full path name to picture
                                 Style&, _
                                 xPos&, yPos&, SquareSize&, SquareSize&, _
                                 hParent&, _
                                 %NULL, _
                                 zInstance,_
                                 BYVAL 0)                 ' creation parameters
              CALL ZI_SetProperty(hWndPreview&, %ZI_GradientTop, GetSysColor(%COLOR_BTNSHADOW))
              CALL ZI_SetProperty(hWndPreview&, %ZI_GradientBottom, GetSysColor(%COLOR_BTNSHADOW))
              CALL ZI_SetProperty(hWndPreview&, %ZI_FitToWindow, %ZI_QualityDefault)

            ' Set new size and center dialog in program window
              CALL GetWindowRect(GetParent(hParent&), rc)     ' Get program pos. and size
              W& = rc.nRight  - rc.nLeft
              H& = rc.nBottom - rc.nTop
              x& = rc.nLeft + (W& \ 2)                        ' Relative Center x&
              y& = rc.nTop  + (H& \ 2)                        ' Relative Center y&

              CALL GetWindowRect(hParent&, rc)                ' Get dialog pos. and size
              W& = rc.nRight  - rc.nLeft + SquareSize& + 7    ' New width
              H& = rc.nBottom - rc.nTop                       ' Same height
              x& = x& - (W& \ 2)                              ' Centered in program
              y& = y& - (H& \ 2)

              CALL SetWindowPos(hParent&, hParent&, x&, y&, W&, H&, %SWP_NOZORDER) ' Resize and center

            ' INCREASE HEIGHT OF DROPPED LIST IN FILETYPE COMBO (AUTO-ADJUSTS TO ITEM COUNT)
              hftCombo& = GetDlgItem(hParent&, %cmb1)         ' handle, Filetype combo
              IF hftCombo& THEN                               ' if we get handle
                 CALL GetClientRect(hftCombo&, rc)            ' get combo's width and set new height
                 CALL SetWindowPos(hftCombo&, %NULL, 0, 0, rc.nRight, 200, %SWP_NOMOVE OR %SWP_NOZORDER)
              END IF

              FUNCTION = 1: EXIT FUNCTION

         CASE %CDN_SELCHANGE ' Triggered when the selection changes in the file listbox
              picFileName$ = SPACE$(%MAX_PATH)
              df& = Sendmessage(@ofnPtr.hdr.hwndFrom, %CDM_GETFILEPATH, LEN(picFileName$), STRPTR(picFileName$))
              IF df& > 0 THEN
                 picFileName$ = LEFT$(picFileName$, df& - 1)
                 So& = INSTR(-1, picFileName$, $Dot)
                 IF So& THEN
                    ImgType$ = UCASE$(MID$(picFileName$, So&)) + $Dot
                    ImageType& = INSTR($GDIPLUSEXT, ImgType$)
                    IF ImageType& THEN

                       NewImage& = zLoadImageFromFile((picFileName$), bmW&, bmH&, 0)
                       IF zSetGdipImageHandle(hWndPreview&, NewImage&) THEN CALL ZI_UpdateWindow(hWndPreview&, %TRUE)

                    END IF
                 END IF
'
              END IF
              picFileName$ = ""

         CASE %CDN_TYPECHANGE
            ' Standard Save dialog blindly adds new extension, this changes it properly instead
              IF @ofnPtr.@lpOFN.nFilterIndex < 5 THEN             ' ignore last one, *.*
                 hftCombo& = GetDlgItem(GetParent(hWnd&), %cmb1)  ' handle, File type combo
                 IF zOsVersion < 500 THEN ID& = %edt1 ELSE ID& = %cmb13
                 hftEdit&  = GetDlgItem(GetParent(hWnd&), ID&)    ' handle, File name text field
                 CALL GetWindowText(hftEdit&, zTxt, SIZEOF(zTxt)) ' get name in text field
                 IF LEN(zTxt) THEN                                ' if we have something
                    lRes& = INSTR(-1, zTxt, $Dot)                 ' look for prefix (file type part)
                    IF lRes& THEN zTxt = LEFT$(zTxt, lRes& - 1)   ' if success, remove prefix
                    SELECT CASE LONG @ofnPtr.@lpOFN.nFilterIndex  ' compare selection against combo list
                    CASE 1: zTxt = zTxt + $Dot + LCASE$(PARSE$(cfgStr$(40), 1)) ' ".png"                  ' and add matching prefix
                    CASE 2: zTxt = zTxt + $Dot + LCASE$(PARSE$(cfgStr$(40), 2)) ' ".jpg"
                    CASE 3: zTxt = zTxt + $Dot + LCASE$(PARSE$(cfgStr$(40), 3)) ' ".bmp"
                    CASE 4: zTxt = zTxt + $Dot + LCASE$(PARSE$(cfgStr$(40), 4)) ' ".tif"
                    CASE 5: zTxt = zTxt + $Dot + LCASE$(PARSE$(cfgStr$(40), 5)) ' ".gif"
                    CASE 6: zTxt = zTxt + $Dot + LCASE$(PARSE$(cfgStr$(40), 6)) ' ".zmi"
                    END SELECT
                    CALL SetWindowText(hftEdit&, zTxt)            ' set corrected name to text field
                END IF
             END IF
         END SELECT

    CASE %WM_DESTROY ' Destroy what we have created to avoid nasty memory leaks

    END SELECT

END FUNCTION


...
« Last Edit: April 14, 2008, 06:35:47 AM by Patrice Terrier »
Patrice Terrier
GDImage (advanced graphic addon)
http://www.zapsolution.com

Offline Frederick J. Harris

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 1005
  • Gender: Male
    • Frederick J. Harris
Re: Fred's Tutorial #5: Windows API Tutorial: The Open File Dialog Box
« Reply #6 on: April 07, 2011, 10:28:30 PM »
Here is a version tested with PowerBASIC For Windows Version 10 (the Form5.rc file processing is the same)

Code: [Select]
#Compile       Exe
#Include       "Win32api.inc"     'Main Windows Include File
#Include       "Comdlg32.inc"     'Include file for the Common Dialog Box Library
#Resource Res, "Form5.res"        'PowerBASIC resource file
%IDM_OPEN      =    1000    'Equate for File    >> Open...
%IDM_SAVE      =    1002    '                   >> Save...
%IDM_EXIT      =    1004    '                   >> Exit
%IDM_SHOWDLG   =    2000    'Equate for Options >> Show Dialog
%IDM_HELP      =    3000    'Equate for Help    >> Help...
%IDC_FILENAME  =    4000    'Equate for Control ID of main text box on center of Form
%IDC_EXIT      =    4002    'Equate for Exit Button on Form
%IDC_NAME      =    2100    'Equate for Textbox on Dialog Box


Type WndEventArgs           'Type for passing window procedure parameters.  Allows me
  wParam As Long            'to shorten parameter list.  .NET does this all the time.
  lParam As Long            'See, for example pea for PaintEventArgs in the OnPaint()
  hWnd   As Dword           'Message Handler.
End Type


Function fnWndProc_OnCreate(wea As WndEventArgs) As Long   'Handles WM_CREATE message
  Local hCtrl As Dword

  'Text Box For Filename or Dialog Box Text
  hCtrl= _
  CreateWindowEx _
  ( _
    %WS_EX_CLIENTEDGE, _     'This gives a text box a nice 3D look
    "Edit", _                'Predefined 'edit' window class
    "", _                    'start out blank
    %WS_CHILD Or %WS_VISIBLE Or %WS_BORDER Or %ES_AUTOHSCROLL, _
    5,20,505,26, _           'edit controls have piles of styles
    Wea.hWnd, _              'parent of edit control, that is main window
    %IDC_FILENAME, _         'equate for control identifier
    GetWindowLong(wea.hWnd,%GWL_HINSTANCE), _
    Byval 0 _                'lpCreateParams -- we're not using any here
  )

  'Exit Button
  hCtrl=_
  CreateWindowEx _
  ( _
    0, _                     'Plain old button will work for me
    "Button", _              'Predefined 'edit' window class
    "Exit", _                'Let's make an exit button
    %WS_CHILD Or %WS_VISIBLE, _     'This works for simple buttons
    205,60,110,26, _                'coordinates & sizes of button
    Wea.hWnd, _                     'Parent of button
    %IDC_Exit, _                    'Equate for button (ctrl id)
    GetWindowLong(wea.hWnd,%GWL_HINSTANCE), _   'Get instance handle
    Byval 0 _                       'No Creation Data
  )

  fnWndProc_OnCreate=0              'Return 0 to Windows
End Function


Sub OnOpen(wea As WndEventArgs)     'Handles File >> Open... From WM_COMMAND
  Local szTitleName As Asciiz*(%MAX_FNAME)
  Local szFileName As Asciiz*%MAX_PATH
  Local szIniDir As Asciiz*256
  Local strFilter As String
  Local ofn As OPENFILENAME

  strFilter= _   'Dynamic strings work best here with PowerBASIC as you can embed NULLs.
  "bas files (*.bas), inc files (*.inc), rc files (*.rc)"+Chr$(0)+"*.bas;*.inc;*.rc"+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)
  szIniDir=CurDir$                                     'Initial directory
  ofn.lStructSize=SizeOf(OPENFILENAME)                 'Size of structure/type
  ofn.hWndOwner=Wea.hWnd                               'Owned by Main Program Window
  ofn.hInstance=GetWindowLong(wea.hWnd,%GWL_HINSTANCE) 'The instance handle can be gotten many ways
  ofn.lpstrFilter=StrPtr(strFilter)                    'Filters file extensions shown in dialog box
  ofn.nMaxFile=%MAX_PATH
  ofn.nMaxFileTitle=%MAX_FNAME
  ofn.lpstrInitialDir = VarPtr(szIniDir)
  ofn.lpstrDefExt=%NULL
  ofn.Flags=%OFN_HIDEREADONLY Or %OFN_CREATEPROMPT
  ofn.lpstrFile=VarPtr(szFileName)                     'This is finally the file name string you want
  ofn.lpstrFileTitle=VarPtr(szTitleName)               'This is as above, but without the full path
  ofn.lpstrTitle=%NULL
  If GetOpenFileName(ofn) Then                         'Function returns non-zero if valid file chosen
     Call SetWindowText(GetDlgItem(Wea.hWnd,%IDC_FILENAME),szFileName)
  Else
     MsgBox("You Must Have Cancelled!")
  End If
End Sub


Sub OnSave(wea As WndEventArgs)                         'Handles File >> Save... From WM_COMMAND
  MsgBox("You Clicked File >> Save...")
End Sub


Sub OnExit(wea As WndEventArgs)                         'Handles File >> Exit From WM_COMMAND
  MsgBox("Send A Message To Windows To Close The Application.")
  Call SendMessage(wea.hWnd,%WM_CLOSE,0,0)
End Sub


Function ModalDlgProc(ByVal hwndDlg As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
  Local szLocalBuffer As Asciiz*80
  Local hwndEdit As Dword

  Select Case As Long wMsg
    Case %WM_INITDIALOG
      hwndEdit=GetDlgItem(hwndDlg,%IDC_NAME)
      Call SetFocus(hwndEdit)                                 'Set focus to text box when
      Function=%FALSE                                         'dialog box opens
    Case %WM_COMMAND
      Select Case Lowrd(wParam)
        Case %IDOK
          hwndEdit=GetDlgItem(hwndDlg,%IDC_NAME)              'GetDlgItem() is a really useful function that plays
          Call GetWindowText(hwndEdit,szLocalBuffer,80)       'a big part in allowing you to eliminate globals
          Call SetWindowText _
          ( _
            GetDlgItem(GetParent(hwndDlg),%IDC_FILENAME), _   'Get parent of dialog box, then
            szLocalBuffer _                                   'control id of text box on main
          )                                                   'form into which to place text.
          Call EndDialog(hwndDlg,%TRUE)                       'EndDialog() ends the dialog box.  If %TRUE is
          Function=%TRUE                                      'returned the user hit the [ENTER] button or clicked
          Exit Function                                       'OK.  If the user cancels or x's out return %FALSE
        Case %IDCANCEL                                        'as the 2nd parameter of EndDialog().
          Call EndDialog(hwndDlg,%FALSE)
          Function=%TRUE
          Exit Function
      End Select
  End Select

  Function=%FALSE
End Function


Sub OnShowDialog(wea As WndEventArgs)
  If DialogBox(GetWindowLong(wea.hWnd,%GWL_HINSTANCE),"dlgBox",wea.hWnd,CodePtr(ModalDlgProc)) Then
     MsgBox _                                                     'DialogBox() is the function that
     ( _                                                          'causes a dialog box to be shown.
       "You Clicked The 'OK' Button Or Pressed The [ENTER] Key!" _'The Function takes four parameters.
     )                                                            'The first (retrieved here by
  Else                                                            'GetWindowLong() Is the program
     MsgBox _                                                     'instance handle.  The second Is
     ( _                                                          'the String which names the Dialog
       "You Cancelled Out!" _                                     'Box In Form5.rc. The third is the
     )                                                            'parent window handle, and the
  End If                                                          'fourth is a Pointer To the Dialog
End Sub                                                           'Box procedure.


Sub OnHelp(wea As WndEventArgs)
  MsgBox("There Is No Help!")
End Sub


Function fnWndProc_OnCommand(wea As WndEventArgs) As Long     'Handles WM_COMMAND message
  Select Case LoWrd(wea.wParam)
    Case %IDM_OPEN
      Call OnOpen(wea)
    Case %IDM_SAVE
      Call OnSave(wea)
    Case %IDM_EXIT
      Call OnExit(wea)
    Case %IDC_EXIT
      Call OnExit(wea)
    Case %IDM_SHOWDLG
      Call OnShowDialog(wea)
    Case %IDM_HELP
      Call OnHelp(wea)
  End Select

  fnWndProc_OnCommand=0
End Function


Function fnWndProc_OnClose(wea As WndEventArgs) As Long       'Handles WM_CLOSE message
  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)

  fnWndProc_OnClose=0
End Function


Function fnWndProc_OnDestroy(wea As WndEventArgs) As Long     'Handles WM_DESTROY message
  Call PostQuitMessage(0)
  fnWndProc_OnDestroy=0
End Function


Function fnWndProc(ByVal hWnd As Long,ByVal wMsg As Long,ByVal wParam As Long,ByVal lParam As Long) As Long
  Local wea As WndEventArgs

  Select Case wMsg
    Case %WM_CREATE
      wea.wParam=wParam: wea.lParam=lParam: wea.hWnd=hWnd
      fnWndProc=fnWndProc_OnCreate(wea)
      Exit Function
    Case %WM_COMMAND
      wea.wParam=wParam: wea.lParam=lParam: wea.hWnd=hWnd
      fnWndProc=fnWndProc_OnCommand(wea)
      Exit Function
    Case %WM_CLOSE
      wea.wParam=wParam: wea.lParam=lParam: wea.hWnd=hWnd
      fnWndProc=fnWndProc_OnClose(wea)
      Exit Function
    Case %WM_DESTROY
      wea.wParam=wParam: wea.lParam=lParam: wea.hWnd=hWnd
      fnWndProc=fnWndProc_OnDestroy(wea)
      Exit Function
  End Select

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


Function WinMain(ByVal hIns As Long, ByVal hPrev As Long, ByVal lpCmdLn As Asciiz Ptr, ByVal iShow As Long) As Long
  Local winclass As WndClassEx
  Local szAppName As Asciiz*8
  Local hMainWnd As Dword
  Local Msg As tagMsg

  szAppName="Form5"                                       : winclass.cbSize=SizeOf(winclass)
  winclass.lpszClassName=VarPtr(szAppName)                : winclass.lpfnWndProc=CodePtr(fnWndProc)
  winclass.style=%CS_HREDRAW Or %CS_VREDRAW               : winclass.hInstance=hIns
  winclass.cbClsExtra=0                                   : winclass.cbWndExtra=0
  winclass.hIcon=LoadIcon(%NULL, ByVal %IDI_APPLICATION)  : winclass.hCursor=LoadCursor(%NULL, ByVal %IDC_ARROW)
  winclass.hbrBackground=%COLOR_BTNFACE+1                 : winclass.lpszMenuName=VarPtr(szAppName)
  Call RegisterClassEx(winclass)
  hMainWnd=CreateWindowEx(0,szAppName,"Form5",%WS_OVERLAPPEDWINDOW,350,300,525,155,0,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