home *** CD-ROM | disk | FTP | other *** search
/ BUG 4 / BUGCD1997_05.BIN / aplic / clip4win / clip4win.exe / C4W30E.HUF / CONVERT.TXT < prev    next >
Text File  |  1996-07-11  |  97KB  |  2,466 lines

  1.  
  2. Converting To Windows
  3. =====================
  4.  
  5. This is about converting:
  6.  
  7.     -  developers (you!)
  8.  
  9.     -  applications
  10.  
  11. to Windows.  Both imply understanding the Windows way of doing things,
  12. which gets quite a bit of coverage below.
  13.  
  14. Before you read on, if you've not followed QUICK.TXT please do so now.
  15.  
  16. The Norton Guide (NG\CLIP4WIN.NG), Windows Help file (HLP\C4W.HLP) and
  17. manuals contain further tutorials, e.g. on Object Orientation, and
  18. a lot of other information.
  19.  
  20. Note: if you have an Evaluation Version of Clip-4-Win, you may have to
  21. download extra files and/or purchase the manuals separately.
  22.  
  23. You may be wondering about the tools you use.  Mostly you just use the
  24. same ones as for DOS Clipper: the same version control system (if any);
  25. the same editor; the same Clipper pre-processor/compiler (CLIPPER.EXE);
  26. the same Norton Guides engine (NG.EXE or EH.EXE) - although you'll also
  27. use the Windows Help engine (WINHELP.EXE).  However, you need a linker
  28. that can make Windows EXE files, which are not the same internally as
  29. DOS EXE files, so unless you already use BLINKER, as many do, you need
  30. one of the Windows linkers (BLINKER, MS LINK or OPTLINK).
  31.  
  32. Note: the Clip-4-Win Toolkit is an economical way to get a good linker.
  33. You may also be able to get an evaluation version of Blinker (ask your
  34. Clipper add-on supplier).
  35.  
  36. A tool that may well be new to you is a resource editor (and compiler).
  37. You can use it visually to design forms (dialogs), bitmaps, icons, etc.,
  38. via point and click, and then place these resources into your EXE file
  39. (or a DLL) for use by your application as it runs.  Resource editors
  40. include MS AppStudio and Borland Resource Workshop.  Form design is
  41. particularly easy in resource editors.  If it helps, think of a resource
  42. editor as like a word processor or code generator for the user interface
  43. parts of your applications.  You may have seen screen designers in
  44. products such as dBASE, FoxPro and Visual Basic.
  45.  
  46. Resource editors can often output an ASCII file (e.g. MYDLG.RC), a
  47. binary file (MYDLG.RES), or can edit the resources in an EXE or DLL.
  48. If you look at a few RC files, you'll soon see they use a simple
  49. scripting language.  An example appears later, with some explanations.
  50. Being ASCII files, you can edit RC files with any text file editor in
  51. addition to using a resource editor.
  52.  
  53. The Clip-4-Win Toolkit contains a resource editor as well as the Windows
  54. linker.
  55.  
  56. Note: you do NOT need a resource editor to use Clip-4-Win, but you will
  57. be able to design screens much more easily if you get one.
  58.  
  59. A tool you may have taken for granted is Windows itself.  You can easily
  60. open more than one DOS session, e.g. one for Norton Guides, another for
  61. your editor and maybe another for RMAKE.  You can Alt+Tab (or Ctrl+Esc
  62. or mouse click) to switch between your sessions and/or Program Manager
  63. (or Explorer or whatever).  There's no need to keep shutting Windows
  64. down.
  65.  
  66. The following sections give details of which parts of your DOS Clipper
  67. applications you can keep and about how to convert the other parts.
  68.  
  69. It turns out that certain Clipper statements are much easier to convert
  70. than others (as will be explained), so if you can start with an application
  71. which has relatively few of the harder statements you'll be finished
  72. sooner and gain confidence and valuable experience.
  73.  
  74. The code that can usually stay the same is your "business logic", i.e.
  75. your code to create and maintain databases, check for existence or
  76. non-existence of parts, customers, orders, and so on.
  77.  
  78. Your business logic is entirely or largely unconcerned with the user
  79. interface, and is often over half the bulk of the code, counting by
  80. lines of code.  If anything, "lines of code" probably underestimates
  81. the importance of your business logic, because developers of complex
  82. applications eventually spend the bulk of their time creating, expanding
  83. and debugging the business logic.  Consequently, your business logic
  84. may be much more than half your application in terms of complexity.
  85. Being able to keep it is very important: you may save many months of
  86. hard work.  Also, if you structure the logic appropriately, you can
  87. maintain just one set of code and use it in both Windows and DOS.
  88.  
  89. You can usually keep your network code (if any), although any parts
  90. that interact with your user are going to need some revision.
  91.  
  92. This leaves the code involving the user interface, printing/reporting,
  93. and your link scripts.
  94.  
  95. Generally speaking, in terms of conversion effort:
  96.   -  menus are very easy
  97.   -  box-drawing is usually very easy
  98.   -  international character strings are very easy
  99.   -  Clipper add-ons are usually very easy or else totally unsuitable
  100.   -  link scripts are very easy
  101.   -  changes to MAIN() are usually easy
  102.   -  browsers are easy
  103.   -  simple inputs using ACHOICE() or MEMOEDIT() are easy
  104.   -  popup windows are usually easy
  105.   -  data entry forms are less easy and also often need structural changes
  106.   -  data driven is often quite easy
  107.   -  printing/reports may be easy or hard
  108.   -  converting from modal to non-modal (*) may be easy or hard
  109.  
  110. (*) "modal" is the typical DOS way: you have a very restricted number
  111. of choices available in each of an application's screens, and to access
  112. the other parts of the application you have to complete or cancel the
  113. current screen.  MS encourages less modality in Windows applications.
  114. Please refer to the topic "Converting From Modal To Modeless", which also
  115. discusses "SDI" and "MDI".
  116.  
  117. Note that menus in DOS Clipper do not have to be implemented using
  118. @ PROMPT ... MENU TO.  That doesn't matter when converting, what does
  119. matter is that you identify the underlying functionality.
  120.  
  121. Similarly, data entry isn't always done using @ ... SAY ... GET ... READ,
  122. but may instead use INKEY() etc.  Regardless of the actual implementation
  123. you have in DOS, you're almost certain to end up with a particular kind
  124. of (different) code in Windows.
  125.  
  126. Why?  Because Windows is different to DOS and does things in different
  127. ways.  Don't be too concerned, however, as Clip-4-Win greatly reduces
  128. your pain.  For example, as well as keeping your business logic you get
  129. some very easy to use menus.  And you get a browser that's very like
  130. TBROWSE yet also supports non-proportional fonts, bitmaps, scroll bars,
  131. the mouse, re-sizing, drag-and-drop columns, and so on.
  132.  
  133. Clip-4-Win helps in another way: even the code you will eventually
  134. convert can usually be left temporarily as it is.  Generally you will
  135. be able to link your application and run it in Windows, although it's
  136. not a good idea to invoke the unconverted code.  Being able to convert
  137. some of the code and leave large amounts unconverted means you can do
  138. the conversion mostly in the order that suits you.
  139.  
  140. Just as you can implement each part of your DOS applications in various
  141. ways, so you can in Windows.  However, unlike in DOS, many things are
  142. fairly well standardised in Windows.  You probably want your applications
  143. to look and feel like they belong in Windows - so be very cautious about
  144. doing things in non-standard ways.  It usually means more work, sometimes
  145. a lot more work, too.
  146.  
  147. This is a good place to stress that you really should think hard before
  148. you decide to do things your own way rather than the standard Windows
  149. way.  Even if you think your way is better, you are likely to be severely
  150. criticised by experienced Windows users, reviewers of your product and
  151. your competitors.  If you don't currently use any Windows applications
  152. in your work, switch over to a few if you can, so you get used to what
  153. people will expect from your applications.
  154.  
  155. Consider reading "Windows Interface Guidelines for Software Design"
  156. (Microsoft Press, ISBN 1-55615-679-0, July 1995, 300 pages).
  157.  
  158. Another source of information, highly recommended, is the MS Developer
  159. Network (on CD-ROM), often known simply as "MSDN".  Currently, this is
  160. available as a subscription service at various levels.  If you're short
  161. of money, just go for the cheapest (Single Edition, if you can get it,
  162. or Level 1), which includes the above book and a truly huge amount of
  163. other information.  Otherwise, at least get the Professional (Level 2,
  164. which includes lots of SDKs).  Only get the most expensive subscription
  165. if you're sure you need it.
  166.  
  167. MSDN includes the Interface Guidelines referenced above.
  168.  
  169. For information about books, see the HLP file topic "Windows Books" or
  170. the NG topic Other...Windows Books.
  171.  
  172. Let's examine each of the parts of a typical Clipper application, and
  173. see how they're done in Windows using Clip-4-Win.  Of course, it may be
  174. that your application isn't at all a typical Clipper one, as Clipper is
  175. flexible enough to be useful in all sorts of applications.  If that's
  176. the case, apologies are due here, but maybe some of the following will
  177. still be relevant.
  178.  
  179. Before looking at the details of converting your code there's the issue
  180. of the "style" you like to use: not the way you indent or use upper/lower
  181. case, but your preference for using commands (Xbase syntax) or functions,
  182. and your liking or not of objects.
  183.  
  184. You can program Clip-4-Win in the same sort of style you use for DOS
  185. Clipper: avoiding UDC's or using lots of them, and few or many objects.
  186. If you use a class-creation add-on such as Class(y), you can continue
  187. using it.  Clip-4-Win doesn't make you change, and it doesn't force you
  188. to use objects - though Clip-4-Win has some powerful new classes.
  189.  
  190. In the details to follow, you'll find information on various ways to do
  191. things, so you should be able to stick to your preferred style.  As with
  192. DOS Clipper, you can freely mix styles (just about everything ends up as
  193. a function call).
  194.  
  195. You may be aware of "standard" ways of programming Windows, e.g. with
  196. a GetMessage() loop.  You can program Clip-4-Win like that if you want,
  197. perhaps because you've got a book about programming for Windows and like
  198. its approach, or you can leave Clip-4-Win to handle such things.  Again,
  199. this is really a personal preference.  There are many samples you can
  200. look at to see which style you like, and the details to come should help,
  201. too.
  202.  
  203. For that matter, you may have heard of MFC (Microsoft Foundation Classes).
  204. Clip-4-Win's Application Classes (included with the product) are the
  205. nearest equivalent, but deliberately easier to use and oriented towards
  206. business applications.
  207.  
  208. A small aside: some people sneer at others wanting to use their own
  209. message loop and access the Windows API directly.  This seems an odd
  210. sort of arrogance, not least because that style means the many excellent
  211. books about Windows are most useful.  The same sneering developers tend
  212. to say "you might as well use C", conveniently forgetting that Clipper
  213. has many powerful features not in C, such as its database (Xbase)
  214. handling, its ragged arrays, string handling, and so on.
  215.  
  216. As mentioned before, you can program in any of the above styles, and
  217. you can mix them.  For that matter, if you ever wish to do so, you can
  218. convert between styles quite easily.  You will probably never do so,
  219. unless perhaps you start out not using objects and then decide to move
  220. over to objects.
  221.  
  222.  
  223. First let's look at the major functional parts of an application, then
  224. at the underlying Clipper statements one by one.
  225.  
  226.  
  227. Converting MAIN()
  228. -----------------
  229.  
  230. Before MAIN() itself, you are likely to want at least:
  231.  
  232. #include "windows.ch"
  233. #define NO_C4WCLASS         // leave this line out if using objects
  234. #include "commands.ch"      // leave this line out if not using UDCs
  235.  
  236. /*
  237.  *  Add the following if you want to create any objects using the
  238.  *     MyClass{ <params> }
  239.  *  syntax:
  240.  */
  241. #include "topclass.ch"
  242.  
  243.  
  244. At the start of MAIN(), if you're avoiding UDCs and avoiding objects,
  245. you usually want:
  246.  
  247.    SET CONFIRM ON
  248.    SET SCOREBOARD OFF
  249.  
  250.    /*
  251.     *  The C4W_AutoClose() function allows us to make the user confirm
  252.     *  their decision to quit the app, even if they try to close a window
  253.     *  with ALT-F4 (or via the System menu).
  254.     */
  255.    C4W_AutoClose(.F.)       // disables the auto-closing of windows
  256.  
  257.    /*
  258.     *  Use the following if you want Windows to have any idle time.
  259.     *  Leaving this out may disable screen savers and/or power
  260.     *  management features, and may result in 100% CPU utilisation.
  261.     *  In practice, you want this line of code unless you've got a
  262.     *  special reason of your own to leave it out.
  263.     */
  264.    C4W_UsePeekMessage(.F.)
  265.  
  266.    /*
  267.     *  You're almost certain to need SetHandleCount( <n> ) !!
  268.     *  (As well as the other ways to let Clipper use many file handles.)
  269.     */
  270.    SetHandleCount(50)       // the default is only 20
  271.  
  272.    /*
  273.     *  Use the following if you want 3-D effects on versions of Windows
  274.     *  that don't automatically provide 3-D.  Note: you also need to
  275.     *  ship CTL3D.DLL with your application.
  276.     *
  277.     *  If you use this, you should try to call Ctl3D(.F.) just before
  278.     *  your application does a QUIT (to unload the DLL).
  279.     */
  280.    Ctl3D(.T.)               // implemented in SOURCE\APPMAIN.PRG
  281.  
  282.    /*
  283.     *  Use the following if you intend to use any Borland-style dialog
  284.     *  items in your application.  Note: you then also need to ship
  285.     *  BWCC.DLL with your application.
  286.     *
  287.     *  If you use this, you should try to call BWCC(.F.) just before
  288.     *  your application does a QUIT (to unload the DLL).
  289.     */
  290.    BWCC(.T.)                // implemented in SOURCE\APPMAIN.PRG
  291.  
  292. The above are done automatically by the UDC (CREATE APPLICATION) to be
  293. used below.  The relevant ones are also done for an OO application by
  294. the WAppBase application base class, in method WAppBase:Init().
  295.  
  296. Note: the defaults for CREATE APPLICATION and WAppBase:Init() are to
  297. use 3-D but not load BWCC.DLL.
  298.  
  299. The 3-D effects are not strictly needed on Windows 95 (or NT 4).  You
  300. can even argue that they're wrong there, as the 3-D look is slightly
  301. different.  See "Converting Data Entry Forms" for more information.
  302.  
  303. Next you want any of your own initialisation code that should run
  304. before you create your main window.  This does not include a menu, as
  305. a menu has to be attached to a window.  For examples of splash screens,
  306. see SOURCE\OO\SPLASH.PRG and SOURCE\SPLASH2.PRG.
  307.  
  308. After this initialisation code you can create your main window and
  309. optionally add a menu (you're not forced to use a menu).  You can create
  310. your window using UDC's or function calls, and avoiding or using objects.
  311. As stated before, you can use the style you prefer.
  312.  
  313. For example:
  314.  
  315. LOCAL oApp, oWnd
  316.  
  317. SET CONFIRM ON
  318.  
  319. . . . your init code . . .
  320.  
  321. CREATE APPLICATION oApp  WINDOW oWnd  TITLE "Clip-4-Win Example" ;
  322.    ON INIT    DoInit(oWnd)
  323. . . .
  324.  
  325.  
  326. STATIC FUNCTION DoInit(oWnd)
  327. /*
  328.  *  You're almost certain to need SetHandleCount( <n> ) !!
  329.  *  (As well as the other ways to let Clipper use many file handles.)
  330.  */
  331. SetHandleCount(60)       // the Windows default is only 20
  332.  
  333. MenuSetup(oWnd)
  334.  
  335. ToolbarSetup(oWnd)
  336. . . .
  337.  
  338. CREATE APPLICATION (from COMMANDS.CH) arranges to create the window
  339. before actually calling DoInit().  Although the fine details of CREATE
  340. APPLICATION may change in future versions of Clip-4-Win, in a non-OO
  341. application this UDC:
  342.  
  343.   -  calls function C4W_AppInit() [in SOURCE\APPMAIN.PRG]
  344.  
  345.   -  creates the main (frame) window, using the Windows API function
  346.      CreateWindow() [described in the Clip-4-Win NG/HLP/manuals]
  347.  
  348.   -  calls the "ON INIT" function, if any [here it's DoInit()]
  349.  
  350.   -  calls function C4W_AppMain() [also in SOURCE\APPMAIN.PRG], which
  351.      contains the "message loop" mentioned before
  352.  
  353. The exact details are different for an OO application, but the
  354. underlying functionality is the same.  CREATE APPLICATION (if used),
  355. does this:
  356.  
  357.   -  creates an application object (oApp)
  358.  
  359.   -  creates a main (frame) window object (oWnd)
  360.  
  361.   -  calls the "ON INIT" function, if any [here it's DoInit()]
  362.  
  363.   -  starts the message loop, using oApp:Start()
  364.  
  365. Don't be too frightened to look at COMMANDS.CH: CREATE APPLICATION
  366. is one of the simpler UDC's.
  367.  
  368. For a longer sample using UDC's, see SOURCE\APP.PRG.  The version
  369. without UDC's is SOURCE\APPOLD.PRG.
  370.  
  371. For an OO sample, try the OO tutorial in the NG, HLP and manuals or
  372. the many samples in the SOURCE\OO directory.
  373.  
  374. Before your application finally exits, it should free any DLL it has
  375. loaded.  This is partly to allow Windows to re-use the memory if it
  376. needs it, but also to let a different version of the DLL be loaded, in
  377. case you have an application that depends on a particular version.
  378.  
  379. You can make sure CTL3D.DLL and/or BWCC.DLL are unloaded using:
  380.     Ctl3D(.F.)
  381.     BWCC(.F.)
  382.  
  383. Both functions ignore any extra calls, so you can call them before any
  384. QUIT, e.g. in ERRORSYS.
  385.  
  386. During development it's easy to leave a DLL in memory by accident.  If
  387. you're not changing it for a different version, having it left in memory
  388. doesn't really matter.  However, if you need to unload a DLL, e.g. so
  389. you can use a newer version, you can try SOURCE\DLLFREE.PRG.  If you
  390. still have problems, restart Windows.
  391.  
  392.  
  393.  
  394. Converting Menus
  395. ----------------
  396.  
  397. As stated, converting menus - whether implemented in DOS using MENU TO
  398. or any other way - is basically very easy.  However, there is a minor
  399. complication.
  400.  
  401. In DOS you often have sub-menus that are scattered around your code,
  402. and are only seen by the user if they choose the relevant menu items
  403. that lead to the sub-menus.  You may restrict access to certain menus
  404. or menu items, depending on such things as login name/password or that
  405. some other activity has taken place previously.
  406.  
  407. You can achieve something roughly equivalent in Windows; the minor
  408. complication is that you should describe all your menus and sub-menus
  409. at once.  This lets Windows handle your user's menu navigation and
  410. selection, whether by mouse, keyboard, pen, or a combination.
  411.  
  412. One way you can restrict access is to gray (disable) some items, which
  413. has the advantage of showing your user that they're potentially available,
  414. but some other condition needs to be met first.  Naturally, you may not
  415. want some options to appear at all if they relate to security or if
  416. keeping the menu as simple as possible is your concern.
  417.  
  418. Another way you can restrict access is to use different menus/sub-menus
  419. depending on the login name/password or some other information.
  420.  
  421. However, you should normally set the menu once and not keep changing
  422. its structure dramatically.  As your application runs, you can disable
  423. and enable items, but avoid even that if you can, as users tend to find
  424. it frustrating/confusing.  It often makes sense to be able to choose at
  425. any time a menu item that opens another window.
  426.  
  427. Frequently, only your top-level (frame) window has a menu, which you may
  428. modify depending on the other windows your user has open.  You can have
  429. menus on other windows if it seems a particularly good idea.  Keep in
  430. mind that you may confuse your users if you use more menus, that it's
  431. more work for you, and that a menu reduces the window area in which you
  432. can display other information (the "client rectangle").
  433.  
  434. Dialogs are a particular kind of window used to get input from users,
  435. and don't normally have a menu of their own.  Indeed, certain dialog
  436. styles cannot have a menu.  The former is a user interface issue, the
  437. latter is a restriction of Windows.  For much more information about
  438. dialogs, see "Converting Data Entry Forms".
  439.  
  440.  
  441. Converting Menus - An Example
  442. -----------------------------
  443.  
  444. Let's suppose you have an application that users must log into.  The
  445. main menu is displayed in function Main().  There's a sub-menu in
  446. function Customers().  There's another sub-menu in Admin() that's only
  447. visible if IsAdminUser() says it should be (by returning true).
  448.  
  449. The essential elements of the code are:
  450.  
  451. function Main()
  452. . . .
  453. . . . init and login code . . .
  454. . . .
  455. @ Row()+1,nLeft PROMPT "Customers"
  456. . . .
  457. if IsAdminUser()
  458.    @ Row()+1,nLeft PROMPT "Admin"
  459. endif
  460.  
  461. MENU TO nChoice
  462.  
  463. do case
  464. case nChoice == 1
  465.    Customers()
  466. . . .
  467. case nChoice == 5
  468.    Admin()
  469. . . .
  470. return nil
  471.  
  472.  
  473. function Customers()
  474. . . .
  475. @ Row()+1,nLeft PROMPT aCustMenu[1]
  476. . . .
  477. MENU TO nChoice
  478. . . .
  479. do case
  480. case nChoice == 1
  481.    CustFind()
  482. . . .
  483. return nil
  484.  
  485.  
  486. function Admin()
  487. . . .
  488. @ Row()+1,nLeft PROMPT aAdminMenu[1]
  489. . . .
  490. MENU TO nChoice
  491. . . .
  492. return nil
  493.  
  494.  
  495. As previously stated, you need to combine all the menus into one.  You
  496. make each sub-menu a POPUP menu, and each other item a MENUITEM.
  497.  
  498. In Windows, POPUP menus are either in a bar menu or in another POPUP
  499. menu.  A MENUITEM can be in a POPUP menu, or (uncommonly) in a bar menu.
  500. You can navigate through a POPUP, if it's not grayed/disabled, but you
  501. can only select a MENUITEM.  (If you've never used Windows and haven't
  502. followed QUICK.TXT, you shouldn't be reading this!)
  503.  
  504. Windows uses a "handle" to identify uniquely each window or menu, or
  505. indeed each other item - whether a user interface item or a non-user
  506. interface item.  This is essentially the same way that you use objects
  507. in Clipper for each GET, TBROWSE or TBCOLUMN.  Names of handles usually
  508. begin with the letter "h", e.g. hWnd, hMenu, hDLL.  Sometimes they're
  509. shown as oWnd, oMenu, and so on, perhaps to make a potential move to
  510. an OO application easier or to use a prefix more familiar to Clipper
  511. developers.
  512.  
  513. Handles are nothing unusual: you've certainly met file handles in DOS
  514. and you may also have met handles in other products, such as CA-Tools.
  515.  
  516. Ignoring for the moment whether it's a good Windows user interface
  517. (there's not enough here to be able to judge), here's a direct
  518. translation, using simple UDC's but no objects:
  519.  
  520. #include "windows.ch"
  521. #define NO_C4WCLASS         // leave this line out if using objects
  522. #include "commands.ch"
  523.  
  524. function Main()
  525. . . .
  526. . . . init and login code . . .
  527. . . .
  528. return nil
  529.  
  530.  
  531. static function MenuSetup(hWnd)
  532. MENU IN hWnd
  533.  
  534.    POPUP "&Customers"
  535.       MENUITEM aCustMenu[1]  ACTION CustFind()
  536.       . . .
  537.    ENDPOPUP 
  538.  
  539.    . . .
  540.  
  541.    if IsAdminUser()
  542.    POPUP "&Admin"
  543.       MENUITEM aAdminMenu[1]  ACTION AdminXXX()
  544.       . . .
  545.    ENDPOPUP 
  546.    endif
  547.  
  548. ENDMENU
  549. return nil
  550.  
  551.  
  552. Please see the section "Converting MAIN()" for details of its call to
  553. MenuSetup().
  554.  
  555. Notice that the nChoice variables and associated DO CASE statements are
  556. no longer needed, and that functions Customers() and Admin() are also no
  557. longer needed.
  558.  
  559. The "&" character makes the following character an accelerator (hotkey)
  560. and indicates this to your user by underlining it.
  561.  
  562. An object-oriented version is almost identical, but you would probably
  563. use a method instead of the static function, and "hWnd" will be "oWnd"
  564. (or "self", for a method).
  565.  
  566. Here's MenuSetup() implemented using function calls:
  567.  
  568. static function MenuSetup(hWnd)
  569. local hMenu, hPopup
  570.  
  571. hMenu = CreateMenu()
  572.  
  573. hPopup = CreatePopupMenu()
  574. AppendMenu(hMenu, "customers", MF_POPUP, "&Customers", hPopup)
  575. AppendMenu(hPopup, "custfind", MF_STRING, aCustMenu[1], {|| CustFind()})
  576. . . .
  577. if IsAdminUser()
  578.    hPopup = CreatePopupMenu()
  579.    AppendMenu(hMenu, "admin", MF_POPUP, "&Admin, hPopup)
  580.    AppendMenu(hPopup, "admin1", MF_STRING, aAdminMenu[1], {|| AdminXXX()})
  581.    . . .
  582. endif
  583.  
  584. SetMenu(hWnd, hMenu)
  585. return nil
  586.  
  587.  
  588. Converting International Character Strings
  589. ------------------------------------------
  590.  
  591. Windows uses a version of the ANSI character set, whereas DOS uses what
  592. Windows calls the OEM character set.  This can cause problems with both
  593. box-drawing and international (e.g. accented) characters.
  594.  
  595. Box-drawing characters should usually be removed and a dialog or menu
  596. used instead, but if you really needed to draw boxes you can easily do
  597. so in Windows, e.g. using MoveTo() and LineTo().
  598.  
  599. You should keep ANSI characters out of database files and file names.
  600. However, you want ANSI characters in window/dialog titles, calls to
  601. MessageBox() and controls.  Windows provides the OemToAnsi() and
  602. AnsiToOem() functions, and the CBS_OEMCONVERT and ES_OEMCONVERT styles
  603. for combo boxes and edit controls.
  604.  
  605. Converting:
  606.              OEM -> ANSI -> OEM
  607. or
  608.              ANSI -> OEM -> ANSI
  609.  
  610. does not always result in the original string, because some characters
  611. have no equivalent.  Don't blame Clip-4-Win!
  612.  
  613. Also, some fonts cannot display certain characters.  This is a feature
  614. (limitation) of the fonts.  Sorry: it is not Clip-4-Win's fault.
  615.  
  616. When using a resource editor, the characters you enter are ANSI ones.
  617.  
  618. DOS text editors usually treat input as OEM characters.  This means if
  619. you have any special characters (e.g. ASCII value > 128) in strings in
  620. your program code, you need to use OemToAnsi() to display those strings
  621. to your users (in menus, dialogs, message boxes etc.).
  622.  
  623. Windows text editors can sometimes be set to use either character set,
  624. but otherwise normally treat your keystrokes as ANSI.
  625.  
  626. If you're not sure what you have in a file, try viewing it with a Windows
  627. tool such as NOTEPAD, and you'll see your characters treated as ANSI.  If
  628. they look right, they are ANSI.  If not, they're OEM characters, so you
  629. probably need to use OemToAnsi().
  630.  
  631. The Clipper compiler doesn't care whether you use OEM or ANSI characters
  632. in strings; it just compiles what it finds.  What matters is what you
  633. use the strings for at runtime.
  634.  
  635. For example,
  636.     MessageBox( , OemToAnsi(CHR(129)), "u umlaut")
  637. and
  638.     MessageBox( , CHR(252), "u umlaut")
  639. and
  640.     MessageBox( , OemToAnsi("ü"), "u umlaut")
  641.  
  642. all do the same thing, but the last one will look wrong if you don't
  643. view this file with a tool which uses the DOS (OEM) character set.
  644.  
  645. You can store ANSI strings as string resources in your EXE or a DLL,
  646. and your application can fetch them using LoadString().  If you have
  647. to support more than one language, consider using a separate DLL for
  648. each language.  If you use a separate DLL for each language and use the
  649. same ID for each translation of a string, a single LoadString() in
  650. your code is all you need, regardless of the language.  Your code can
  651. select the appropriate DLL once, and then the rest of your code need
  652. not be dependent on the language.
  653.  
  654. For example, you might keep the name of the appropriate DLL in an INI
  655. file, with the same name as the EXE (but INI as the extension), stored
  656. where the EXE resides, and proceed like this:
  657.  
  658. static hDLL
  659.  
  660. function InitLanguage()   // the once-only init code
  661. local cEXE := GetModuleFileName()    // where the EXE lives
  662. local cINI := left(cEXE, rat(".", cEXE)) + "INI"
  663. local cLang := GetPvProfString("Init", "Language", "ENG", cINI)
  664.  
  665. if (hDLL := LoadLibrary(cLang + ".DLL")) <= 32
  666.     // error ...
  667. endif
  668. return nil
  669.  
  670. function LoadStr(nId)
  671. return LoadString(hDLL, nId)
  672.  
  673. exit function Cleanup()
  674. FreeLibrary(hDLL)
  675. return nil
  676.  
  677. The various Windows API functions used above are described in the
  678. Clip-4-Win documentation.
  679.  
  680. Notice that the above code is very generic.  You can support another
  681. language in the future just by adding another DLL and changing the INI
  682. file.
  683.  
  684. You can maintain these language-specific DLLs using a resource editor, 
  685. which has the advantage that you don't need a programmer for the job.
  686.  
  687. Of course, you can also store strings in database files, but then you
  688. need some specific software to maintain them.
  689.  
  690. Note: a right-to-left language (e.g. Arabic or Hebrew) or one using
  691. pictograms/ideograms is beyond the scope of this discussion.
  692.  
  693.  
  694. Converting Clipper Add-On Code
  695. ------------------------------
  696.  
  697. Clipper add-ons which are well-behaved in protected mode usually work,
  698. providing they don't rely on low-level things like I/O port accessing,
  699. hardware interrupts, or screen RAM.
  700.  
  701. Well-behaved add-ons are believed to include: ADS, Blinker 3 or 4,
  702. Class(y), CLGraph/3D, CLImage, ClipWKS, COMIX, dbClass, Ed, FlexFile,
  703. FUNCky (not the screen I/O), HighClass, some of Nanforum and Netto,
  704. NetLib, NovLib, ObjectDB, OClip (with care can even be used as well as
  705. TopClass), RaSQL/B, SIx Driver (and related RDD's), TechWriter.
  706.  
  707. NOTE: For NovLib 2 you need to call NLWinApp() before any other NovLib
  708.       function.
  709.  
  710. RTLink (as shipped with Clipper) and ExoSpace are not relevant or
  711. needed, because they cannot create Windows EXE files.  Clip-4-Win uses
  712. Windows as a protected mode extender.
  713.  
  714. Badly-behaved add-ons include: CA-TOOLS, dGE (use Graphics Server),
  715. Telepathy (use Silverware).
  716.  
  717. Windows add-ons include: almost anything that's a DLL, AppStudio,
  718. Borland Resource Workshop, BTrieve, CA-RET / CA-Visual Express,
  719. Crystal Reports, Graphics Server, products supporting DDE, MS Excel,
  720. MS LINK, MS ODBC, MS Word for Windows (WinWord), Q+E Database Lib,
  721. OptLink, R & R for Windows, ReportSmith, SofToolS, SOS Info Author,
  722. Strike! for Windows.
  723.  
  724.  
  725.  
  726. Converting Link Scripts
  727. -----------------------
  728.  
  729. There's no need to overlay, so you can remove anything to do with that.
  730. Forget all about "load size", it's irrelevant in Windows.  Get used to
  731. the idea that Clipper applications are huge DOS programs but Clip-4-Win
  732. applications are quite small Windows programs.  If your EXE is 2MB it
  733. doesn't matter to Windows.
  734.  
  735. You need to use a DEF file (or in Blinker DEFBEGIN ... DEFEND) because
  736. it's the only thing that makes the linker build a Windows EXE file.
  737.  
  738. The SOURCE directory contains a number of sample DEF files.  You can
  739. just use CLIP4WIN.DEF, but you may well need a larger stacksize if you
  740. have a complex application and/or you nest function calls deeply.  Try
  741. going up 500 bytes at a time.  You may also need to increase heapsize,
  742. in which case try similar steps.  Unfortunately, there's no simple way
  743. to tell whether you're having a stack/heap problem or something else.
  744.  
  745. Few applications need more than 14000 for stacksize.  Heapsize rarely
  746. needs to be more than a few thousand at the most.  Keep both of these
  747. as small as you reasonably can, as they come from Clipper's scarce
  748. DGROUP - which is limited to 64KB.
  749.  
  750. If you make your own DEF file, note that the "name" is limited to 8
  751. characters.  Also, don't change the exetype unless you're sure you know
  752. what you're doing.  E.g. do not use 3.11, stick to 3.1.
  753.  
  754. Don't worry about most of what's in CLIP4WIN.DEF - just use it.
  755.  
  756. MS LINK uses the equivalent of RTLink's positional syntax.  If you need
  757. to use multiple lines, place a "+" character at the end of each line
  758. before the final line.
  759.  
  760. Whichever linker you use, list RDD's and most add-ons before Clip-4-Win's
  761. libraries, and if you list any of Clipper's libraries put them last.
  762.  
  763.  
  764.  
  765. Converting Box-Drawing
  766. ----------------------
  767.  
  768. See "Converting International Character Strings".
  769.  
  770.  
  771. Converting Browsers
  772. -------------------
  773.  
  774. Although you can use TBROWSE in Windows, it does not allow you to use
  775. the new facilities provided by Windows.  So you can't use proportional
  776. fonts, bitmaps, Windows scroll bars, etc.  Using WBROWSE instead of
  777. TBROWSE gives you many new features.
  778.  
  779. You can convert your TBROWSE code to WBROWSE largely by removing your
  780. entire stabilize loop.  It's now effectively internal to WBROWSE, and
  781. has some new code blocks e.g. KeyBlock, FindBlock and ScanBlock.  You
  782. may well want to set the Escape or other properties (instance variables).
  783.  
  784. You need to change from TBCOLUMNNEW() to WBCOLUMN{}, and TBROWSENEW()
  785. or TBROWSEDB() to WBROWSE{}.  See the comment in "Converting MAIN()"
  786. about #include "topclass.ch".
  787.  
  788. Note: WBROWSE{} needs more parameters than TBROWSEDB()/TBROWSENEW() and
  789.       in a different order, consistent with Windows.  WBCOLUMN{} allows
  790.       more parameters than TBCOLUMNNEW(), but the first 2 are enough to
  791.       start with.
  792.  
  793. If you use multi-line rows/headers, they're much easier to do using
  794. WBROWSE: for a column heading you use an array (one element for each
  795. line), and for a cell your column block only has to return a string with
  796. carriage return and linefeed (CHR(13) + CHR(10)) between lines.
  797.  
  798. You may have to control the vertical scroll bar yourself, e.g. for an
  799. array.  You can do this in your SkipBlock, as shown in the samples
  800. named below and in the HLP file.
  801.  
  802. Please see the WinHelp file HLP\WBROWSET.HLP and the many samples using
  803. WBROWSE.  These include SOURCE\WBROWDEF.PRG, the in-depth demonstration
  804. SOURCE\WBTDEMO.ZIP, and in SOURCE\OO\ there are BROW.PRG, MDIWBROW.PRG, 
  805. ODBCBROW.PRG and ODBCWBRO.PRG.
  806.  
  807. This is perhaps a suitable point to say just how powerful WBROWSE is.
  808. It really makes good use of the GUI environment.  For example, you can
  809. allow your user to re-size columns with the mouse, or re-arrange the
  810. columns by dragging and dropping them.  Build SOURCE\WBTDEMO.ZIP and
  811. see for yourself!
  812.  
  813. A special feature of WBROWSE is that you can use it in a dialog where
  814. a Windows LISTBOX control would otherwise be used.  By doing this you
  815. can overcome various limitations of list boxes, e.g. no right-aligned
  816. or centered items and serious problems handling more than 64K items.
  817.  
  818. If you want to do in-cell editing, as commonly done in spreadsheets such
  819. as Excel, SOURCE\WBTDEMO.ZIP has an example (CELLEDIT.PRG).
  820.  
  821.  
  822.  
  823. Converting ACHOICE() and MEMOEDIT()
  824. -----------------------------------
  825.  
  826. You can probably use SOURCE\ACHOICE.PRG and SOURCE\MEMOEDIT.PRG without
  827. any changes, but you can make your own version of the code if you need
  828. something different.  Note: the "user function" UDF's are not supported
  829. (the parameters are ignored).  If you really need something similar,
  830. see "Converting Data Entry Forms".
  831.  
  832.  
  833. Converting Popup Windows
  834. ------------------------
  835.  
  836. In each case, identify the underlying functionality.  Menus, browsers,
  837. and data entry forms are discussed separately.  That leaves things
  838. like informational/warning/error messages.  An easy way to implement
  839. these is to use a MessageBox().  You might also take a look to see if
  840. SOURCE\ALERT.PRG is suitable for your needs.
  841.  
  842.  
  843. Converting Data Entry Forms
  844. ---------------------------
  845.  
  846. There's a lot of detail in this section and the related sections
  847. "Converting Forms - An Example" and "Validation".  Please plan to read
  848. them all, and more than once!  Quite a few samples are discussed; you
  849. might find it worthwhile to print them for easier reference.
  850.  
  851. Data input screens normally need converting to dialogs.  If you have a
  852. multi-page form, you might like to use a tabbed dialog (class oTab).
  853. See HLP\OTABT.HLP and SOURCE\OTABDEMO.ZIP.
  854.  
  855. A dialog is special kind of window built from a template.  Clip-4-Win
  856. supports dynamic dialogs, where the template is created at runtime, as
  857. well as resource dialogs, which are constructed in advance using a
  858. resource editor and then stored in the EXE or in a DLL.
  859.  
  860. Dialogs contain other kinds of windows, called controls: push buttons,
  861. radio buttons, check boxes, list boxes, combo boxes, "static" text
  862. controls, group boxes, edit controls (input fields), and so on.
  863.  
  864. Don't be fooled - static controls are badly named: you can change the
  865. text at any time.  You can also use a static control to display an icon.
  866.  
  867. In case you discover owner-drawn buttons, these are also not brilliantly
  868. named: you can use them for just about anything you might want to do.
  869. If you want to know more, refer to the sources of information about
  870. Windows described previously.  You could also take a look at several of
  871. the classes provided by Clip-4-Win, which use owner-drawn buttons as a
  872. convenient way to acquire a rectangular area for their own use.  In the
  873. SOURCE\OO\CLASSES directory, see ARROWBTN.PRG, BITMAP.PRG, BUTTNBMP.PRG,
  874. PROGBAR.PRG and SPINBTN.PRG.
  875.  
  876. Most kinds of windows, including dialogs and controls, can be created
  877. with a variety of settings, called styles.  The styles that apply to
  878. most windows are the WS_* values defined in WINDOWS.CH (e.g. WS_VISIBLE
  879. and WS_DISABLED).  You can use these styles with controls and each type
  880. of control also has its own specific styles.  E.g. an edit control uses
  881. ES_PASSWORD to mean that it should display each input character using a
  882. replacement character (default '*').  Similarly, the presence or absence
  883. of ES_MULTILINE is the difference between a single-line input field and
  884. one you could use for a memo.
  885.  
  886. A resource editor is an easy way to choose the right styles for dialogs
  887. and controls for a number of reasons:
  888.  
  889.   -  a sensible default is provided
  890.  
  891.   -  you can choose from just the applicable alternatives
  892.  
  893.   -  you can change styles very easily (e.g. point and click)
  894.  
  895.   -  you see the effect of any changes immediately
  896.  
  897. You use a WBROWSE in a dialog by specifying an owner-drawn listbox.  Do
  898. this by choosing a listbox and then be sure to use styles:
  899.    LBS_OWNERDRAWVARIABLE, LBS_NOINTEGRALHEIGHT and also LBS_NOTIFY.
  900. The exact way you choose the style depends on the particular resource
  901. editor you use; typically you can use check boxes with names similar
  902. to the above.  For a non-resource (dynamic) dialog, just add the above
  903. values to typical ones for a listbox, e.g.
  904.    LBS_OWNERDRAWVARIABLE + LBS_NOINTEGRALHEIGHT + LBS_NOTIFY
  905.    + WS_TABSTOP + WS_CLIPCHILDREN + WS_VISIBLE + WS_CHILD
  906. (The "@ ID ... BLISTBOX" UDC will do this for you.)
  907.  
  908. If you want to have 3-D effects in a version of Windows that doesn't
  909. automatically supply them (i.e. Win3.1/3.11, Workgroups, OS/2, NT 3.x),
  910. you can use CTL3D.DLL.  You can do this as described in "Converting
  911. MAIN()", and your application's dialogs will then be given a 3-D look.
  912. The CREATE APPLICATION UDC and the WAppBase class default to enabling
  913. this 3-D look.
  914.  
  915. In Windows 95 and NT 4, you can either use the above 3-D effects or you
  916. can use the native effects, which have a slightly different appearance.
  917. To use the in-built 3-D, instead of using CTL3D.DLL you need to add 4
  918. (four) to each dialog style.  Hint: you may like to use:
  919.     #ifndef DS_3DLOOK
  920.     #define DS_3DLOOK 4
  921.     #endif
  922.  
  923. You can safely add this to dialogs for use with any version of Windows,
  924. as it's ignored by earlier versions.
  925.  
  926. In 95/NT4, you may want to specify a non-bold font for each dialog.
  927.  
  928. We intend to make the above easier in a future release of Clip-4-Win!
  929.  
  930. If you use the Borland Resource Workshop to design your dialogs, you
  931. may well choose one or more items specific to Borland (such as their
  932. bitmapped buttons).  If you do, you need to ship BWCC.DLL with your
  933. application and at runtime your application needs to load BWCC.DLL into
  934. memory before it uses any of the dialogs with Borland effects.
  935.  
  936. You can do this with CREATE APPLICATION just by using the optional
  937. clause BORLAND (or BWCC).
  938.  
  939. An OO application can use:
  940.     oApp:BWCC := .T.    // an ASSIGN method in WAppBase
  941.  
  942. You can load the DLL yourself like this:
  943.     BWCC(.T.)           // in SOURCE\APPMAIN.PRG
  944.  
  945. Before your application finally exits, it should free any DLL it has
  946. loaded.  This is partly to allow Windows to re-use the memory if it
  947. needs it, but also to let a different version of the DLL be loaded, in
  948. case you have an application that depends on a particular version.  To
  949. remove an unwanted DLL, see the description of DLLFREE in "Converting
  950. MAIN()".
  951.  
  952. Dialogs can be modal or modeless (non-modal).  A modal dialog is one
  953. your user must either finish or cancel; a modeless dialog lets your
  954. user switch to (or try to start or try to end) other activities within
  955. the same application.  The function call used to invoke the dialog is
  956. what determines whether a dialog is modal or modeless.
  957.  
  958. A modal dialog is usually application modal, which means the rest of
  959. the application is disabled including its menu (if any) and any other
  960. dialogs.  However, the user can still switch to other applications.
  961.  
  962. You can also use a system modal dialog.  If you do, the rest of Windows
  963. is disabled during the dialog!  You will probably never use a system
  964. modal dialog, but if you do be sure to test it very thoroughly.  Start
  965. by testing it as an ordinary application modal dialog, and only later
  966. add the style DS_SYSMODAL.
  967.  
  968. Converting modal DOS forms to modeless dialogs is discussed in the
  969. topic "Converting From Modal To Modeless".  It's not something to try
  970. to do if you're new to Windows programming.
  971.  
  972. If you used non-modal screens in DOS Clipper, keeping them non-modal in
  973. Windows is simple.  You may well have such modeless DOS screens if you
  974. used a DOS Clipper add-on such as ProVision:Windows, CLWindows, CUA
  975. Bausteine 3 or if you wrote your own event-driven system.  You may have
  976. been influenced by Clipper "gurus" such as Gilbert Chauvaux and Erik
  977. Wynn.  And you're probably finding this discussion tedious - sorry!
  978.  
  979. Designing pleasant-looking dialogs with a resource editor is reasonably
  980. easy.  It is considerably harder without a resource editor.
  981.  
  982. When you're designing a dialog, you're doing two things: deciding the
  983. user interface and the business logic associated with it.  These two
  984. are related, but many of the details affect only one or other of the
  985. two.  In particular, the position of each control is usually irrelevant
  986. to the logic.  This brings some advantages:
  987.  
  988.   -  you can design the dialogs fully, but write little code
  989.      (e.g. to see if your user likes the proposed look)
  990.  
  991.   -  design a dialog very quickly and then concentrate on getting the
  992.      logic right
  993.  
  994.   -  have some people who program and some who design dialogs (who need
  995.      not be developers)
  996.  
  997. Some of the commonest ways of using controls are discussed next, but
  998. first you need to know a little about handles and ID's.
  999.  
  1000. Every window on the screen, including each dialog and each control, has
  1001. a "handle" which is used by Windows to uniquely identify the window.
  1002. Handles are stored as Clipper numerics.  In fact, Windows also uses
  1003. handles for fonts, brushes, menus, pens, and other things.  Handles
  1004. your application creates are never zero.  Handles are allocated by
  1005. Windows when your application requests them, and are usually different
  1006. each time a program is run.
  1007.  
  1008. Each control in a dialog has a numeric (16-bit) ID, which you or the
  1009. resource editor choose.  Although it is possible to change ID's each
  1010. time an application is run, it is almost never done because it is so
  1011. rarely useful.  Instead, each ID is kept constant and the same value
  1012. is used in the resource file (if any) and at runtime.  The ID may also
  1013. be used to find the window handle of a control.
  1014.  
  1015. For example, if hDlg is the handle of a dialog and you have a typical OK
  1016. button, its ID is IDOK.  You can get the window handle of the OK button
  1017. using hCtrl := GetDlgItem(hDlg, IDOK).  You could disable it using
  1018. EnableWindow(GetDlgItem(hDlg, IDOK), .F.), and it would then be drawn
  1019. grey.
  1020.  
  1021. Similarly, you could disable a list box whose ID is ID_XXX using
  1022. EnableWindow(GetDlgItem(hDlg, ID_XXX), .F.).  You can do this sort of
  1023. thing with any of the standard controls.
  1024.  
  1025. Developers often set the ID's of controls which aren't referenced in
  1026. their code to -1 (in 16-bits this is the same as 65535).
  1027.  
  1028. You can convert from a control's window handle to its ID using
  1029. GetDlgCtrlID(hCtrl).
  1030.  
  1031. As well as the above ways to use control ID's at runtime, you use them
  1032. when creating a control and when associating an edit control with a
  1033. Clipper GET.  A detailed example follows, containing things like:
  1034.  
  1035.     @ ID 101 EDIT  AT 60,10  SIZE 24,14
  1036.     . . .
  1037.     @ DIALOG hDlg  ID 101  GET cZip PICTURE "@!"
  1038.  
  1039. When stored as a resource a dialog also has an ID, but this is only
  1040. used at runtime to find the dialog in the EXE/DLL.  A dialog ID may be
  1041. either a string or a (16-bit) numeric, which should not be zero.
  1042. E.g.
  1043.     CREATE DIALOG aDlg  RESOURCE "ZIP"
  1044.  
  1045. The way the Tab key moves around a dialog (the "tab order") is decided
  1046. by which controls have the WS_TABSTOP style and the order of those
  1047. controls within the dialog.  This is the order in the RC file or the
  1048. order in which the controls were created using "@ ... ID" or
  1049. AppendDialog().  If you're having trouble with the tab order of a
  1050. resource dialog, remember you can edit the RC file!
  1051.  
  1052. When the dialog is displayed, the first control with WS_TABSTOP is by
  1053. default given the input focus.
  1054.  
  1055. As far as the user interface is concerned, the commonest ways of using
  1056. controls are as follows.
  1057.  
  1058. You usually use a static text control instead of @ ... SAY, but a group
  1059. box is sometimes appropriate (it can have text as well as its rectangle).
  1060.  
  1061. As with menus, you can use "&" before a character to make it an
  1062. accelerator (hotkey).  This is useful in radio buttons, check boxes,
  1063. buttons and so on as the user can use a simple keystroke instead of the
  1064. mouse or having to tab around.  If you use an accelerator in static text
  1065. placed before an edit control, the input focus will move to the edit
  1066. control if the accelerator is used.
  1067.  
  1068. GETs which have only two valid inputs can probably become check boxes.
  1069.  
  1070. GETs which have a small number of different inputs can usually become
  1071. check boxes, radio buttons, or a list box.  A drop down list is
  1072. implemented using a combo box with the CBS_DROPDOWN or CBS_DROPDOWNLIST
  1073. style.
  1074.  
  1075. All your multi-character GETs are likely to end up as edit controls,
  1076. with most using @ ID ... GET or @ DIALOG ... GET.  These allow PICTURE
  1077. clauses.  See below for a discussion of validation.
  1078.  
  1079. Multi-line input (a memo) can use a multi-line edit control.  These
  1080. automatically support the mouse, clipboard, and so on.  If you want
  1081. the Enter key to start a new line (instead of terminating the dialog),
  1082. use the ES_WANTRETURN style.
  1083.  
  1084. If you think you have so much to input within a single dialog that you
  1085. need scroll bars, try using a tabbed dialog (property sheet).  See
  1086. HLP\OTABT.HLP and SOURCE\OTABDEMO.ZIP.
  1087.  
  1088. As well as deciding the appearance of a dialog, you have to decide the
  1089. details of the code (your business logic).  This gets much easier as
  1090. you gain experience - but the first time can be rather daunting!
  1091.  
  1092. It helps to understand the major states a dialog can be in and the
  1093. important events that affect a dialog:
  1094.  
  1095. (a) Initially, the dialog is stored as a resource or has yet to be
  1096. created dynamically.
  1097.  
  1098. (b) The resource is fetched into memory from the EXE or a DLL, or your
  1099. application builds a dynamic dialog.  At this stage, no window handles
  1100. exist.  Any problem with a resource dialog (e.g. not in the EXE or you
  1101. use an incorrect ID) causes an error return.  For a modal dialog, e.g.
  1102. using SHOW DIALOG or DialogBox(), the error code is -1.  For a modeless
  1103. dialog, e.g. using SHOW DIALOG or CreateDialog(), the error code is 0.
  1104.  
  1105. (c) Windows/Clip-4-Win create the dialog window and the controls.
  1106.  
  1107. (d) If no error occurred, the dialog is made visible.
  1108.  
  1109. (e) Your user interacts with the dialog.
  1110.  
  1111. (f) The dialog is ended.  The windows are destroyed and the handles
  1112. cease to be valid.
  1113.  
  1114. You can write code to influence almost any of the above.
  1115.  
  1116. Windows keeps your application informed about the things that occur by
  1117. sending it special messages (some of the WM_* values defined in
  1118. WINDOWS.CH).  Your application can perform some of its own processing
  1119. as it receives these messages.  You very rarely handle more than a few
  1120. of the messages.  The ones you commonly handle are very important and
  1121. are discussed next.
  1122.  
  1123. The messages are sent by Windows to your "dialog procedure", a function
  1124. to process them.  Clip-4-Win provides several built-in dialog procedures,
  1125. e.g. in the WDialog class, and in the support code for SHOW DIALOG.  You
  1126. can also write your own dialog procedures.  E.g. SOURCE\DROP.PRG and
  1127. SOURCE\USERSRES\USERSRES.PRG.
  1128.  
  1129. Note: this section discusses dialog procedures, not "window procedures",
  1130. because in Clip-4-Win you will probably never need to use a window
  1131. procedure.  Just be aware that something more generic exists and applies
  1132. to all kinds of windows.
  1133.  
  1134. You need to know at least a little about Windows messages, because they
  1135. are so incredibly important in making things work.  You don't need to
  1136. learn what all the different messages do, but you do need to learn what
  1137. a few do and when they occur.
  1138.  
  1139. You can imagine messages as being like keystrokes in DOS Clipper, and
  1140. a dialog procedure as being like a SET KEY procedure.  Alternatively,
  1141. you can say messages are like keystrokes in DOS and a dialog procedure
  1142. is like the "user function" UDF used with ACHOICE, DBEDIT and MEMOEDIT.
  1143.  
  1144. Each message consists of four values:
  1145.  
  1146.   -  the handle of the receiving window (or dialog or control: remember
  1147.      they're all kinds of windows), often called hWnd, hDlg or hCtrl
  1148.  
  1149.   -  the message (a 16-bit number, one of the WM_* values in WINDOWS.CH),
  1150.      usually called nMsg or msg
  1151.  
  1152.   -  a 16-bit additional parameter, usually called nwParam or wparam
  1153.  
  1154.   -  a 32-bit additional parameter, usually called nlParam or lparam
  1155.  
  1156. These are all Clipper numerics in Clip-4-Win.  Unused values are zero.
  1157.  
  1158. The Clip-4-Win Application Classes use methods with names derived from
  1159. message names, e.g. OnCommand() for WM_COMMAND, to make things easy to
  1160. find.
  1161.  
  1162. The two most important messages are:
  1163.  
  1164.   -  WM_INITDIALOG  nwParam is the handle of the control to get the
  1165.                         input focus (you usually ignore this)
  1166.                     nlParam can be ignored
  1167.  
  1168.   -  WM_COMMAND     nwParam is the ID of the control sending the
  1169.                         message e.g. IDOK for an OK button
  1170.                     nlParam holds two values you can access as:
  1171.                         C4W_LoWord(nlParam) = the control's handle
  1172.                         C4W_HiWord(nlParam) = the notification code
  1173.                                               (normally ignore this)
  1174.  
  1175. Despite their name, dialog procedures must return a value each time
  1176. they are called.  The valid return values depend on the message, but
  1177. are almost always zero (0) if you want the default action to occur
  1178. or one (1) if you do not want the default (i.e. you handled the message
  1179. yourself).  Note: WM_INITDIALOG is different!  Read on...
  1180.  
  1181. You usually want to perform some actions as soon as the dialog and
  1182. controls exist, but before they are made visible.  These are actions
  1183. such as positioning database files and loading default values into input
  1184. fields (edit controls).  You do this by using the ON INIT clause of SHOW
  1185. DIALOG, or by handling the WM_INITDIALOG message, or by using the
  1186. OnInitDialog() method of class WDialog.
  1187.  
  1188. If you handle WM_INITDIALOG, you should return 1 unless you set the
  1189. input focus - in which case return 0.  A return value of 1 means that
  1190. Windows will set the input focus to the first control it can that has
  1191. the WS_TABSTOP setting.  SHOW DIALOG and method WDialog:OnInitDialog()
  1192. both return 1 by default.
  1193.  
  1194. After WM_INITDIALOG, quite a number of different messages can be sent
  1195. during the life of a dialog, but the only type you're likely to need are
  1196. WM_COMMAND messages, as explained below.
  1197.  
  1198. In many cases, the only processing you need to do during the user's
  1199. interaction with a dialog can be carried out by VALID blocks and/or by
  1200. handling the requests to end the dialog.  For more information see the
  1201. "Validation" section, which discusses WHENs and such things as using a
  1202. status message area.
  1203.  
  1204. In Windows, your users are supposed to use the Tab key to move between
  1205. input fields.  The Enter key is meant to signal that the dialog is
  1206. complete.
  1207.  
  1208. However, if your users want to use Enter to move between input fields
  1209. (e.g. to do fast data entry), you can add extra code to allow it.  All
  1210. you have to do is to check whether the current control is the OK button
  1211. (or maybe the last input field on the form).  If not, use SetFocus() to
  1212. move the input focus to the relevant input field.  For an example, see
  1213. SOURCE\CONTRIB\ENTER.ZIP or SOURCE\LOGON.PRG.
  1214.  
  1215. By default, dialogs are ended when the user selects Close (if you use
  1216. the dialog style WS_SYSMENU) or presses Enter, Escape or Alt+F4.
  1217.  
  1218. Your application can decide not to close a dialog, e.g. because the
  1219. form is not complete, or your application can carry out some work of
  1220. its own before closing a dialog.  To do this you handle the WM_COMMAND
  1221. message and the ID values you're interested in (as stated above, the ID
  1222. is in the nwParam).  For Enter, the ID is usually IDOK.  For a cancel
  1223. of any kind you usually want IDCANCEL.
  1224.  
  1225. Clip-4-Win's "@ ID ... BUTTON" UDC makes it very easy to handle these
  1226. WM_COMMAND messages, because it arranges to call a function you name
  1227. if the message is sent.
  1228. E.g.
  1229.    @ ID IDOK     BUTTON MyOkFunc() TEXT "&Ok"      AT 90,5   SIZE 37,12
  1230.    @ ID IDCANCEL BUTTON CancelFn() TEXT "&Cancel"  AT 90,19  SIZE 37,12
  1231.  
  1232. the above buttons result in function MyOkFunc() or CancelFn() being
  1233. called if the user presses a button.  The appropriate function is also
  1234. called if the user tries to end the dialog.
  1235.  
  1236. If a user tries to end a dialog or presses Enter, IDOK/IDCANCEL are
  1237. nearly always sent.  About the only way to prevent them ever being sent
  1238. is to have buttons with the appropriate ID's and disable the buttons
  1239. using EnableWindow().  If you don't want your user to see the buttons,
  1240. either hide them using ShowWindow() with SW_HIDE or put the buttons
  1241. outside the visible area of the dialog.
  1242.  
  1243. See SOURCE\LOGON.PRG for a sample.  It also allows movement between
  1244. input fields using the Enter key, as does SOURCE\CONTRIB\ENTER.ZIP.
  1245.  
  1246. If the default action is not appropriate, you can readily handle these
  1247. WM_COMMAND messages yourself, no matter which programming style you like
  1248. to use.  For example, if you like UDC's you can use @ ID...BUTTON, which
  1249. allows you to specify a function to be called if that button is pressed.
  1250. You can of course use your own dialog procedure and check for the
  1251. WM_COMMAND message signifying the button press.  In addition, the WDialog
  1252. class not only has the OnCommand() method, corresponding to WM_COMMAND,
  1253. but also the methods Ok() and Cancel().  The default WDialog:OnCommand()
  1254. automatically evaluates any code block associated with the buttons, or
  1255. else calls the Ok()/Cancel() method.  Consequently, you usually provide
  1256. a code block for the button or subclass WDialog and override the Ok()
  1257. and/or Cancel() method.  For a button whose ID is not IDOK or IDCANCEL,
  1258. you usually specify a code block or override OnCommand().
  1259.  
  1260. If you prefer to have a Save button, that's no problem.  You can still
  1261. use IDOK for it (but you don't have to do so).
  1262.  
  1263.  
  1264.  
  1265. Converting Forms - An Example
  1266. -----------------------------
  1267.  
  1268. Here's a simple DOS screen:
  1269.  
  1270. LOCAL cZip,            ;    // Zip code variable
  1271.       cState                //    State code variable
  1272.  
  1273. . . .
  1274.     SCROLL()
  1275.     cZip   := SPACE(5)
  1276.     cState := "  "
  1277.  
  1278.     @ 10,0 SAY "Enter zip code" GET cZip PICTURE "@!"
  1279.     READ
  1280.     IF LastKey() == 27
  1281.         EXIT
  1282.     ENDIF
  1283.  
  1284.     @ 12,0 SAY "State that zip code resides is " + Zip2St(cZip)
  1285.  
  1286.     @ 14,0 SAY "Now enter state code" GET cState PICTURE "!!" ;
  1287.         VALID IsState(cState)
  1288.     READ
  1289.     IF LastKey() == 27
  1290.         EXIT
  1291.     ENDIF
  1292. . . .
  1293.  
  1294.  
  1295. The closest Clip-4-Win equivalent is probably the heavily UDC-based
  1296. version below.  It doesn't use resources, so originally deciding the
  1297. positions of the controls could have been quite painful.  Also, since
  1298. it doesn't use resources, it takes a while to build the dialog. Lastly,
  1299. it's using objects, which slows it down some more.  These are small
  1300. overheads on all but the slowest of systems, but maybe your clients
  1301. have slow systems - or maybe you'd rather avoid UDC's and/or objects.
  1302. If so, further examples follow.
  1303.  
  1304. #include "windows.ch"
  1305. #include "commands.ch"
  1306.  
  1307. . . .
  1308.     cZip   := SPACE(5)
  1309.     cState := "  "
  1310.  
  1311.     CREATE DIALOG oDlg  TITLE "ZIP"  AT 50,50  SIZE 100,40  IN oWnd
  1312.  
  1313.     @ ID 100 SAY "Enter zip code"   AT 10,12  SIZE 50,14  IN oDlg
  1314.     @ ID 101 GET cZip PICTURE "@!"  AT 60,10  SIZE 24,14  IN oDlg
  1315.  
  1316.     SHOW DIALOG oDlg  RESULT nRet
  1317.     IF nRet == IDCANCEL
  1318.         EXIT
  1319.     ENDIF
  1320.  
  1321.  
  1322.     CREATE DIALOG oDlg  TITLE "State"  AT 50,50  SIZE 130,60  IN oWnd
  1323.  
  1324.     @ ID 100 SAY "State that zip code resides is " + Zip2St(cZip) ;
  1325.         AT 10,10  SIZE 110,14  IN oDlg
  1326.  
  1327.     @ ID 101 SAY "Now enter state code"  AT 10,32  SIZE 80,14  IN oDlg
  1328.     @ ID 102 GET cState PICTURE "!!"     AT 90,30  SIZE 18,14  IN oDlg ;
  1329.         VALID IsState(cState)
  1330.  
  1331.     SHOW DIALOG oDlg  RESULT nRet
  1332.     IF nRet == IDCANCEL
  1333.         EXIT
  1334.     ENDIF
  1335. . . .
  1336.  
  1337.  
  1338. Next, let's try the same thing without objects.  Using Clip-4-Win's
  1339. UDC's it's quite similar:
  1340.  
  1341. #include "windows.ch"
  1342. #include "dialog.ch"
  1343. #define    NO_C4WCLASS
  1344. #include "commands.ch"
  1345.  
  1346. STATIC cZip,            ;    // Zip code variable
  1347.        cState                //    State code variable
  1348.  
  1349. . . .
  1350. SET SCOREBOARD OFF
  1351. SET CONFIRM ON
  1352.  
  1353. . . .
  1354. LOCAL GetList, aDlg, nRet
  1355. . . .
  1356.     cZip   := SPACE(5)
  1357.     cState := "  "
  1358.  
  1359.     GetList := {}
  1360.     CREATE DIALOG aDlg  TITLE "ZIP"  AT 50,50  SIZE 100,40
  1361.  
  1362.     @ ID 100 SAY "Enter zip code"  AT 10,12  SIZE 50,14
  1363.     @ ID 101 EDIT                  AT 60,10  SIZE 24,14
  1364.  
  1365.     SHOW DIALOG aDlg  RESULT nRet ;
  1366.         ON INIT {|hDlg| ZipDlg(hDlg, GetList)}
  1367.     IF nRet == IDCANCEL
  1368.         EXIT
  1369.     ENDIF
  1370.  
  1371.  
  1372.     GetList := {}
  1373.     CREATE DIALOG aDlg  TITLE "State"  AT 50,50  SIZE 130,60
  1374.  
  1375.     @ ID 100 SAY "State that zip code resides is " + Zip2St(cZip) ;
  1376.         AT 10,10  SIZE 110,14
  1377.  
  1378.     @ ID 101 SAY "Now enter state code"  AT 10,32  SIZE 80,14
  1379.     @ ID 102 EDIT                        AT 90,30  SIZE 18,14
  1380.  
  1381.     SHOW DIALOG aDlg  RESULT nRet ;
  1382.         ON INIT {|hDlg| StateDlg(hDlg, GetList)}
  1383.     IF nRet == IDCANCEL
  1384.         EXIT
  1385.     ENDIF
  1386. . . .
  1387.  
  1388. STATIC FUNCTION ZipDlg(hDlg, GetList)
  1389. @ DIALOG hDlg  ID 101  GET cZip PICTURE "@!"
  1390. RETURN NIL
  1391.  
  1392. STATIC FUNCTION StateDlg(hDlg, GetList)
  1393. @ DIALOG hDlg  ID 102  GET cState PICTURE "!!" VALID IsState(cState)
  1394. RETURN NIL
  1395.  
  1396.  
  1397. The main changes are that GetList and two small functions have been
  1398. added.  These functions are so that the edit controls can be used as
  1399. GETs.  This was happening "behind the scenes" in the version using
  1400. objects (in the WDialog and WGet classes).
  1401.  
  1402. Incidentally, the above example makes use of a small convenience:
  1403. "@ ... ID" defaults to "IN aDlg" (or "IN self" for an object oriented
  1404. program).
  1405.  
  1406.  
  1407. Let's explore the problem some more, and consider using resources.
  1408.  
  1409. It's a simple example, but has a common requirement: one of the fields
  1410. is only known at runtime.  That means the resources must be modified
  1411. at runtime.
  1412.  
  1413. Here's the version using resources and UDC's:
  1414.  
  1415. #include "windows.ch"
  1416. #include "dialog.ch"
  1417. #define    NO_C4WCLASS
  1418. #include "commands.ch"
  1419.  
  1420. STATIC cZip,            ;    // Zip code variable
  1421.        cState                //    State code variable
  1422.  
  1423. . . .
  1424. SET SCOREBOARD OFF
  1425. SET CONFIRM ON
  1426.  
  1427. . . .
  1428. LOCAL GetList, aDlg, nRet
  1429. . . .
  1430.     cZip   := SPACE(5)
  1431.     cState := "  "
  1432.  
  1433.     GetList := {}
  1434.     CREATE DIALOG aDlg  RESOURCE "ZIP"
  1435.  
  1436.     SHOW DIALOG aDlg  RESULT nRet ;
  1437.         ON INIT {|hDlg| ZipDlg(hDlg, GetList)}
  1438.  
  1439.     IF nRet == IDCANCEL
  1440.         EXIT
  1441.     ENDIF
  1442.  
  1443.     GetList := {}
  1444.     CREATE DIALOG aDlg  RESOURCE "State"
  1445.  
  1446.     SHOW DIALOG aDlg  RESULT nRet ;
  1447.         ON INIT {|hDlg| StateDlg(hDlg, GetList)}
  1448.  
  1449.     IF nRet == IDCANCEL
  1450.         EXIT
  1451.     ENDIF
  1452. . . .
  1453.  
  1454. STATIC FUNCTION ZipDlg(hDlg, GetList)
  1455. @ DIALOG hDlg  ID 101  GET cZip PICTURE "@!"
  1456. RETURN NIL
  1457.  
  1458. STATIC FUNCTION StateDlg(hDlg, GetList)
  1459. SetDlgItemText(hDlg, 100, "State that zip code resides is " + Zip2St(cZip))
  1460. @ DIALOG hDlg  ID 102  GET cState PICTURE "!!" VALID IsState(cState)
  1461. RETURN NIL
  1462.  
  1463.  
  1464. Things to notice: various @ SAY's have disappeared (they're in the
  1465. resource file); cZip and cState are STATIC; and we modify the text of
  1466. a static text control using SetDlgItemText().  The control's ID (100)
  1467. better be the same in both the Clipper code and the resource file!
  1468. You can do this easily, because from a resource editor you can get an
  1469. #include file with #defines suitable for Clipper.
  1470.  
  1471. The resource file with the two dialogs (zip and state) follows.  Bear
  1472. in mind that you don't have to edit it yourself: that's what a resource
  1473. editor does.
  1474.  
  1475. However, for the curious, here are some details: groups of four numbers
  1476. are left, top, width, height (in that order); a fifth number preceding
  1477. a group of four numbers is the ID; vertical bar ("|") means binary OR
  1478. (think of it as "+" in Clipper); LTEXT is left-aligned text; EDITTEXT
  1479. is an edit control (input field).  If you really want to know more, try
  1480. experimenting with a resource editor and/or consult some of the sources
  1481. of information about Windows (such as MSDN or the Windows SDK).
  1482.  
  1483. #include "windows.ch"
  1484.  
  1485. zip DIALOG 50,50,100,40
  1486. STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION
  1487. CAPTION "ZIP"
  1488. BEGIN
  1489.     LTEXT "Enter zip code", 100, 10,12,50,14
  1490.     EDITTEXT 101, 60,10,24,14
  1491. END
  1492.  
  1493. state DIALOG 50,50,130,60
  1494. STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION
  1495. CAPTION "State"
  1496. BEGIN
  1497.     LTEXT "", 100, 10,10,110,14
  1498.     LTEXT "Now enter state code", 101, 10,32,80,14
  1499.     EDITTEXT 102, 90,30,18,14
  1500. END
  1501.  
  1502.  
  1503. Finally, for those who want to program much closer to Windows, here's
  1504. a version for you, where the processing of OK and Cancel can be clearly
  1505. seen.  As this version is avoiding UDCs, it does not need the #include
  1506. "commands.ch" used in the other examples.
  1507.  
  1508. #define    WIN_WANT_ALL
  1509. #include "windows.ch"
  1510. #include "dialog.ch"
  1511.  
  1512. . . .
  1513. SET SCOREBOARD OFF
  1514. SET CONFIRM ON
  1515.  
  1516. . . .
  1517.     cZip   := SPACE(5)
  1518.     cState := "  "
  1519.  
  1520.     GetList := {}
  1521.     nRet := DialogBox( , "ZIP", , ;
  1522.                       {|hDlg, nMsg, nwParam, nlParam| ;
  1523.                        ZipDlg(GetList, hDlg, nMsg, nwParam, nlParam)})
  1524.     IF nRet == IDCANCEL
  1525.         EXIT
  1526.     ENDIF
  1527.  
  1528.  
  1529.     GetList := {}
  1530.     nRet := DialogBox( , "state", , ;
  1531.                       {|hDlg, nMsg, nwParam, nlParam| ;
  1532.                        StateDlg(GetList, hDlg, nMsg, nwParam, nlParam)})
  1533.     IF nRet == IDCANCEL
  1534.         EXIT
  1535.     ENDIF
  1536. . . .
  1537.  
  1538. STATIC FUNCTION ZipDlg(GetList, hDlg, nMsg, nwParam, nlParam)
  1539. DO CASE
  1540. CASE nMsg == WM_INITDIALOG
  1541.     @ DIALOG hDlg  ID 101  GET cZip PICTURE "@!"
  1542.     RETURN 1
  1543.  
  1544. CASE nMsg == WM_COMMAND
  1545.     DO CASE
  1546.     CASE nwParam == IDOK
  1547.         IF IsDialogOK(hDlg, nwParam)
  1548.             EndDialog(hDlg, nwParam)
  1549.             RETURN 1
  1550.         ENDIF
  1551.     CASE nwParam == IDCANCEL
  1552.         CANCEL DIALOG hDlg
  1553.         EndDialog(hDlg, nwParam)
  1554.         RETURN 1
  1555.     ENDCASE
  1556. ENDCASE
  1557. RETURN 0
  1558.  
  1559.  
  1560. STATIC FUNCTION StateDlg(GetList, hDlg, nMsg, nwParam, nlParam)
  1561. DO CASE
  1562. CASE nMsg == WM_INITDIALOG
  1563.     SetDlgItemText(hDlg, 100, "State that zip code resides is " + Zip2St(cZip))
  1564.     @ DIALOG hDlg  ID 102  GET cState PICTURE "!!" VALID IsState(cState)
  1565.     RETURN 1
  1566.  
  1567. CASE nMsg == WM_COMMAND
  1568.     DO CASE
  1569.     CASE nwParam == IDOK
  1570.         IF IsDialogOK(hDlg, nwParam)
  1571.             EndDialog(hDlg, nwParam)
  1572.             RETURN 1
  1573.         ENDIF
  1574.     CASE nwParam == IDCANCEL
  1575.         CANCEL DIALOG hDlg
  1576.         EndDialog(hDlg, nwParam)
  1577.         RETURN 1
  1578.     ENDCASE
  1579. ENDCASE
  1580. RETURN 0
  1581.  
  1582.  
  1583.  
  1584. Validation
  1585. ----------
  1586.  
  1587. Windows introduces some new complications with input validation as well
  1588. as the ability for your user to use the mouse to move around your form
  1589. in any order.
  1590.  
  1591. When an edit control (or any window) is given the input focus it is
  1592. sent a WM_SETFOCUS message, and when it loses the focus it receives a
  1593. WM_KILLFOCUS message.  In general, no message is sent to ask whether
  1594. the control is willing to receive or lose the focus.  Focus can be
  1595. going to a window/control in the current application or a different
  1596. application.
  1597.  
  1598. Clip-4-Win evaluates any WHEN block when a WM_SETFOCUS arrives, and
  1599. it evaluates any VALID block when a WM_KILLFOCUS arrives.  This is
  1600. very close to DOS Clipper's behaviour.
  1601.  
  1602. Bear in mind that your user can move around using the mouse and may be
  1603. able to signal a form is complete at any time.  Consequently, it's a
  1604. good idea to do relatively little validation on each input, and do the
  1605. rest only when/if the user does signal the form is (supposedly) complete.
  1606.  
  1607. You can enable/disable controls at any time, using EnableWindow(), and
  1608. this may well help both you and your users.  For example, a disabled
  1609. Save button will appear grayed and cannot be pressed.
  1610.  
  1611. It's much better to disable a control instead of using a WHEN that
  1612. prevents the user accessing the control.  This is because a user can
  1613. see that a control is disabled, but gets no visual feedback from a
  1614. control with a WHEN until trying to move to it - only to find it's not
  1615. allowed.  If you use a status message area, you might like to use WHENs
  1616. that set the status message and then return TRUE.
  1617.  
  1618. One thing you might do in a VALID which fails validation, is to prompt
  1619. your user in some way.  This is likely to cause further focus changes.
  1620.  
  1621. Similarly, using ALERT() or MessageBox() is going to cause more focus
  1622. changes, so more messages will be sent.  You can easily cause a "storm"
  1623. of messages which either confuse you (or your code), or cause the stack
  1624. to overflow.
  1625.  
  1626. If you want to know the messages that are being sent to a dialog, avoid
  1627. anything that causes further messages, i.e. avoid ALERT and MessageBox().
  1628. Instead, write the messages out to a file, or another window, or to a
  1629. status area, or even use SetWindowText() to change a window's caption.
  1630.  
  1631. SOURCE\DBG.PRG logs messages to an array which can be examined at
  1632. any later time.  A debug window is provided by SOURCE\DBWIN2.ZIP and
  1633. can be accessed using the OutputDebugString() Windows API function
  1634. (in SOURCE\WINAPI\OUTDEBUG.PRG) and the UDC's DbgTrace(), $ and $$
  1635. defined by INCLUDE\WINDOWS.CH if you compile with /dWIN_DBWIN
  1636. (or use #define WIN_DBWIN).
  1637.  
  1638. Note: The Windows API function MessageBox() does not always restore the
  1639. focus properly, so if you use MessageBox() you should program around
  1640. this.  E.g.
  1641.    LOCAL hFocus
  1642.    . . .
  1643.    hFocus := GetFocus()
  1644.    nRet := MessageBox(...)
  1645.    SetFocus(hFocus)
  1646.  
  1647. Overall, you should write your VALIDs to be able to be evaluated more
  1648. than once and to be cautious about focus changes.
  1649.  
  1650. Here's part of a DOS form (see SOURCE\FORMDOS.PRG):
  1651.  
  1652. mName      := SPACE(35)
  1653. mFTName    := SPACE(35)
  1654. mAddr_1    := SPACE(35)
  1655. mAddr_2    := SPACE(20)
  1656. mAddr_3    := SPACE(20)
  1657. mAddr_4    := SPACE(15)
  1658. mAddr_5    := SPACE(10)
  1659. mContact   := SPACE(25)
  1660. mTelephone := SPACE(15)
  1661. mFax       := SPACE(15)
  1662.  
  1663. DO WHILE .T.
  1664.    CLEAR GETS
  1665.    CLS
  1666.    @  3,55 SAY "[ Add Contact ]"
  1667.    @  5,13 SAY    "Name        : " GET mName      PICTURE '@!' valid genval(!empty(mName),"Need a Name Please")
  1668.    @  6,13 SAY    "Address 1   : " GET mAddr_1    PICTURE '@!' valid genval(!empty(mAddr_1),"Need an Address Please")
  1669.    @  7,13 SAY    "Address 2   : " GET mAddr_2    PICTURE '@!' valid genval(!empty(mAddr_2),"Need an Address Please")
  1670.    @  8,13 SAY    "Address 3   : " GET mAddr_3    PICTURE '@!' valid genval(!empty(mAddr_3),"Need an Address Please")
  1671.    @  9,13 SAY    "Address 4   : " GET mAddr_4    PICTURE '@!'
  1672.    @ 10,13 SAY    "Post Code   : " GET mAddr_5    PICTURE '@!'
  1673.    @ 11,13 SAY    "Contact     : " GET mContact   PICTURE '@!' valid genval(!empty(mContact),"Need a Contact Please")
  1674.    @ 12,13 SAY    "Telephone   : " GET mTelephone PICTURE '@!' valid genval(!empty(mTelephone),"Need a Telephone Number Please")
  1675.    @ 13,13 SAY    "Fax No      : " GET mFax       PICTURE '@!'
  1676.    @ 14,13 SAY    "FT Name     : " GET mFTName    PICTURE '@!' valid genval(!empty(mFTName),"Need a Name for Financial Transactions Please")
  1677.    READ
  1678.    if lastkey() == 27
  1679.       EXIT
  1680.    ENDIF
  1681.  
  1682.    . . . update . . .
  1683. ENDDO
  1684. . . .
  1685.  
  1686. static function genval(lOk, cMsg)
  1687. if !lOk
  1688.     @ 24,10 CLEAR TO 24,70
  1689.     @ 24,10 SAY cMsg
  1690.     tone(523.3,0.5)
  1691. endif
  1692. return lOk
  1693.  
  1694.  
  1695. Below is a close translation of the main loop and some validation using
  1696. Clip-4-Win.  Note how the code in function OnOk() is simplified by
  1697. using consecutively numbered edit control ID's.  Also, note how easily
  1698. you can use a status area placed at the bottom of the dialog.
  1699. (See SOURCE\FORMWIN.PRG).
  1700.  
  1701. . . . #includes, #define NO_C4WCLASS, SET etc. as before . . .
  1702.  
  1703. LOCAL GetList, aDlg, nRet
  1704. . . .
  1705. DO WHILE .T.
  1706.    CLEAR GETS
  1707.    CREATE DIALOG aDlg  TITLE "Add Contact"  AT 50,10 SIZE 220,160
  1708.    @ ID 100 SAY    "Name: "         AT 10,12  SIZE 45,10
  1709.    @ ID 210 EDIT                    AT 48,10  SIZE 105,14
  1710.    @ ID 101 SAY    "Address 1: "    AT 10,28  SIZE 45,10
  1711.    @ ID 211 EDIT                    AT 48,26  SIZE 105,14
  1712.    @ ID 102 SAY    "Address 2: "    AT 10,44  SIZE 45,10
  1713.    @ ID 212 EDIT                    AT 48,42  SIZE 60,14
  1714.    @ ID 103 SAY    "Address 3: "    AT 10,60  SIZE 45,10
  1715.    @ ID 213 EDIT                    AT 48,58  SIZE 60,14
  1716.    @ ID 104 SAY    "Address 4: "    AT 10,76  SIZE 45,10
  1717.    @ ID 214 EDIT                    AT 48,74  SIZE 45,14
  1718.    @ ID 105 SAY    "Post Code: "    AT 120,76  SIZE 35,10
  1719.    @ ID 215 EDIT                    AT 158,74  SIZE 35,14
  1720.    @ ID 106 SAY    "Contact: "      AT 10,92  SIZE 45,10
  1721.    @ ID 216 EDIT                    AT 48,90  SIZE 75,14
  1722.    @ ID 107 SAY    "Telephone: "    AT 10,108  SIZE 45,10
  1723.    @ ID 217 EDIT                    AT 48,106  SIZE 45,14
  1724.    @ ID 108 SAY    "Fax No: "       AT 120,108 SIZE 35,10
  1725.    @ ID 218 EDIT                    AT 158,106  SIZE 45,14
  1726.    @ ID 109 SAY    "FT Name: "      AT 10,124  SIZE 45,10
  1727.    @ ID 219 EDIT                    AT 48,122  SIZE 105,14
  1728.  
  1729.    // here's a status (message) area:
  1730.    @ ID 300 SUNKENFRAME             AT  3,145  SIZE 214,13
  1731.    @ ID 301 SAY    ""               AT  5,147  SIZE 190,10
  1732.  
  1733.    // dialogs behave as if this button always exists:
  1734.    // (let's use it to do extra validation)
  1735.    @ ID IDOK BUTTON OnOk(GetList)
  1736.  
  1737.    SHOW DIALOG aDlg  RESULT nRet  ON INIT {|hDlg| FormInit(hDlg, GetList)}
  1738.  
  1739.    if nRet == IDCANCEL
  1740.       EXIT
  1741.    ENDIF
  1742.  
  1743.    . . . update . . .
  1744. ENDDO
  1745. . . .
  1746.  
  1747. static function FormInit(hDlg, GetList)
  1748. @ DIALOG hDlg ID 210 GET mName      PICTURE '@!' valid genval(!empty(mName),"Need a Name Please", hDlg)
  1749. @ DIALOG hDlg ID 211 GET mAddr_1    PICTURE '@!' valid genval(!empty(mAddr_1),"Need an Address Please", hDlg)
  1750. @ DIALOG hDlg ID 212 GET mAddr_2    PICTURE '@!' valid genval(!empty(mAddr_2),"Need an Address Please", hDlg)
  1751. @ DIALOG hDlg ID 213 GET mAddr_3    PICTURE '@!' valid genval(!empty(mAddr_3),"Need an Address Please", hDlg)
  1752. @ DIALOG hDlg ID 214 GET mAddr_4    PICTURE '@!'
  1753. @ DIALOG hDlg ID 215 GET mAddr_5    PICTURE '@!'
  1754. @ DIALOG hDlg ID 216 GET mContact   PICTURE '@!' valid genval(!empty(mContact),"Need a Contact Please", hDlg)
  1755. @ DIALOG hDlg ID 217 GET mTelephone PICTURE '@!' valid genval(!empty(mTelephone),"Need a Telephone Number Please", hDlg)
  1756. @ DIALOG hDlg ID 218 GET mFax       PICTURE '@!'
  1757. @ DIALOG hDlg ID 219 GET mFTName    PICTURE '@!' valid genval(!empty(mFTName),"Need a Name for Financial Transactions Please", hDlg)
  1758. return nil
  1759.  
  1760.  
  1761. static function OnOk(GetList, hDlg)
  1762. local    aRequired := {.t., .t., .t., .t., .f., .f., .t., .t., .f., .t.}
  1763. local    i, n := len(GetList), oGet
  1764.  
  1765. // check all required fields have been entered
  1766. for i = 1 to n
  1767.     oGet = GetList[i]
  1768.     if !oGet:BadDate() .and. oGet:Changed
  1769.         oGet:Assign()
  1770.     endif
  1771.     if aRequired[i]
  1772.         if empty(oGet:VarGet())
  1773.             // force input focus to this one!
  1774.             // Note: the ID's count from 210...
  1775.             SetFocus(GetDlgItem(hDlg, 210 + i - 1))
  1776.             exit
  1777.         endif
  1778.     endif
  1779. next i
  1780.  
  1781. return nil
  1782.  
  1783.  
  1784. static function genval(lOk, cMsg, hDlg)
  1785. SetDlgItemText(hDlg, 301, "")
  1786. if !lOk
  1787.     SetDlgItemText(hDlg, 301, cMsg)
  1788.     tone(523.3,0.5)
  1789. endif
  1790. return lOk
  1791.  
  1792.  
  1793.  
  1794. Converting Custom GET Readers
  1795. -----------------------------
  1796.  
  1797. Custom GET readers need converting to appropriate things in Windows.
  1798. These include check boxes, radio buttons, and list boxes / browsers.
  1799. If necessary, really unusual controls can be implemented, e.g. using
  1800. owner-drawn or subclassed controls, but the consistent look and feel
  1801. encouraged by Windows means you should avoid creating special controls.
  1802.  
  1803. Many controls send notification messages as the user interacts with
  1804. them, and these provide another chance for special requirements to be
  1805. implemented.  Notification messages are WM_COMMAND messages with a
  1806. notification code in C4W_HiWord(nlParam).  For example, edit controls
  1807. send WM_COMMAND with the EN_* values (defined in WINDOWS.CH), and
  1808. list boxes send WM_COMMAND with LBN_* notification codes.
  1809.  
  1810. WBROWSE gives you various options by providing a DoubleClick block,
  1811. a FindBlock, a KeyBlock, aAltKeys, and even a MsgBlock.
  1812.  
  1813. You can do almost anything if you really must, but it's so very unusual
  1814. that you'll have to refer to documentation about Windows, as listed
  1815. previously.  Look for messages such as WM_KEYDOWN and WM_SYSKEYDOWN.
  1816. You might also want to handle WM_KEYUP, but that's even rarer because
  1817. it does not occur during auto-repeating keys (keys held down).
  1818.  
  1819.  
  1820. Converting Data Driven Applications
  1821. -----------------------------------
  1822.  
  1823. Data driven code can usually be converted fairly easily because using
  1824. Clip-4-Win you can readily build/modify menus, dialogs, etc. at runtime.
  1825.  
  1826. You should probably re-structure your menus, largely in line with the
  1827. section "Converting Menus" - except you'll probably be loading at least
  1828. parts of your menus from data files.
  1829.  
  1830. The discussions of Data Entry Forms and Validation apply to data driven.
  1831. Whether it's easy to convert your code (and the data that drives it) can
  1832. depend heavily on how easily you can keep or adapt your validation logic.
  1833.  
  1834. You will probably have to change your data to add information specific
  1835. to Windows e.g. control types (push button, check box, etc.).
  1836.  
  1837. If you want to build dialogs at runtime, you probably have to add more
  1838. precise positioning information than row and column.
  1839.  
  1840. The above changes are important but may not be terribly hard.  If you
  1841. wish, you can use your new driver file(s) with your DOS Clipper code.
  1842.  
  1843. A small aside: if you use any INI files, keep them small and don't use
  1844. them too often.  They're limited to 64KB and tend to be quite slow.
  1845.  
  1846.  
  1847. Converting Printing/Reports
  1848. ---------------------------
  1849.  
  1850. For reports, there are some really good report writers available for
  1851. Windows, such as R & R for Windows and Crystal Reports.
  1852.  
  1853. For interface code for R & R, see SOURCE\RRW*.PRG.
  1854.  
  1855. For samples using Crystal Reports see SOURCE\CONTRIB\CRYSTAL.ZIP,
  1856. SOURCE\CONTRIB\CRWCLS.PRG and SOURCE\ADDMGR\CRW.PRG.  (Some of these
  1857. are not shipped with the Evaluation version of Clip-4-Win.)
  1858.  
  1859. For Report Smith, see SOURCE\RPTSMTH.PRG.
  1860.  
  1861. For CA-RET, see SOURCE\CONTRIB\CARET.ZIP and SOURCE\OO\CMD\CARET.PRG.
  1862. CA-Visual Express is similar.
  1863.  
  1864. For other printing, you may still be able to print directly to the printer
  1865. as with DOS Clipper, but Windows does not encourage it and in any case you
  1866. may want to stop using all those ESCape sequences.  Taking advantage of
  1867. the device independence provided by Windows means you can use almost all
  1868. the same code to output to the screen or to any printer.
  1869.  
  1870. When converting an application, you are recommended to get your user
  1871. interface and related changes working, and do printing later.  This means
  1872. you will have gained useful experience, and it may even help save a tree
  1873. or two!
  1874.  
  1875. Expect printing under Windows to be slower than under DOS.  There are
  1876. several reasons, including spooling via Print Manager, font mapping
  1877. (which you may not want), the device independence provided by Windows,
  1878. and the much more complex device drivers involved.
  1879.  
  1880. About fonts and font mapping: Windows tries to match your application's
  1881. requested fonts with those available on the device, and substitutes if
  1882. it thinks it's necessary.  This is fine when it works, but a real pain
  1883. when it doesn't.  Don't contact us about it, please, because we don't
  1884. know the answer!
  1885.  
  1886. If you want to print bar codes, you can buy fonts to do it.  Ask your
  1887. software supplier for their recommendations.  You can also get Windows
  1888. add-ons that provide bar code support using a DLL, which you should be
  1889. able to use via Clip-4-Win's _DLL UDC or CallDLL(), which is used by
  1890. _DLL.
  1891.  
  1892. Some samples showing simple printing include SOURCE\PRINTHEL.PRG,
  1893. SOURCE\PRINTER.PRG, SOURCE\PRINTFLS.PRG, SOURCE\PRINTDC.ZIP and
  1894. SOURCE\WBTDEMO.ZIP.  Please spend some time looking at them when you
  1895. want to know about printing.
  1896.  
  1897. Without using SET PRINTER TO, here's about the shortest way to print
  1898. "Hello World!" on the default printer.  You can find this and other
  1899. samples in SOURCE\PRINTHEL.PRG.
  1900.  
  1901. The actual printing is done using a "device context".  You can think
  1902. of a device context as being a little like a work area.  It's something
  1903. you have to use to do certain things (in this case output), and it has
  1904. its own set of functions that must be used.  These functions can both
  1905. control and query the device.
  1906.  
  1907. As you should expect in Windows, you deal with a device context using
  1908. a handle.
  1909.  
  1910. You can get a handle to a printer device context using functions such
  1911. as GetPrintDC() and PrintDlg().  When you've finished printing you
  1912. *must* delete the handle using DeleteDC(), otherwise you lose important
  1913. internal Windows memory (the limited part that stores resources when
  1914. in memory).
  1915.  
  1916. WARNING: Freeing resources is vital!  Going back to the comparison
  1917.          with a work area, failing to free resources is much worse
  1918.          than failing to close your work areas.  Windows is likely
  1919.          to crash eventually if you don't free things properly!
  1920.          Always free things as soon as you can.  That makes it less
  1921.          likely you'll forget, and also means Windows can share its
  1922.          limited resources with other apps without running out.
  1923.  
  1924. An example that queries the device is to get the size of a page:
  1925.    nWidth := GetDeviceCaps(hDC, HORZRES)
  1926.    nHeight := GetDeviceCaps(hDC, VERTRES)
  1927.  
  1928. static function DoHello()
  1929. local    hDC
  1930.  
  1931. if !empty(hDC := GetPrintDC())        // see also: PrintDlg()
  1932.     StartDoc(hDC, "SomeDocument")
  1933.  
  1934.     StartPage(hDC)
  1935.     TextOut(hDC, 50, 50, "Clip-4-Win says: Hello World!")
  1936.     EndPage(hDC)
  1937.  
  1938.     EndDoc(hDC)
  1939.     DeleteDC(hDC)
  1940. endif
  1941. return nil
  1942.  
  1943.  
  1944. Here's the same thing, but with some extra printing in Arial 10 point
  1945. and also a bitmap.
  1946.  
  1947. Note how easily you can create a font; see FONT.CH for more information.
  1948. As usual in Windows, what you get is a handle to the item - in this case
  1949. a font handle.  When you've finished with it, you *must* delete it using
  1950. DeleteObject(), otherwise you'll lose Windows resources.
  1951.  
  1952. Actually using the font is just a little work: you use SelectObject().
  1953. It's a nuisance, but you must save and restore the old font.  If you
  1954. don't, you'll again lose Windows resources.
  1955.  
  1956. Please re-read the WARNING above, about freeing resources!
  1957.  
  1958. #include "font.ch"    // for CREATE FONT
  1959.  
  1960. static function DoArial()
  1961. local    hDC, hFont, hOldFont, cBmp
  1962.  
  1963. if !empty(hDC := GetPrintDC())        // see also: PrintDlg()
  1964.  
  1965.     CREATE FONT  HANDLE hFont  NAME "Arial"  SIZE 10  DC hDC
  1966.     StartDoc(hDC, "SomeDocument")
  1967.  
  1968.     StartPage(hDC)
  1969.     TextOut(hDC, 50, 50, "Clip-4-Win says: Hello World!")
  1970.  
  1971.     hOldFont = SelectObject(hDC, hFont)
  1972.     TextOut(hDC, 50, 150, "In Arial 10: Hello World!")
  1973.     SelectObject(hDC, hOldFont)
  1974.  
  1975.     cBmp = ReadDIB(hDC, "CLIP4WIN.BMP")
  1976.     ShowDIB(hDC, cBmp, 200, 250)
  1977.     EndPage(hDC)
  1978.  
  1979.     EndDoc(hDC)
  1980.     DeleteObject(hFont)
  1981.     DeleteDC(hDC)
  1982. endif
  1983. return nil
  1984.  
  1985.  
  1986.  
  1987. Converting From Modal To Modeless
  1988. ---------------------------------
  1989.  
  1990. Most DOS Clipper applications are modal: in each screen your user has
  1991. a limited number of choices available, e.g. menus can only be accessed
  1992. if no data entry form is currently active.  In a completely modeless
  1993. application, your user could do anything at any time.
  1994.  
  1995. Modal applications/screens restrict users, and are usually easier to
  1996. develop.  Windows encourages less modality, especially when browsing
  1997. data (including previews).
  1998.  
  1999. Of course, business applications have more places where modal behaviour
  2000. is sensible than word processors, drawing programs, etc.  File/record
  2001. locks are too important to be kept indefinitely, so you have to limit
  2002. modeless screens and/or release locks after a reasonable time interval.
  2003.  
  2004. If you convert your DOS application in a straightforward way, you'll
  2005. get a mainly modal Windows application, although browsers and previews
  2006. tend to be modeless unless you intervene.  The only other exception may
  2007. be your menus.  If you find that these allow unwanted activities to be
  2008. started, you will have to disable some menu items from time to time.
  2009.  
  2010. This kind of straightforward application is often called SDI (Single
  2011. Document Interface; don't dwell on the word "Document").  The other end
  2012. of the spectrum is MDI (Multiple Document Interface), where you have a
  2013. main ("frame" or "shell") window and any other ("child") windows are
  2014. inside the frame, the way Program Manager is organised.
  2015.  
  2016. Doing MDI properly means extra work: as many child windows as sensible
  2017. should be modeless (non-modal), your last two popup menus should be
  2018. "Window" (with specific menu items e.g. using the UDC MDIWINDOWPOPUP)
  2019. and "Help", any child menus should be displayed in the frame, and so
  2020. on.  To learn more, use some of the most successful Windows applications
  2021. and check out as many sources of Windows information as you can.  The
  2022. MS documentation is rather incomplete in some areas, but still good in
  2023. other areas.  See "Windows Interface Guidelines for Software Design",
  2024. listed previously.
  2025.  
  2026. It's probably not a good idea to do MDI if you're a beginner in Windows
  2027. or Clip-4-Win.  Also, unless you're adventurous, plan to use objects to
  2028. handle the standard stuff just listed.  The WMDIApp, WDialog, WMDIFrame,
  2029. WMDIClient and WMDIChild classes are all organised to co-operate in
  2030. making MDI reasonably painless.  The WTable class provides encapsulated
  2031. access to databases/work areas.  You can't use STATICs for data entry
  2032. (unless you force the data entry form to be used only once at a time,
  2033. which isn't ideal for MDI) - instead put them in an array or object,
  2034. e.g. as done by SOURCE\OO\USERSRES\USERSMDI.PRG.
  2035.  
  2036. Now that you know some of the pitfalls of MDI, why does anyone use it?
  2037. Mainly to provide increased convenience for users, and to be consistent
  2038. with the leading Windows applications.  You can do the same, if you're
  2039. willing to expend the extra effort required.
  2040.  
  2041. It's worth pointing out that you can have modeless activities in an SDI
  2042. application, and you can have modal ones in an MDI application.  This
  2043. sounds like there's some sort of overlap between the two, and although
  2044. that's inevitably somewhat so, you should do your very best to make
  2045. your mind up sooner rather than later.  As with other software issues,
  2046. changing your strategy is possible but means more work.
  2047.  
  2048. Despite the above, converting a well-written OO application from SDI to
  2049. MDI tends to be fairly easy, because using classes should mean everything
  2050. is well encapsulated.
  2051.  
  2052. If you're really not sure whether you should use MDI, you should almost
  2053. certainly go for SDI.
  2054.  
  2055. Let's suppose you start with SDI, e.g. because it's less work and more
  2056. like the structure of your DOS application.
  2057.  
  2058. Next, suppose you want fewer modal screens but without necessarily going
  2059. to MDI.
  2060.  
  2061. For browsers, you need to create a new window for each browse - as you
  2062. may already be doing.  You have to decide whether to let your user open
  2063. browsers in such a way that you need a database in two or more places
  2064. at the same time.  If you do, you can either save and restore all the
  2065. information about position etc. or you can use the WTable class to open
  2066. the file multiple times, each in its own work area and with a unique
  2067. alias.  (Or you could use a Clipper add-on such as dbClass or ObjectDB.)
  2068.  
  2069. If you decide to write your own code (instead of using WTable etc.) to
  2070. allow the same database to be used in two or more places concurrently
  2071. without opening the database more than once, you have to save and
  2072. restore all the work area information (and any connected information).
  2073. This is so painful that you should probably think again!  However, if
  2074. you decide to continue, when do you save and restore?  The answer
  2075. depends on the way you've written your application, but is likely to
  2076. be whenever you start or end a screen (form or browse), and any time
  2077. a browse needs data or movement occurs.
  2078.  
  2079. If your browsers never lock any files or records, and never add, edit or
  2080. delete any records, your work is nearly done.  You just have to get any
  2081. active browse to refresh its output if your user adds, edits or deletes
  2082. a record somewhere else in your application.  You can use the WTable
  2083. class to help with this, as it has a ReDo() method specifically for a
  2084. situation like this.
  2085.  
  2086. If, as is common, your user can add, edit or delete when browsing, and
  2087. you do this in a modal way, you're in the same situation as above.
  2088.  
  2089. Otherwise, you'll have to think about, and probably enhance, your
  2090. strategies for locking files and records.  You're in danger of letting
  2091. your user lock the same record or file more than once, from a single
  2092. application.  Yes, it's potentially worse than multi-user (network)
  2093. programming.
  2094.  
  2095. Several of the samples in SOURCE\OO\USERS and SOURCE\OO\USERSRES use
  2096. WTable:ReDo().  They're dialogs; a browser is easier: the ReDo() method
  2097. just needs to oBrowse:RefreshAll().
  2098.  
  2099. Let's continue with this SDI application we're trying to make less
  2100. modal.
  2101.  
  2102. Dialogs can typically be converted to be modeless with a trivial change
  2103. to the source code, e.g. by adding the MODELESS clause to SHOW DIALOG
  2104. or by changing DialogBox() to CreateDialog(), but your application will
  2105. then usually malfunction!
  2106.  
  2107. Why?  Because your code that puts the dialog on the screen will expect
  2108. the dialog to have been completed or cancelled before it returns.  A
  2109. modeless dialog, however, starts up but then returns and at the same
  2110. time carries on running!  The equivalent in DOS Clipper would be for
  2111. ACHOICE(), MEMOEDIT(), or READ/READMODAL() to start their work and then
  2112. return without waiting for the user - and yet also keep the screen
  2113. showing their output and the user able to interact with it as usual.
  2114.  
  2115. If you're having trouble imagining the mayhem that would result, try
  2116. looking at some of your DOS Clipper code.  Find an ACHOICE(), MEMOEDIT()
  2117. or READ/READMODAL() and look at what the code just after it does.  It
  2118. probably uses the result(s) or LASTKEY() in some way.  In a modeless
  2119. situation, the results are not yet available and no key has yet been
  2120. pressed.
  2121.  
  2122. Now imagine you could put the code that needs the result(s) and/or
  2123. LASTKEY() somewhere else, so that it only gets run at the right time.
  2124. This is exactly what's needed for event driven programming.
  2125.  
  2126. For a dialog, instead of immediately after the code that creates the
  2127. dialog, you put your result-handling code where it will be run when the
  2128. user tries to end or cancel the dialog (or presses a Save button, if you
  2129. use that mechanism).  So, not only do you perform any extra validation
  2130. needed when the user presses OK/Save, you also do updates etc. at the
  2131. same time.  This is the way all the versions of the USERS sample work,
  2132. even the modal ones, largely because this way you can change between
  2133. modal and modeless very easily.
  2134.  
  2135. The USERS samples are discussed in the tutorials (in the NG, HLP and 
  2136. manuals).  The source code is in SOURCE\USERS, SOURCE\USERSRES,
  2137. SOURCE\OO\USERS, and SOURCE\OO\USERSRES.
  2138.  
  2139.  
  2140.  
  2141. Converting: Alphabetical Clipper Commands/Functions
  2142. ---------------------------------------------------
  2143.  
  2144. ? / ??
  2145.     For testing, usually works.  Needs removing eventually, or converting
  2146.     to e.g. a MessageBox().
  2147.  
  2148. @...BOX
  2149.     Not usually appropriate in Windows.  Often indicates a new menu or
  2150.     form, in which case convert as appropriate.  In a form, a Group Box
  2151.     is the control you want.  If your code really is drawing, try
  2152.     MoveTo(), LineTo(), PolyLine(), Polygon(), Rectangle(), etc. (see
  2153.     "drawing and output functions" in the NG/HLP).
  2154.  
  2155. @...CLEAR
  2156.     Not appropriate in Windows.  Often indicates a new menu or form,
  2157.     in which case convert as appropriate.
  2158.  
  2159. @...GET
  2160.     Almost always need converting to a control (usually an edit control)
  2161.     in a dialog.  See @ ID ... EDIT, @ ID ... GET, @ DIALOG ... GET.
  2162.     Be wary of trying to control the input focus e.g. during a VALID;
  2163.     you may cause an error or annoy your user.
  2164.  
  2165. @...PROMPT
  2166.     Use Clip-4-Win's menus (or maybe use push buttons).
  2167.  
  2168. @...SAY
  2169.     Almost always need converting to use a static text control in a
  2170.     dialog.  Despite its name, you can change the contents of a static
  2171.     text control, e.g. using SetWindowtext(), and some of its versions
  2172.     aren't text at all, e.g. SS_ICON.
  2173.  
  2174. ACCEPT
  2175.     For testing, usually works.  Needs removing eventually, or converting
  2176.     to e.g. a dialog.
  2177.  
  2178. ACHOICE()
  2179.     Supported by Clip-4-Win's ACHOICE.PRG.
  2180.  
  2181. ALERT()
  2182.     Supported by Clip-4-Win's ALERT.PRG.
  2183.  
  2184. ALTD()
  2185.     Only supported with no parameters; invokes the debugger.
  2186.  
  2187. BROWSE()
  2188.     Look at WBrowseDef() in SOURCE\WBROWDEF.PRG, which uses WBROWSE
  2189.     (see HLP\WBROWSET.HLP).
  2190.  
  2191. CHECKBOX()
  2192.     Use a CHECKBOX control, almost always in a dialog.
  2193.  
  2194. CLEAR GETS
  2195.     To terminate a modal dialog, use EndDialog(); for a modeless dialog
  2196.     use DestroyWindow().
  2197.  
  2198. CLEAR SCREEN / CLS
  2199.     For testing, usually works.  Needs removing eventually, or converting
  2200.     to opening a new window e.g. a dialog.
  2201.  
  2202. CLEAR TYPEAHEAD
  2203.     Not appropriate in Windows.
  2204.  
  2205. COL()
  2206.     For testing, usually works.  Probably part of a menu or form, in
  2207.     which case convert the code appropriately.
  2208.  
  2209. COLORSELECT()
  2210.     Not appropriate in Windows.
  2211.  
  2212. DBEDIT()
  2213.     Look at WBrowseDef() in SOURCE\WBROWDEF.PRG, which uses WBROWSE
  2214.     (see HLP\WBROWSET.HLP and SOURCE\WBTDEMO.ZIP).
  2215.  
  2216. DEVOUT() / DEVOUTPICT() / DEVPOS()
  2217.     Not really appropriate in Windows, but may work adequately.
  2218.  
  2219. DISPBEGIN() / DISPCOUNT() / DISPEND()
  2220.     Not appropriate in Windows.
  2221.  
  2222. DISPBOX()
  2223.     Not appropriate in Windows.  Often indicates a new menu or form,
  2224.     in which case convert as appropriate.
  2225.  
  2226. DISPOUT()
  2227.     May work for testing, but not appropriate in Windows.  May be part
  2228.     of a new menu or form, in which case convert as appropriate.
  2229.  
  2230. FKLABEL() / FKMAX()
  2231.     Not appropriate in Windows.
  2232.  
  2233. G (Graphics) Functions
  2234.     Convert to use Windows functions, e.g. ShowDIB(), Ellipse(),
  2235.     Rectangle(), MoveTo(), LineTo(), Polygon(), PolyLine(), SetPixel(),
  2236.     DrawText(), TextOut().
  2237.  
  2238. GETNEW()
  2239.     Convert to a control (usually an edit control) in a dialog.  See
  2240.     @ ID ... EDIT, @ ID ... GET, @ DIALOG ... GET.  Be wary of trying
  2241.     to control the input focus e.g. during a VALID; you may cause an
  2242.     error or annoy your user.
  2243.  
  2244.     See Also: "Converting Custom GET Readers".
  2245.  
  2246. INKEY()
  2247.     For testing, usually works - helping you to convert in stages. Needs
  2248.     removing/converting eventually to e.g. a menu or dialog.  If part
  2249.     of a TBROWSE loop, remove it and if need be set the WBROWSE object's
  2250.     KeyBlock.
  2251.  
  2252. INPUT
  2253.     For testing, usually works.  Needs removing eventually, or converting
  2254.     to e.g. a dialog.
  2255.  
  2256. KEYBOARD
  2257.     Should not be used in Windows.  For some usages, you can get a similar
  2258.     effect by using PostMessage(), but not for keystrokes.
  2259.  
  2260. LASTKEY()
  2261.     Needs removing/converting.  If it's there to handle a menu, it should
  2262.     be removed (Windows handles the user interface).  If it's there to
  2263.     handle a cancelled form, e.g. checking for ESC, the equivalent for
  2264.     DialogBox() is to check for the return value IDCANCEL.  Similarly,
  2265.     MessageBox() returns IDOK/IDCANCEL/... depending on the MB_* value(s)
  2266.     you use for the <nType> parameter (default MB_OK).
  2267.  
  2268. LISTBOX()
  2269.     Use a LISTBOX (or COMBOBOX) control, almost always in a dialog.
  2270.  
  2271. MAXCOL() / MAXROW()
  2272.     For testing, usually works.  Probably part of a menu or form, in
  2273.     which case convert the code appropriately.
  2274.  
  2275. M (Mouse) Functions
  2276.     Not appropriate in Windows.  Mouse messages in Windows are handled
  2277.     through the same central mechanism as keystrokes, timers, window
  2278.     creation, window destruction, and so on.  SetCursor() controls the
  2279.     kind of mouse cursor.
  2280.  
  2281. MEMOEDIT()
  2282.     Supported by Clip-4-Win's MEMOEDIT.PRG.
  2283.  
  2284. MENU TO
  2285.     Use Clip-4-Win's menus.
  2286.  
  2287. MENUITEM()
  2288.     Use Clip-4-Win's menus.
  2289.  
  2290. NEXTKEY()
  2291.     Not appropriate in Windows.
  2292.  
  2293. PCOL() / PROW()
  2294.     Not really appropriate in Windows, but may work adequately.
  2295.  
  2296. POPUP()
  2297.     Use Clip-4-Win's menus.
  2298.  
  2299. PUSHBUTTON()
  2300.     Use a PUSHBUTTON control, almost always in a dialog.
  2301.  
  2302. QOUT() / QQOUT()
  2303.     For testing, usually works.  Needs removing eventually, or converting
  2304.     to e.g. a MessageBox().
  2305.  
  2306. RADIOBUTTO()
  2307.     Use a RADIOBUTTON control, almost always in a dialog.
  2308.  
  2309. RADIOGROUP()
  2310.     Use a GROUPBOX control, almost always in a dialog.
  2311.  
  2312. READ / READMODAL()
  2313.     Not appropriate in Windows.  Probably marks the activation of a form
  2314.     or menu, in which case convert the preceding code appropriately.
  2315.  
  2316. READEXIT()
  2317.     Not appropriate in Windows.
  2318.  
  2319. READFORMAT
  2320.     Not appropriate in Windows.
  2321.  
  2322. READINSERT()
  2323.     Not appropriate in Windows.
  2324.  
  2325. READKEY()
  2326.     Not appropriate in Windows.  See LASTKEY().
  2327.  
  2328. READKILL()
  2329.     Not really appropriate in Windows, but may work adequately.
  2330.  
  2331. READUPDATED()
  2332.     Not really appropriate in Windows, but may work adequately.
  2333.  
  2334. READVAR()
  2335.     Not really appropriate in Windows, but may work adequately with
  2336.     @ DIALOG ... GET.
  2337.  
  2338. RESTSCREEN()
  2339.     Not appropriate in Windows.  Probably marks the end of a menu, popup
  2340.     window or form, in which case convert the preceding code appropriately.
  2341.  
  2342. ROW()
  2343.     For testing, usually works.  Probably part of a menu or form, in
  2344.     which case convert the code appropriately.
  2345.  
  2346. SAVESCREEN()
  2347.     Not appropriate in Windows.  Probably marks the beginning of a menu,
  2348.     popup window or form, in which case convert the code appropriately.
  2349.  
  2350. SCROLL()
  2351.     For testing, usually works.  Probably part of a menu or form, in
  2352.     which case convert the code appropriately and remove the SCROLL().
  2353.  
  2354. SCROLLBAR()
  2355.     Use a SCROLLBAR control, or use window style WS_HSCROLL and/or
  2356.     WS_VSCROLL.  Avoid scroll bars in dialogs; consider a tabbed dialog
  2357.     (property sheet page) using the oTab class (see HLP\OTABT.HLP and
  2358.     SOURCE\OTABDEMO).
  2359.  
  2360. SETBLINK()
  2361.     Not appropriate in Windows.
  2362.  
  2363. SETCANCEL()
  2364.     Not appropriate in Windows.
  2365.  
  2366. SET COLOR / SETCOLOR()
  2367.     Not appropriate in Windows.  You can have coloured text in a browser
  2368.     easily e.g. using WBCOLUMN's bgcolor and fgcolor properties or
  2369.     WBROWSE's colorSpec.  Note that these all use the Windows way of
  2370.     specifying colours as RGB(nRed, nGreen, nBlue) values.
  2371.  
  2372.     Coloured text in dialogs is discouraged, but can be done if you
  2373.     handle the WM_CTLCOLOR message or use an owner-draw control.
  2374.  
  2375. SET CONFIRM
  2376.     You almost certainly want SET CONFIRM ON in your MAIN().
  2377.  
  2378. SET CONSOLE
  2379.     Not really appropriate in Windows, but may work adequately.
  2380.  
  2381. SET CURSOR / SETCURSOR()
  2382.     Different in Windows, where the cursor means the mouse cursor.  The
  2383.     equivalent of the DOS Clipper cursor is known as a caret.  You don't
  2384.     usually alter either the cursor or the caret in the same way as you
  2385.     do in DOS, so you probably just want to remove your DOS cursor code.
  2386.     If you know an operation will take quite a while, change the (mouse)
  2387.     cursor to an hour-glass temporarily:
  2388.         hCursor := LoadCursor( , IDC_WAIT)
  2389.         hOldCursor := SetCursor(hCursor)
  2390.         . . .lengthy activity. . .
  2391.         SetCursor(hOldCursor)  // restore previous cursor
  2392.  
  2393. SET DEVICE
  2394.     Not really appropriate in Windows, but may work adequately.
  2395.  
  2396. SET ESCAPE
  2397.     Supported a little differently in Windows.  ESC in a dialog is 
  2398.     generally treated as a Cancel request; you can handle it as you wish.
  2399.  
  2400. SET FUNCTION
  2401.     See SET KEY.
  2402.  
  2403. SET INTENSITY
  2404.     Not appropriate in Windows.
  2405.  
  2406. SET KEY / SETKEY()
  2407.     Hotkeys are called accelerators in Windows.  They are easily added
  2408.     to dialogs by using "&" before a character.  Other keys can be
  2409.     supported using LoadAccelerator(), SetAccelerator(), etc.  Apart
  2410.     from dialogs, try to use them sparingly, and keep them consistent
  2411.     throughout your application.
  2412.  
  2413. SET MARGIN
  2414.     Not really appropriate in Windows, but may work adequately.
  2415.  
  2416. SET MESSAGE
  2417.     Use Clip-4-Win's menus.
  2418.  
  2419. SETMODE()
  2420.     Not appropriate in Windows.
  2421.  
  2422. SETPOS()
  2423.     For testing, usually works.  Probably part of a menu or form, in
  2424.     which case convert the code appropriately.
  2425.  
  2426. SETPRC()
  2427.     Not really appropriate in Windows, but may work adequately.
  2428.  
  2429. SET PRINTER
  2430.     Not really appropriate in Windows, but may work adequately.
  2431.  
  2432. SET SCOREBOARD
  2433.     You almost certainly want SET SCOREBOARD OFF in your MAIN().
  2434.  
  2435. SET TYPEAHEAD
  2436.     Not appropriate in Windows.
  2437.  
  2438. SET VIDEOMODE
  2439.     Not appropriate in Windows.
  2440.  
  2441. SET WRAP
  2442.     Use Clip-4-Win's menus.
  2443.  
  2444. TBCOLUMNNEW()
  2445.     Use WBCOLUMN{} (see HLP\WBROWSET.HLP and SOURCE\WBTDEMO.ZIP).
  2446.     If the "{}" cause a compile error, you to need to #include "TopClass.ch"
  2447.  
  2448. TBROWSEDB() / TBROWSENEW()
  2449.     Use WBROWSE{} (see HLP\WBROWSET.HLP and SOURCE\WBTDEMO.ZIP).
  2450.     If the "{}" cause a compile error, you to need to #include "TopClass.ch"
  2451.     Remove your "stabilize loop": it's effectively internal to WBROWSE.
  2452.  
  2453. TONE()
  2454.     Appears to work, perhaps surprisingly.
  2455.  
  2456. TOPBAR()
  2457.     Use Clip-4-Win's menus.
  2458.  
  2459. UPDATED()
  2460.     Not really appropriate in Windows, but may work adequately.
  2461.  
  2462. WAIT
  2463.     For testing, usually works.  Needs removing eventually, or converting
  2464.     to e.g. MessageBox().
  2465.  
  2466.