home *** CD-ROM | disk | FTP | other *** search
/ BUG 4 / BUGCD1997_05.BIN / aplic / clip4win / clip4win.exe / C4W30E.HUF / C4WV1 < prev    next >
Text File  |  1994-03-21  |  20KB  |  514 lines

  1. -------------------------------
  2. Now's the Time to Get EVENTful!
  3. -------------------------------
  4.  
  5. If you believe that Windows is here to stay (for a few years anyway),
  6. and you would prefer to be on rather than off the bandwagon, then read
  7. on!  CA must be convinced that Windows is serious stuff to have invested
  8. time and resources in CA-Visual Objects for Clipper.  VO was
  9. demonstrated at the PC Development Day at Olympia in March (yes, VO
  10. IS coming) but what an alien beastie it appears to be.  Yet another 
  11. massive learning curve, full of objects, entities, classes and events 
  12. waiting to be tackled.  Feeling exhausted already?  Then take a tip 
  13. and start NOW.
  14.  
  15. I'm assuming that you have by now got to grips with Clipper 5 and that
  16. code blocks, browse and get objects no longer terrify.  With thanks to
  17. various Clipper third-party developers, you can already start building
  18. your own object classes (SuperClass, Class(Y), StarClass).  So why not
  19. go one step further and become event driven too?
  20.  
  21. Clip-4-Win
  22. ----------
  23.  
  24. This add-on library, written by John Skelton (Skelton Software), opens 
  25. up the challenge of developing genuine Windows applications within 
  26. the comfortable framework of good old Clipper.  However, as there is a 
  27. huge amount of Windows terminology to get acquainted with, a comprehensive
  28. Windows reference book is a must alongside the Clip-4-Win manual.
  29. Two recommendations (they make great Christmas or birthday presents)
  30. are Charles Petzold's Programming Windows 3.1 and James Conger's
  31. Windows API Bible.  The thickness of each alone should convince you
  32. that this is no lighthearted challenge.
  33.  
  34. When installing the Clip-4-Win library and an array of .CH files, check
  35. that all your products are compatible.  You are recommended to use
  36. CA-Clipper 5.2c with a linker that supports Windows executables (Blinker
  37. 3.00 or Microsoft's MS LINK 5.30 or later.   Flexfile addicts are advised 
  38. to move to version 2 and upgrade BroPlus at the same time.  If you 
  39. haven't already switched to Flexfile's RDD, now is your opportunity - it 
  40. is so much easier to use!
  41.  
  42. Getting Started
  43. ---------------
  44.  
  45. For a quick introduction to convince yourself that Windows is a
  46. great environment to work in, try a modal dialog box with an
  47. edit box and some buttons to complete the picture.  The Clip-4-Win manual
  48. contains an example under AppendDialog() and CreateDialog(), and a 
  49. sample source program LISTBOX.PRG is included in the package.
  50.  
  51. However, this is not what Windows is all about and your end-users
  52. will soon rebel at being straightjacketed inside a single window.
  53.  
  54. A different approach has to be taken when designing Windows applications.
  55. The conventional way of controlling the end-user and mapping out
  56. clearly defined pathways to be followed must be forgotten.  The
  57. programmer instead responds to the whims of the end-user who might be
  58. flipping between several windows, one of which is running your precious
  59. application.  Windows will keep you informed of all this activity by
  60. issuing 'messages' or 'events'.  It is up to you to decide which ones
  61. to handle and which to ignore.
  62.  
  63. Handling Events
  64. ---------------
  65.  
  66. Windows sends each event to an application as a message.  Clip-4-Win
  67. handles some automatically, the rest are put in a queue for your
  68. application to handle if required.  Each of these message is made up 
  69. of four items of data which can be interrogated using Clip-4-Win 
  70. functions :
  71.  
  72.      DataItem  Data Type          Meaning                   C4W Function
  73.      -------------------------------------------------------------------     
  74.      hWnd      window handle      the window the event      _LasthWnd()
  75.                                   relates to
  76.      msg       16-bit number      message number            _LastMsg()
  77.                                   (defined in WINDOWS.CH)     
  78.      wParam    16-bits of data    additional data           _LastwParam()
  79.      lParam    32-bits of data    additional data           _LastlParam()
  80.  
  81. The high and low 16-bit values of lParam are available from the functions
  82. _LastHilParam() and _LastLolParam().
  83.  
  84. A well written Windows application will have a single event checking 
  85. loop which passes the event to the appropriate code block for handling.
  86. Three Clip-4-Win functions enable you to maintain an array of information 
  87. for each separate window created, determine which window the event 
  88. relates to and execute the appropriate code block(s).  These are 
  89. ChkEvent(), HandleEvent(nEvent) and AddHandler(hWnd,bAction).
  90.  
  91.           #define WIN_WANT_ALL
  92.           #include "windows.ch"
  93.           STATIC hWnd
  94.           STATIC cAppName := "Clip4Win"
  95.           STATIC nEvent
  96.  
  97.           ***************
  98.           FUNCTION main()
  99.           ***************
  100.           LOCAL hMenu
  101.           LOCAL hCurrWnd
  102.  
  103.           hWnd   := WinSetUp(cAppName,"TITLE",0,20,640,460)
  104.           hMenu  := MenuSetup()     // define menu structure
  105.  
  106.           DO WHILE TRUE
  107.              DO WHILE (nEvent := ChkEvent()) == EVENT_NONE
  108.                 // some background processing could go here             
  109.              ENDDO
  110.  
  111.              HandleEvent(nEvent)    // the ONLY time it is used
  112.  
  113.              DO CASE
  114.                 CASE nEvent == EVENT_CLOSE
  115.                    hCurrWnd := _lastHWnd()
  116.  
  117.                    IF hWnd == hCurrWnd
  118.                       DoExit()     // closedown routines
  119.                    ENDIF
  120.              ENDCASE
  121.           ENDDO
  122.           RETURN 0
  123.  
  124. Typically, whenever a menu option is selected, a procedure is run
  125. which creates a new window (plus child windows as required) and adds 
  126. to the window handle array :
  127.  
  128.           AddHandler(hWnd1,bAction)
  129.  
  130. Whenever an event occurs within that window (hWnd1), HandleEvent() will 
  131. execute the associated code block (bAction) which is usually a large 
  132. DO CASE statement for all the relevant events.  You determine which 
  133. events you include in the DO CASE.
  134.  
  135. An example for a Window handling an expenses claim - a subject dear 
  136. to my heart - is: 
  137.  
  138.           #define CONTROL_ID       _LastwParam() 
  139.           
  140.           DO CASE
  141.              CASE nEvent == EVENT_CONTROL      
  142.                 // message received from a child control
  143.  
  144.                 DO CASE
  145.                    CASE CONTROL_ID == VAT_BUTTON  
  146.                       // User has clicked the VAT calculation button
  147.                       nVat := nGross*(control->vat/(1+control->vat))
  148.                       cVat := str(nVat,8,2)
  149.                    
  150.                       // Update VAT display box with result
  151.                       SendMessage(VAT_BOX,.....,len(cVat),cVat)
  152.  
  153.                    CASE .....
  154.  
  155.                 ENDCASE
  156.  
  157.              CASE nEvent == EVENT_REDRAW
  158.                 // Redraw window
  159.  
  160.              CASE nEvent == EVENT_QUIT
  161.                 // Exit window & destroy                
  162.                 DoExit()
  163.           ENDCASE
  164.  
  165. Those three lines of code are all you need to do when the VAT button
  166. is actioned.
  167.  
  168. Note that the pre-processor is used extensively to make coding easier
  169. to read and maintain.  Clip-4-Win's WINDOWS.CH contains hundreds of
  170. #defines mapping to Windows messages.  If you have not used the 
  171. pre-processor before it would be as well to become very familiar with it.
  172.  
  173. Typical events which you may wish to handle in your code are:
  174.  
  175.       *       Mouse clicks
  176.       *       Getting and losing focus (if you are familiar with the Get 
  177.               system this will present no problems)
  178.       *       User moving and resizing the window (how dare they!)
  179.       *       User hitting an accelerator key
  180.       *       Windows telling you to redraw your window 
  181.               (Windows gets pretty bossy).
  182.  
  183.  
  184. Event Handling Summary
  185. ----------------------
  186.  
  187. Within an application I have a different .prg for each main menu
  188. option with all its own global STATICS.  Each .prg consists of two 
  189. main procedures or functions (use a function when there is something 
  190. to return, otherwise a procedure).  The first function creates the 
  191. main window with all its child windows and adds a code block for the
  192. window to the event handling array.  The second contains a DO CASE 
  193. structure for each appropriate event.  Each event usually calls a 
  194. STATIC FUNCTION with all the necessary coding to handle it.  
  195.  
  196. Remembering that the end-user may be switching between multiple windows
  197. both inside and outside your own application, be extremely careful when
  198. handling files.  Do not rely on a file already being correctly 
  199. positioned.  Either reposition or write a function to restore the 
  200. whole environment as necessary.
  201.  
  202.  
  203. Debugging with Clip-4-Win
  204. -------------------------
  205.  
  206. 1.  Identifying Events
  207. ----------------------
  208.  
  209. Often, you will have mapped out the series of events which you wish 
  210. to handle for a particular Window but things will not go according 
  211. to plan.  Unfortunately the debugger does not work under Windows (yet?) 
  212. so you will have establish for yourself what is happening.  I have
  213. added a hook into John Skelton's ChkEvent() code which is supplied with  
  214. Clip-4-Win for this sort of purpose.  My code reads :
  215.  
  216.           DO WHILE TRUE
  217.              DO WHILE (nEvent := ChkEvent()) == EVENT_NONE
  218.              ENDDO
  219.              #IFDEF LOG
  220.                 LogAdd(nEvent)            // Ensure time is stored
  221.              #ENDIF
  222.              HandleEvent(nEvent)
  223.           ENDDO
  224.  
  225. I have similar LogAdd() calls in my event handlers with a string 
  226. identifying where the message was handled.  This shows me where 
  227. Windows/Clip-4-Win is sending each message for processing.  The
  228. LogAdd() routine merely stores the time and the contents of the 
  229. message into a .DBF which I can inspect later with DBU or BROPLUS.
  230.  
  231.       ************************************************
  232.       STATIC FUNCTION SetUpLog()   // Set Up the Log *
  233.       ************************************************
  234.       IF !file('Eventlog.dbf')
  235.          dbcreate('eventlog',;
  236.             { {'time'     ,   'C',    8, 0},;
  237.               {'event'    ,   'N',    5, 0},;
  238.               {'hwnd'     ,   'N',    5, 0},;
  239.               {'msg'      ,   'N',    5, 0},;
  240.               {'wpara'    ,   'N',    5, 0},;
  241.               {'lpara'    ,   'N',   10, 0},;
  242.               {'lolpa'    ,   'N',    5, 0},;
  243.               {'hilpa'    ,   'N',    5, 0}})
  244.       ENDIF
  245.  
  246.       USE eventlog NEW EXCLUSIVE
  247.       ZAP
  248.       RETURN NIL
  249.  
  250.       *********************************************
  251.       FUNCTION LogAdd(ev,cLog)  // Add to the log *
  252.       *********************************************
  253.       IF ev != EVENT_OTHER
  254.          eventlog->(dbappend())
  255.          IF empty(cLog)
  256.             eventlog->time   := time()     // if called without message
  257.          ELSE
  258.             eventlog->time   := cLog       // store identifier
  259.          ENDIF
  260.          
  261.          eventlog->event  := ev
  262.          eventlog->hwnd   := _LasthWnd()
  263.          eventlog->msg    := _LastMsg()
  264.          eventlog->wpara  := _LastwParam()
  265.          eventlog->lpara  := _LastlParam()
  266.          eventlog->lolpa  := _LastlolParam()
  267.          eventlog->hilpa  := _LasthilParam()
  268.          eventlog->(dbcommit())
  269.       ENDIF
  270.       RETURN NIL
  271.  
  272.  
  273. 2.   Interrogating Data
  274. -----------------------
  275.  
  276. Occasionally it is necessary to inspect the contents of series of 
  277. variables or fields.  I have developed two functions which can be 
  278. inserted into the code as needed to open a window and display the 
  279. required information.
  280.  
  281. The code snippet included is an example of a modal dialog box.  
  282. Although not to be used too often, a modal box is ideal for messages, 
  283. error messages and debug displays.  Please feel free to use it if you 
  284. wish.  Note that the function FitText() is used to position text ready 
  285. for display.  This enables me to use the sexier Windows proportional 
  286. fonts while still getting my output to look columnar.  This involves 
  287. the use of device contexts.  If you are still interested I shall cover 
  288. these next time.
  289.  
  290. ***********************************************
  291. // FUNCS.PRG
  292. ***********************************************
  293. #define WIN_WANT_LB     // Control the behaviour of pre-processor
  294. #define WIN_WANT_LBS
  295. #define WIN_WANT_ALL
  296.  
  297. #include "windows.ch"   // Supplied with Clip-4Win
  298. #include "pam.ch"       // My header
  299. #include "pamwin.ch"    // My header
  300.  
  301. #xtranslate  GETPIXELWIDTH(<h>,<c>) => C4W_LoWord(GetTextExtent(<h>,<c>))
  302.  
  303. static hWindow
  304.  
  305. ******************************************************
  306. FUNCTION DispData(hW,aR,cT) // Display data elements *
  307. ******************************************************
  308. local aList := {}
  309. local cTitle := 'P.A.M. Data Display' +  IF(!empty(cT),' - ' + cT,'')
  310. local aDlg
  311. local hInst  := _GetInstance()
  312. local nDlgRes
  313. local cSelected
  314. local nP,i
  315.  
  316. #define BOX_RIGHT     315
  317. #define BOX_BOTTOM    225
  318.  
  319. hWindow := hW
  320. aeval(aR,{|r| aadd(aList,FitText(hWindow,r[1],decode(r[2]),100,'L'))})
  321.  
  322. aDlg := CreateDialog(cTitle,;
  323.                      WS_CAPTION + WS_SYSMENU + WS_GROUP + WS_TABSTOP +;
  324.                      WS_THICKFRAME + WS_VISIBLE + WS_POPUP,;
  325.                      0,0,BOX_RIGHT,BOX_BOTTOM)
  326.  
  327. aDlg := AppendDialog(aDLG,'list',DLG_LISTBOX,;
  328.                           WS_TABSTOP + WS_CHILD + WS_VISIBLE +;
  329.                           WS_VSCROLL + WS_BORDER,;
  330.                           0,0,BOX_RIGHT,BOX_BOTTOM - 50,;
  331.                           aList)             // The list
  332.  
  333. aDlg := AppendDialog(aDlg,'ok',DLG_BUTTON,;
  334.                           BS_DEFPUSHBUTTON + WS_TABSTOP +;
  335.                           WS_CHILD + WS_VISIBLE,;
  336.                           BOX_RIGHT/2 - 25, BOX_BOTTOM - 30,;
  337.                           50,25,'&OK')
  338.  
  339. aDlg := AppendDialog(aDlg,'help',DLG_STATIC,;
  340.                           SS_LEFT + WS_CHILD + WS_VISIBLE,;
  341.                           BOX_RIGHT/2 + 35, BOX_BOTTOM - 30,;
  342.                           100,25,'OK with an array selected')
  343.  
  344. aDlg := AppendDialog(aDlg,'help2',DLG_STATIC,;
  345.                           SS_LEFT + WS_CHILD + WS_VISIBLE,;
  346.                           BOX_RIGHT/2 + 35, BOX_BOTTOM - 20,;
  347.                           100,25,'will display the array')
  348.  
  349. DO WHILE TRUE
  350.    nDlgRes := ModalDialog(aDlg,hInst,hW)
  351.    DO CASE
  352.       CASE nDlgRes <= 0
  353.          EXIT
  354.       CASE nDlgRes == 2     // OK
  355.          cSelected := GetDialogResult(aDlg,'list')
  356.          IF !empty(cSelected)
  357.             nP := at(' ',cSelected)
  358.             cSelected := left(cSelected,nP-1)
  359.             IF !empty(i := ascan(aR,{|r| r[1] == cSelected}))
  360.                IF valtype(aR[i][2]) == 'A'
  361.                   DispArray(hW,aR[i][2],aR[i][1])
  362.                ENDIF
  363.             ENDIF
  364.          ELSE
  365.             EXIT
  366.          ENDIF
  367.    ENDCASE
  368. ENDDO
  369. RETURN 0
  370.  
  371. ************************************************
  372. FUNCTION DispArray(hW,aR,cT)  // Display array *
  373. ************************************************
  374. local aList := {}
  375. local cTitle := 'P.A.M. Array Browser' +  IF(!empty(cT),' - ' + cT,'')
  376. local aDlg
  377. local hInst  := _GetInstance()
  378.  
  379. hWindow := hW
  380. aList(aR,aList)    // Process the array into a list
  381.  
  382. aDlg := CreateDialog(cTitle,;
  383.                      WS_CAPTION + WS_SYSMENU + WS_GROUP + WS_TABSTOP +;
  384.                      WS_THICKFRAME + WS_VISIBLE + WS_POPUP,;
  385.                      0,0,BOX_RIGHT,BOX_BOTTOM)
  386.  
  387. aDlg := AppendDialog(aDLG,'list',DLG_LISTBOX,;
  388.                           WS_TABSTOP + WS_CHILD + WS_VISIBLE +;
  389.                           WS_VSCROLL + WS_BORDER,;
  390.                           0,0,BOX_RIGHT,BOX_BOTTOM,;
  391.                           aList)             // The list
  392.  
  393. ModalDialog(aDlg,hInst,hW)
  394. RETURN 0
  395.  
  396. ******************************************************************
  397. STATIC FUNCTION aList(aR,aList,cLine)   // Process aR into aList *
  398. ******************************************************************
  399. local i
  400. local cType
  401. local cThisLine
  402. DEFAULT cLine to ''
  403.  
  404. #define ELEMENT  '[' + alltrim(str(i)) + ']'
  405.  
  406. FOR i = 1 to len(aR)
  407.    cType := valtype(aR[i])
  408.    DO CASE
  409.       CASE valtype(aR[i]) == 'A'
  410.          aList(aR[i],aList,cLine + ELEMENT)
  411.       OTHERWISE
  412.          cThisLine := cLine + ELEMENT + '  ' + decode(aR[i])
  413.          aadd(aList,cThisLine)
  414.    ENDCASE
  415. NEXT
  416. RETURN 0
  417.  
  418. ************************************************
  419. static function decode(x)   // Decode the data *
  420. ************************************************
  421. local cT      := valtype(x)
  422. local cRet := FitText(hWindow,cT,':',20,'L')
  423. local i
  424. local aBlocks := { {'A',{|d| 'Array of length ' + alltrim(str(len(d)))}},;
  425.                    {'B',{|d| '{|| .... }'}},;
  426.                    {'C',{|d| '"' + d + '"'}},;
  427.                    {'D',{|d| dtoc(d)}},;
  428.                    {'L',{|d| if(d,'.T.','.F.')}},;
  429.                    {'N',{|d| alltrim(str(d)) }},;
  430.                    {'U',{|d| 'NIL'} } }
  431.  
  432. IF !empty( i := ascan(aBlocks,{|b| b[1] == cT}))
  433.    cRet := FitText(hWindow,cRet,eval(aBlocks[i][2],x),40,'L')
  434. ELSE
  435.    cRet := FitText(hWindow,cRet,'***',40,'L')
  436. ENDIF
  437. RETURN cRet
  438.  
  439. *******************************************************
  440. FUNCTION FitText(hW,cT1,cT2,nP,cF,hDC)   // Fit text  *
  441. *******************************************************
  442. local   lCreateDC := FALSE
  443. local   n1, n2, nSp
  444. local   cRes  := ''
  445. local   cPre, cPost, nDec
  446.  
  447. IF empty(hDC)                            // Establish device context
  448.    lCreateDC := TRUE                     // only if not passed
  449.    hDC := GetDC(hW)
  450. ENDIF
  451.  
  452. n1    := GETPIXELWIDTH(hDC,cT1)
  453. n2    := GETPIXELWIDTH(hDC,cT2)
  454. nSp   := GETPIXELWIDTH(hDC,' ')          // Pix width of a space
  455.  
  456.  
  457. DO CASE
  458.    CASE cF == 'L'                        // aaaaaaa    |bbbbbb
  459.        DO WHILE (n1 := GETPIXELWIDTH(hDC,cT1)) > nP
  460.           cT1 := left(cT1,len(cT1)-1)
  461.        ENDDO
  462.        cRes := cT1 + IF(n1<nP,space(int((nP-n1)/nSp)),'') + cT2
  463.  
  464.    CASE cF == 'R'                       // aaaaaaaa    bbbbbbbb|
  465.        DO WHILE (n1 := GETPIXELWIDTH(hDC,cT1)) + ;
  466.                 (n2 := GETPIXELWIDTH(hDC,cT2)) > nP
  467.           IF !empty(cT1)
  468.              cT1 := left(cT1,len(cT1)-1)
  469.           ENDIF
  470.           IF !empty(cT2)
  471.              cT2 := right(cT2,len(cT2)-1)
  472.           ENDIF
  473.        ENDDO
  474.        cRes := cT1 + space(int((nP-(n1+n2))/nSp)) + cT2
  475.  
  476.                                          //                  |
  477.     CASE cF == 'N'                       //     aaaaaaaaa 123.45
  478.        cT2    := alltrim(cT2)            // remove odd blanks
  479.        nDec   := at('.',cT2)
  480.        cPre   := left(cT2,nDec-1)
  481.        cPost  := substr(cT2,nDec)
  482.        cRes   := FitText(hW,cT1,cPre,nP,'R',hDC)    // Watch the recursion!
  483.        cRes   := FitText(hW,cRes,cPost,nP,'L',hDC)
  484. ENDCASE
  485.  
  486. IF lCreateDC                           // Only release the DC if I created it
  487.    ReleaseDC(hW,hDC)
  488. ENDIF
  489. RETURN cRes
  490.  
  491.  
  492.  
  493. APPENDIX
  494. ========
  495.  
  496. Clip-4-Win : written by John Skelton of Skelton Software
  497. (Kendal Cottage, Hillam, Leeds LS25 5HP).  
  498. Available through QBS.
  499.  
  500. Notes on PMP
  501. ------------
  502.  
  503. Pam Pitcher has been running her own company P.A.M. Ltd for the past 
  504. 14 years, designing and writing software predominantly for PCs.  
  505. She is a Clipper addict and is looking forward(?) to tackling CA_VO.  Her
  506. husband Mike, also part of P.A.M.,  specialises in IT consultancy
  507. at all levels for all sizes of corporations.
  508.  
  509. P.A.M. Ltd
  510. Sandycot, Cadsden Road
  511. Princes Risborough, Bucks, HP27 0NB
  512. 0844 346687 (CompuServe : 100020,3157)
  513.  
  514.