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