home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
BUG 4
/
BUGCD1997_05.BIN
/
aplic
/
clip4win
/
clip4win.exe
/
C4W30E.HUF
/
C4WV1
< prev
next >
Wrap
Text File
|
1994-03-21
|
20KB
|
514 lines
-------------------------------
Now's the Time to Get EVENTful!
-------------------------------
If you believe that Windows is here to stay (for a few years anyway),
and you would prefer to be on rather than off the bandwagon, then read
on! CA must be convinced that Windows is serious stuff to have invested
time and resources in CA-Visual Objects for Clipper. VO was
demonstrated at the PC Development Day at Olympia in March (yes, VO
IS coming) but what an alien beastie it appears to be. Yet another
massive learning curve, full of objects, entities, classes and events
waiting to be tackled. Feeling exhausted already? Then take a tip
and start NOW.
I'm assuming that you have by now got to grips with Clipper 5 and that
code blocks, browse and get objects no longer terrify. With thanks to
various Clipper third-party developers, you can already start building
your own object classes (SuperClass, Class(Y), StarClass). So why not
go one step further and become event driven too?
Clip-4-Win
----------
This add-on library, written by John Skelton (Skelton Software), opens
up the challenge of developing genuine Windows applications within
the comfortable framework of good old Clipper. However, as there is a
huge amount of Windows terminology to get acquainted with, a comprehensive
Windows reference book is a must alongside the Clip-4-Win manual.
Two recommendations (they make great Christmas or birthday presents)
are Charles Petzold's Programming Windows 3.1 and James Conger's
Windows API Bible. The thickness of each alone should convince you
that this is no lighthearted challenge.
When installing the Clip-4-Win library and an array of .CH files, check
that all your products are compatible. You are recommended to use
CA-Clipper 5.2c with a linker that supports Windows executables (Blinker
3.00 or Microsoft's MS LINK 5.30 or later. Flexfile addicts are advised
to move to version 2 and upgrade BroPlus at the same time. If you
haven't already switched to Flexfile's RDD, now is your opportunity - it
is so much easier to use!
Getting Started
---------------
For a quick introduction to convince yourself that Windows is a
great environment to work in, try a modal dialog box with an
edit box and some buttons to complete the picture. The Clip-4-Win manual
contains an example under AppendDialog() and CreateDialog(), and a
sample source program LISTBOX.PRG is included in the package.
However, this is not what Windows is all about and your end-users
will soon rebel at being straightjacketed inside a single window.
A different approach has to be taken when designing Windows applications.
The conventional way of controlling the end-user and mapping out
clearly defined pathways to be followed must be forgotten. The
programmer instead responds to the whims of the end-user who might be
flipping between several windows, one of which is running your precious
application. Windows will keep you informed of all this activity by
issuing 'messages' or 'events'. It is up to you to decide which ones
to handle and which to ignore.
Handling Events
---------------
Windows sends each event to an application as a message. Clip-4-Win
handles some automatically, the rest are put in a queue for your
application to handle if required. Each of these message is made up
of four items of data which can be interrogated using Clip-4-Win
functions :
DataItem Data Type Meaning C4W Function
-------------------------------------------------------------------
hWnd window handle the window the event _LasthWnd()
relates to
msg 16-bit number message number _LastMsg()
(defined in WINDOWS.CH)
wParam 16-bits of data additional data _LastwParam()
lParam 32-bits of data additional data _LastlParam()
The high and low 16-bit values of lParam are available from the functions
_LastHilParam() and _LastLolParam().
A well written Windows application will have a single event checking
loop which passes the event to the appropriate code block for handling.
Three Clip-4-Win functions enable you to maintain an array of information
for each separate window created, determine which window the event
relates to and execute the appropriate code block(s). These are
ChkEvent(), HandleEvent(nEvent) and AddHandler(hWnd,bAction).
#define WIN_WANT_ALL
#include "windows.ch"
STATIC hWnd
STATIC cAppName := "Clip4Win"
STATIC nEvent
***************
FUNCTION main()
***************
LOCAL hMenu
LOCAL hCurrWnd
hWnd := WinSetUp(cAppName,"TITLE",0,20,640,460)
hMenu := MenuSetup() // define menu structure
DO WHILE TRUE
DO WHILE (nEvent := ChkEvent()) == EVENT_NONE
// some background processing could go here
ENDDO
HandleEvent(nEvent) // the ONLY time it is used
DO CASE
CASE nEvent == EVENT_CLOSE
hCurrWnd := _lastHWnd()
IF hWnd == hCurrWnd
DoExit() // closedown routines
ENDIF
ENDCASE
ENDDO
RETURN 0
Typically, whenever a menu option is selected, a procedure is run
which creates a new window (plus child windows as required) and adds
to the window handle array :
AddHandler(hWnd1,bAction)
Whenever an event occurs within that window (hWnd1), HandleEvent() will
execute the associated code block (bAction) which is usually a large
DO CASE statement for all the relevant events. You determine which
events you include in the DO CASE.
An example for a Window handling an expenses claim - a subject dear
to my heart - is:
#define CONTROL_ID _LastwParam()
DO CASE
CASE nEvent == EVENT_CONTROL
// message received from a child control
DO CASE
CASE CONTROL_ID == VAT_BUTTON
// User has clicked the VAT calculation button
nVat := nGross*(control->vat/(1+control->vat))
cVat := str(nVat,8,2)
// Update VAT display box with result
SendMessage(VAT_BOX,.....,len(cVat),cVat)
CASE .....
ENDCASE
CASE nEvent == EVENT_REDRAW
// Redraw window
CASE nEvent == EVENT_QUIT
// Exit window & destroy
DoExit()
ENDCASE
Those three lines of code are all you need to do when the VAT button
is actioned.
Note that the pre-processor is used extensively to make coding easier
to read and maintain. Clip-4-Win's WINDOWS.CH contains hundreds of
#defines mapping to Windows messages. If you have not used the
pre-processor before it would be as well to become very familiar with it.
Typical events which you may wish to handle in your code are:
* Mouse clicks
* Getting and losing focus (if you are familiar with the Get
system this will present no problems)
* User moving and resizing the window (how dare they!)
* User hitting an accelerator key
* Windows telling you to redraw your window
(Windows gets pretty bossy).
Event Handling Summary
----------------------
Within an application I have a different .prg for each main menu
option with all its own global STATICS. Each .prg consists of two
main procedures or functions (use a function when there is something
to return, otherwise a procedure). The first function creates the
main window with all its child windows and adds a code block for the
window to the event handling array. The second contains a DO CASE
structure for each appropriate event. Each event usually calls a
STATIC FUNCTION with all the necessary coding to handle it.
Remembering that the end-user may be switching between multiple windows
both inside and outside your own application, be extremely careful when
handling files. Do not rely on a file already being correctly
positioned. Either reposition or write a function to restore the
whole environment as necessary.
Debugging with Clip-4-Win
-------------------------
1. Identifying Events
----------------------
Often, you will have mapped out the series of events which you wish
to handle for a particular Window but things will not go according
to plan. Unfortunately the debugger does not work under Windows (yet?)
so you will have establish for yourself what is happening. I have
added a hook into John Skelton's ChkEvent() code which is supplied with
Clip-4-Win for this sort of purpose. My code reads :
DO WHILE TRUE
DO WHILE (nEvent := ChkEvent()) == EVENT_NONE
ENDDO
#IFDEF LOG
LogAdd(nEvent) // Ensure time is stored
#ENDIF
HandleEvent(nEvent)
ENDDO
I have similar LogAdd() calls in my event handlers with a string
identifying where the message was handled. This shows me where
Windows/Clip-4-Win is sending each message for processing. The
LogAdd() routine merely stores the time and the contents of the
message into a .DBF which I can inspect later with DBU or BROPLUS.
************************************************
STATIC FUNCTION SetUpLog() // Set Up the Log *
************************************************
IF !file('Eventlog.dbf')
dbcreate('eventlog',;
{ {'time' , 'C', 8, 0},;
{'event' , 'N', 5, 0},;
{'hwnd' , 'N', 5, 0},;
{'msg' , 'N', 5, 0},;
{'wpara' , 'N', 5, 0},;
{'lpara' , 'N', 10, 0},;
{'lolpa' , 'N', 5, 0},;
{'hilpa' , 'N', 5, 0}})
ENDIF
USE eventlog NEW EXCLUSIVE
ZAP
RETURN NIL
*********************************************
FUNCTION LogAdd(ev,cLog) // Add to the log *
*********************************************
IF ev != EVENT_OTHER
eventlog->(dbappend())
IF empty(cLog)
eventlog->time := time() // if called without message
ELSE
eventlog->time := cLog // store identifier
ENDIF
eventlog->event := ev
eventlog->hwnd := _LasthWnd()
eventlog->msg := _LastMsg()
eventlog->wpara := _LastwParam()
eventlog->lpara := _LastlParam()
eventlog->lolpa := _LastlolParam()
eventlog->hilpa := _LasthilParam()
eventlog->(dbcommit())
ENDIF
RETURN NIL
2. Interrogating Data
-----------------------
Occasionally it is necessary to inspect the contents of series of
variables or fields. I have developed two functions which can be
inserted into the code as needed to open a window and display the
required information.
The code snippet included is an example of a modal dialog box.
Although not to be used too often, a modal box is ideal for messages,
error messages and debug displays. Please feel free to use it if you
wish. Note that the function FitText() is used to position text ready
for display. This enables me to use the sexier Windows proportional
fonts while still getting my output to look columnar. This involves
the use of device contexts. If you are still interested I shall cover
these next time.
***********************************************
// FUNCS.PRG
***********************************************
#define WIN_WANT_LB // Control the behaviour of pre-processor
#define WIN_WANT_LBS
#define WIN_WANT_ALL
#include "windows.ch" // Supplied with Clip-4Win
#include "pam.ch" // My header
#include "pamwin.ch" // My header
#xtranslate GETPIXELWIDTH(<h>,<c>) => C4W_LoWord(GetTextExtent(<h>,<c>))
static hWindow
******************************************************
FUNCTION DispData(hW,aR,cT) // Display data elements *
******************************************************
local aList := {}
local cTitle := 'P.A.M. Data Display' + IF(!empty(cT),' - ' + cT,'')
local aDlg
local hInst := _GetInstance()
local nDlgRes
local cSelected
local nP,i
#define BOX_RIGHT 315
#define BOX_BOTTOM 225
hWindow := hW
aeval(aR,{|r| aadd(aList,FitText(hWindow,r[1],decode(r[2]),100,'L'))})
aDlg := CreateDialog(cTitle,;
WS_CAPTION + WS_SYSMENU + WS_GROUP + WS_TABSTOP +;
WS_THICKFRAME + WS_VISIBLE + WS_POPUP,;
0,0,BOX_RIGHT,BOX_BOTTOM)
aDlg := AppendDialog(aDLG,'list',DLG_LISTBOX,;
WS_TABSTOP + WS_CHILD + WS_VISIBLE +;
WS_VSCROLL + WS_BORDER,;
0,0,BOX_RIGHT,BOX_BOTTOM - 50,;
aList) // The list
aDlg := AppendDialog(aDlg,'ok',DLG_BUTTON,;
BS_DEFPUSHBUTTON + WS_TABSTOP +;
WS_CHILD + WS_VISIBLE,;
BOX_RIGHT/2 - 25, BOX_BOTTOM - 30,;
50,25,'&OK')
aDlg := AppendDialog(aDlg,'help',DLG_STATIC,;
SS_LEFT + WS_CHILD + WS_VISIBLE,;
BOX_RIGHT/2 + 35, BOX_BOTTOM - 30,;
100,25,'OK with an array selected')
aDlg := AppendDialog(aDlg,'help2',DLG_STATIC,;
SS_LEFT + WS_CHILD + WS_VISIBLE,;
BOX_RIGHT/2 + 35, BOX_BOTTOM - 20,;
100,25,'will display the array')
DO WHILE TRUE
nDlgRes := ModalDialog(aDlg,hInst,hW)
DO CASE
CASE nDlgRes <= 0
EXIT
CASE nDlgRes == 2 // OK
cSelected := GetDialogResult(aDlg,'list')
IF !empty(cSelected)
nP := at(' ',cSelected)
cSelected := left(cSelected,nP-1)
IF !empty(i := ascan(aR,{|r| r[1] == cSelected}))
IF valtype(aR[i][2]) == 'A'
DispArray(hW,aR[i][2],aR[i][1])
ENDIF
ENDIF
ELSE
EXIT
ENDIF
ENDCASE
ENDDO
RETURN 0
************************************************
FUNCTION DispArray(hW,aR,cT) // Display array *
************************************************
local aList := {}
local cTitle := 'P.A.M. Array Browser' + IF(!empty(cT),' - ' + cT,'')
local aDlg
local hInst := _GetInstance()
hWindow := hW
aList(aR,aList) // Process the array into a list
aDlg := CreateDialog(cTitle,;
WS_CAPTION + WS_SYSMENU + WS_GROUP + WS_TABSTOP +;
WS_THICKFRAME + WS_VISIBLE + WS_POPUP,;
0,0,BOX_RIGHT,BOX_BOTTOM)
aDlg := AppendDialog(aDLG,'list',DLG_LISTBOX,;
WS_TABSTOP + WS_CHILD + WS_VISIBLE +;
WS_VSCROLL + WS_BORDER,;
0,0,BOX_RIGHT,BOX_BOTTOM,;
aList) // The list
ModalDialog(aDlg,hInst,hW)
RETURN 0
******************************************************************
STATIC FUNCTION aList(aR,aList,cLine) // Process aR into aList *
******************************************************************
local i
local cType
local cThisLine
DEFAULT cLine to ''
#define ELEMENT '[' + alltrim(str(i)) + ']'
FOR i = 1 to len(aR)
cType := valtype(aR[i])
DO CASE
CASE valtype(aR[i]) == 'A'
aList(aR[i],aList,cLine + ELEMENT)
OTHERWISE
cThisLine := cLine + ELEMENT + ' ' + decode(aR[i])
aadd(aList,cThisLine)
ENDCASE
NEXT
RETURN 0
************************************************
static function decode(x) // Decode the data *
************************************************
local cT := valtype(x)
local cRet := FitText(hWindow,cT,':',20,'L')
local i
local aBlocks := { {'A',{|d| 'Array of length ' + alltrim(str(len(d)))}},;
{'B',{|d| '{|| .... }'}},;
{'C',{|d| '"' + d + '"'}},;
{'D',{|d| dtoc(d)}},;
{'L',{|d| if(d,'.T.','.F.')}},;
{'N',{|d| alltrim(str(d)) }},;
{'U',{|d| 'NIL'} } }
IF !empty( i := ascan(aBlocks,{|b| b[1] == cT}))
cRet := FitText(hWindow,cRet,eval(aBlocks[i][2],x),40,'L')
ELSE
cRet := FitText(hWindow,cRet,'***',40,'L')
ENDIF
RETURN cRet
*******************************************************
FUNCTION FitText(hW,cT1,cT2,nP,cF,hDC) // Fit text *
*******************************************************
local lCreateDC := FALSE
local n1, n2, nSp
local cRes := ''
local cPre, cPost, nDec
IF empty(hDC) // Establish device context
lCreateDC := TRUE // only if not passed
hDC := GetDC(hW)
ENDIF
n1 := GETPIXELWIDTH(hDC,cT1)
n2 := GETPIXELWIDTH(hDC,cT2)
nSp := GETPIXELWIDTH(hDC,' ') // Pix width of a space
DO CASE
CASE cF == 'L' // aaaaaaa |bbbbbb
DO WHILE (n1 := GETPIXELWIDTH(hDC,cT1)) > nP
cT1 := left(cT1,len(cT1)-1)
ENDDO
cRes := cT1 + IF(n1<nP,space(int((nP-n1)/nSp)),'') + cT2
CASE cF == 'R' // aaaaaaaa bbbbbbbb|
DO WHILE (n1 := GETPIXELWIDTH(hDC,cT1)) + ;
(n2 := GETPIXELWIDTH(hDC,cT2)) > nP
IF !empty(cT1)
cT1 := left(cT1,len(cT1)-1)
ENDIF
IF !empty(cT2)
cT2 := right(cT2,len(cT2)-1)
ENDIF
ENDDO
cRes := cT1 + space(int((nP-(n1+n2))/nSp)) + cT2
// |
CASE cF == 'N' // aaaaaaaaa 123.45
cT2 := alltrim(cT2) // remove odd blanks
nDec := at('.',cT2)
cPre := left(cT2,nDec-1)
cPost := substr(cT2,nDec)
cRes := FitText(hW,cT1,cPre,nP,'R',hDC) // Watch the recursion!
cRes := FitText(hW,cRes,cPost,nP,'L',hDC)
ENDCASE
IF lCreateDC // Only release the DC if I created it
ReleaseDC(hW,hDC)
ENDIF
RETURN cRes
APPENDIX
========
Clip-4-Win : written by John Skelton of Skelton Software
(Kendal Cottage, Hillam, Leeds LS25 5HP).
Available through QBS.
Notes on PMP
------------
Pam Pitcher has been running her own company P.A.M. Ltd for the past
14 years, designing and writing software predominantly for PCs.
She is a Clipper addict and is looking forward(?) to tackling CA_VO. Her
husband Mike, also part of P.A.M., specialises in IT consultancy
at all levels for all sizes of corporations.
P.A.M. Ltd
Sandycot, Cadsden Road
Princes Risborough, Bucks, HP27 0NB
0844 346687 (CompuServe : 100020,3157)