API Example 2




4) Dialog "Console" - using Dialog Editor 

Our "console" example in (3) suffers from one severe limitations. That is, not all messages are "posted", some are "send" directly to the window procedure. For example, one of these are the WM_DESTROY message. Normally we terminate (PostQuitMessage) as part of this message. But, in our example in (3) we had to use the WM_RBUTTONUP message, as WM_DESTROY is not "posted". 

Thus, let's rewrite our "console" application, but use a Dialog with a Dialog Procedure. We will use the DialogBox function to create our modal dialog. A modal dialog box stops execution of the WinMain till the function returns. The function returns when the dialog is closed (EndDialog). The modal dialog manager also include it's own message loop, so we do not need to define our own. You can also use the CreateDialog function (modeless) with your own message loop. 

The Dialog window layout is created with a Dialog Editor and stored in the resource file. This is by far the easiest, but if you don't like it, you can create the dialog in code (which is done in the next example).

If you look at the screenshot of our new "console" application below, you will see that it looks quite nice, with a system menu and system close box. The only "trick" in this example, is the method to intercept the Enter key in a textbox in a dialog. It is done by having an invisible default button which will receive the Enter key through a WM_COMAND message.

The code to create this window is shown below.

a) Firstly the Winmain function (only to display the dialog box, which serves as main window):

integer function WinMain( hInstance, hPrevInstance, lpszCmdLine, nCmdShow )
!MS$ ATTRIBUTES STDCALL, ALIAS : '_WinMain@16' :: WinMain

 !win32 definitions
 use msfwin
 include "resource.fd"

 !Function parameters
 integer(4), intent(in) :: hInstance,hPrevInstance,nCmdShow,lpszCmdLine
 integer(4) :: iret

 !Interface to dialog procedure
 interface
  integer(4) function DlgProc ( hWnd, iMsg, wParam, lParam )
  !MS$ ATTRIBUTES STDCALL, ALIAS : '_DlgProc@16' :: DlgProc
   integer(4) :: hWnd, iMsg, wParam, lParam
  end function 
 end interface

 !Create dialog
 iret = DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG), 0, loc(DlgProc))

 ! Exit program
 WinMain = 0

end

b) Secondly the Dialog box procedure (where everything now happens):

integer(4) function DlgProc ( hWnd, iMsg, wParam, lParam )
!MS$ ATTRIBUTES STDCALL, ALIAS : '_DlgProc@16' :: DlgProc

 !win32 definitions
 use msfwin
 include "resource.fd"

 !Declarations
 integer(4), intent(in) :: hWnd, iMsg, wParam, lParam
 integer(4) :: iret, iErr=0
 real(8)    :: nRead 
 character (len=*), parameter :: nl=char(13)//char(10)
 character (len=1024) :: szText="", szRead=""

 !Big Switch
 select case(iMsg)

 case (WM_INITDIALOG)
  !Set inital text
  iret = SetDlgItemText(hWnd, IDC_EDIT, &
   "Exit from System menu (or type exit)"//nl//nl// &
   "Enter number : "//nl//char(0))
  DlgProc = 1 
  return

 case (WM_CLOSE)
  !End Modal dialog when Close from system menu
  iret = EndDialog(hWnd,1)
  DlgProc = 1 
  return

 case (WM_COMMAND)
  select case (LoWord(wParam))
  case (IDOK)
   !Def button required, but not shown.
   !Read last line
   iret = SendDlgItemMessage(hWnd, IDC_EDIT, EM_GETLINECOUNT, 0, 0)
   iret = SendDlgItemMessage(hWnd, IDC_EDIT, EM_GETLINE, iret-1, loc(szText))
   szText(iret+1:len(szText)) = ""
   !First see if "exit" or "end"
   if (trim(szText) == "exit" .or. trim(szText) == "end") then
    iret = EndDialog(hWnd,1)
    DlgProc = 1 
    return
   end if
   !Internal read into Double
   read(szText, *, IOSTAT=iErr) nRead
   if (iErr /= 0) then
    iret = MessageBox(NULL, "Error reading input."//char(0), "KFWIN"//char(0), MB_OK) 
    szRead = ""
   else
    !Now do calcs on input.
    nRead = (nRead + 1.0)**2
    !Convert result to string fro display
    write (szRead,*) nRead; szRead = adjustl(szRead)
   end if
   !Display results at end of existing text and ask for next input
   iret = GetDlgItemText(hWnd, IDC_EDIT, szText, len(szText))
   szText(index(szText,char(0)):len(szText)) = ""
   iret = SetDlgItemText(hWnd, IDC_EDIT, &
     trim(szText)//nl// &
     "Result of (x+1)^2 = " // &
     trim(szRead)//nl// &
     "Enter next number below : "//nl//char(0))
   !Reset focus in case of msgbox, set caret, and scroll to it 
   iret = setfocus(GetDlgItem(hWnd, IDC_EDIT))
   iret = SendDlgItemMessage(hWnd, IDC_EDIT, WM_GETTEXTLENGTH, 0, 0)
   iret = SendDlgItemMessage(hWnd, IDC_EDIT, EM_SETSEL, iret, iret)
   iret = SendDlgItemMessage(hWnd, IDC_EDIT, EM_SCROLLCARET, 0, 0)

   DlgProc = 1 
   return

  end select

 end select

 !Return false if not handled
 DlgProc = 0

end function 

c) And lastly the resource file and constants:

File "resource.fd"
! Used by fortran
!
      integer, parameter :: IDD_DIALOG                      = 102
      integer, parameter :: IDC_EDIT                        = 1000

File "resource.res"
//
// Dialog
//
#define IDD_DIALOG                      102
#define IDC_EDIT                        1000

#include "afxres.h"

IDD_DIALOG DIALOG DISCARDABLE  0, 0, 220, 175
STYLE DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "KFWIN Console"
FONT 8, "MS Sans Serif"
BEGIN
    EDITTEXT        IDC_EDIT,7,7,206,161,ES_MULTILINE | ES_AUTOVSCROLL | 
                    ES_NOHIDESEL | WS_VSCROLL
    DEFPUSHBUTTON   "OK",IDOK,72,155,61,13,NOT WS_VISIBLE
END
 

5) Dialog "Console" - Created in code only 

If it is not convenient to use a dialog editor, the programmer can create everything in code. There are (at least) three approaches open to him:

(i) Create a typical winmain function (with own class and message loop). Then create all the controls you want on that main window in the WM_CREATE message of the window function, using the CreateWindow function.
(ii) Create a dialog box and it's controls in the winmain function and displays it. In this case you don't need your own class or message loop.
(iii) A combonation of (i) and (ii). Create a dialog box in the winmain function and displays it. Then create all the controls you want on that dialog window in the WM_CREATE message of the window function, using the CreateWindow function. In this case you don't need your own class or message loop.

Method (i) is fairly easy and plenty of examples are available. These examples are mostly in C, but fortran examples are available with the Fortran compilers. KFWIN also shows this approach. Method (iii) is easy once method (i) and (iii) is understood.

Thus, we will show approach (ii) here, and recreate example (3) without using a dialog editor.

The code to achieve this is shown below. Only the winmain and supporting function are shown, as the DlgProc function is exactly the same.

integer function WinMain( hInstance, hPrevInstance, lpszCmdLine, nCmdShow )
!MS$ ATTRIBUTES STDCALL, ALIAS : '_WinMain@16' :: WinMain

!win32 definitions
 use msfwin
 include "resource.fd"

 !Interfaces
 interface
  integer(4) function DlgProc ( hWnd, iMsg, wParam, lParam )
  !MS$ ATTRIBUTES STDCALL, ALIAS : '_DlgProc@16' :: DlgProc
   integer(4) :: hWnd, iMsg, wParam, lParam
  end function 
  integer(4) function SetDlg (iPtr, strClass, strCaption, iStyle, X, Y, Width, Height, iCtrl)
  !Function returns pointer (DLG if strClass == "", else control)
   character(len=*) :: strClass, strCaption
   integer(4), intent(in) :: iPtr, iStyle, X, Y, Width, Height, iCtrl
  end function SetDlg
 end interface

 !Function parameters
 integer(4), intent(in) :: hInstance,hPrevInstance,nCmdShow,lpszCmdLine
 integer(4) :: iret, hgbl, lpdt, lpdti
 logical(4) :: bret 

 !Reserve memory for DLG
 hgbl = GlobalAlloc(GMEM_ZEROINIT, 1024)
 lpdt = GlobalLock(hgbl); lpdti = lpdt !Store for later 

 !0) Setup dialog (Class="", iCtrl=Number of controls on dialog)
 lpdti = SetDlg (lpdti, "", "Title : Dialog in code", &
    ior(WS_POPUP, ior(WS_BORDER, ior(WS_SYSMENU, WS_CAPTION))), &
    10, 10, 220, 175, 2)

 !1) Setup controls (iCtrl=Control ID)
 lpdti = SetDlg (lpdti, "button", "OK", &
    ior(WS_CHILD, BS_DEFPUSHBUTTON), &
    72, 155, 61, 13, IDOK)

 !2) Setup controls (iCtrl=Control ID)
 lpdti = SetDlg (lpdti, "edit", "Edit 2 is longer, with line breaks and the rest.", &
    ior(WS_VSCROLL, ior(ES_MULTILINE, ior(WS_BORDER, ior(WS_CHILD, WS_VISIBLE)))), &
    7, 7, 206, 161, IDC_EDIT)

 !Create dialog 
 iret = DialogBoxIndirect (hInstance, lpdt, 0, loc(DlgProc), 0)
 iret = MessageBox(NULL, "Ended OK"//char(0), "KFWIN Window"//char(0), MB_OK) 

 !Release memory for DLG
 bret = GlobalUnlock(hgbl)
 iret = GlobalFree(hgbl)

 ! Exit program
 WinMain = 0

end function
 

integer(4) function SetDlg (iPtr, strClass, strCaption, iStyle, X, Y, Width, Height, iCtrl)
 !Function returns pointer (DLG if strClass == "", else control)

 !win32 definitions
 use msfwin
 implicit none

 !Interfaces
 interface
  integer(4) function MultiByteToWideChar (CodePage,dwFlags,lpMultiByteStr,cbMultiByteStr,lpWideCharStr,cchWideChar) 
  !MS$ATTRIBUTES STDCALL, ALIAS : '_MultiByteToWideChar@24' :: MultiByteToWideChar
  !MS$ATTRIBUTES REFERENCE :: lpMultiByteStr
   character*(*) lpMultiByteStr
   integer(4) :: CodePage,dwFlags,cbMultiByteStr,lpWideCharStr,cchWideChar
  end function MultiByteToWideChar
 end interface

 character(len=*) :: strClass, strCaption
 integer(4), intent(in) :: iPtr, iStyle
 integer(4), intent(in) :: X, Y, Width, Height, iCtrl  !Only 2 bytes used.
 integer(4) :: lpdit, NoChar

 call CopyMemory(iPtr, loc(iStyle),4) ;lpdit = iPtr+8
 if (strClass == "") then 
  call CopyMemory(lpdit, loc(iCtrl),2);lpdit = lpdit+2
 end if
 call CopyMemory(lpdit, loc(X),2)  ;lpdit = lpdit+2
 call CopyMemory(lpdit, loc(Y),2)  ;lpdit = lpdit+2
 call CopyMemory(lpdit, loc(Width),2) ;lpdit = lpdit+2
 call CopyMemory(lpdit, loc(Height),2) ;lpdit = lpdit+2
 if (strClass == "") then
    call CopyMemory(lpdit, loc(0),2) ;lpdit = lpdit+2 !Menu and class for DLG
    call CopyMemory(lpdit, loc(0),2) ;lpdit = lpdit+2
    NoChar = MultiByteToWideChar (0, 0, strCaption//char(0), -1, lpdit, len(strCaption)+1) 
    lpdit = lpdit+2*NoChar
 else
    call CopyMemory(lpdit, loc(iCtrl),2) ;lpdit = lpdit+2 !CtrlID - 255 max in Win9X
    NoChar = MultiByteToWideChar (0, 0, strClass//char(0), -1, lpdit, len(strClass)+1) 
    lpdit = lpdit+2*NoChar
    NoChar = MultiByteToWideChar (0, 0, strCaption//char(0), -1, lpdit, len(strCaption)+1)
    lpdit = lpdit+2*NoChar
    call CopyMemory(lpdit, loc(0),2);lpdit = lpdit+2 !Creation data
 end if

 !Allign to DWORD per MS (add 3 and remove 2 right bits)
 lpdit = lpdit + 3
 call mvbits(0, 0, 2, lpdit, 0) ! returns = 000XXX00

 !Return pointer for next store
  SetDlg = lpdit
end function

From the code it is (hopefully) clear that the winmain function is only used to set up, create and display the dialog and the controls on the dialog. Which is done by calling our own SetDlg function. A couple of comments on the code:

(i) Normally C-style pointers are used to set up the dialog. In SetDlg it was simulated with the CopyMemory function.
(ii) All strings must be Unicode. The MultiByteToWideChar function is used for that.
(iii) Each control must be alligned on a DWORD boundary in memory.
(iv) Powersation 4 did not have a declaration for the DialogBoxIndirect function, so we defined our own as follows,

  integer(4) function DialogBoxIndirect(hInstance,hDialogTemplate,hWndParent,lpDialogFunc,dwInitParam) 
  !MS$ATTRIBUTES STDCALL, ALIAS : '_DialogBoxIndirectParamA@20' :: DialogBoxIndirect
   integer(4) :: hInstance, hDialogTemplate, hWndParent, lpDialogFunc,dwInitParam
  end function DialogBoxIndirect
 

6) Window/Dialog Procedures 
If you write small Windows programs, the techniques shown above are very usefull. But for larger application, you will typically:

(i) Define your own window class and give it it's own procedure (callback function).
(ii) Have a procedure for the main window and for each dialog.

In the "typical" windows application, the Winmain function is only used to register and create the main window and enter the message loop. No messages are handled here. Instead, they are all Dispatched to and handled in the window procedure/function. The window function usually contain a big Select Case construct (the even more infamous Big Switch) to see what message was send to the window.

Most Fortran compiler vendors provide examples of typically Winmain and callback functions. Also, please have a look at the KFWIN functions to see how they can simplify this process.
 

 
   
KFWIN © was developed and is maintained and distributed by KORF Software.
Click image (if scripting enabled) to email us any technical or commercial questions.

This product is protected by copyright law and international treaties.