home *** CD-ROM | disk | FTP | other *** search
/ The Mother of All Windows Books / CD-MOM.iso / cd_mom / newsletr / winprogj / wpjv1n3 / wpjv1n3.txt < prev   
Text File  |  1993-03-02  |  126KB  |  3,718 lines

  1.  
  2.  
  3.  
  4.  
  5.  
  6.  
  7.     WW     WW     WW      PPPPPPPP              JJ
  8.     WW     WW     WW      PP    PP              JJ
  9.      WW   WWWW   WW       PP    PP              JJ
  10.      WW  WW  WW  WW       PPPPPPPP              JJ
  11.      WW  WW  WW  WW       PP             JJ     JJ
  12.       WWWW    WWWW        PP              JJ   JJ
  13.        WW      WW         PP               JJJJJ
  14.  
  15. ----------------------------------------------------------------
  16. The Windows Programmer's Journal                       Volume 01
  17. Copyright 1993 by Peter J. Davis                       Number 03
  18. and Mike Wallace                                          Mar 93
  19. ----------------------------------------------------------------
  20. A monthly forum for novice-advanced programmers to share ideas and concepts
  21. about programming in the Windows (tm) environment.   Each issue is uploaded
  22. to  the info  systems  listed below  on the  first of  the month,  but made
  23. available at the convenience of the sysops, so allow for a couple of days.
  24.  
  25. You can get in touch with the editors via Internet or Bitnet at:
  26.  
  27. HJ647C at GWUVM.BITNET   or   HJ647C at GWUVM.GWU.EDU
  28.  
  29. CompuServe: 71141,2071
  30.  
  31. GEnie: P.DAVIS5
  32.  
  33. or you can send paper mail to:
  34.  
  35. Windows Programmer's Journal
  36. 9436 Mirror Pond Dr.
  37. Fairfax, Va. 22032
  38.  
  39. We can also be reached by phone at: (703) 503-3165.
  40.  
  41. The WPJ BBS can be reached at: (703) 503-3021.
  42.  
  43. The WPJ  BBS is currently 2400 Baud (8N1). We'll  be going to 14,400 in the
  44. near future, we hope.
  45.  
  46.  
  47.  
  48.  
  49.  
  50.  
  51.  
  52.  
  53.  
  54.  
  55.  
  56.  
  57.  
  58.  
  59.  
  60.  
  61.  
  62.  
  63.  
  64.  
  65.  
  66.  
  67.  
  68.                                 LEGAL STUFF
  69.  
  70.  
  71. - Microsoft, MS-DOS, Microsoft Windows, Windows NT, Windows for Workgroups,
  72. Windows for Pen Computing,  Win32, and Win32S are registered  trademarks of
  73. Microsoft Corporation.
  74.  
  75. - Turbo  Pascal for  Windows, Turbo  C++ for Windows,  and Borland  C++ for
  76. Windows are registered trademarks of Borland International.
  77.  
  78. -  Other trademarks mentioned herein  are the property  of their respective
  79. owners.
  80.  
  81. - WordPerfect is a registered trademark of WordPerfect Corporation.
  82.  
  83. -  WPJ  is  available  from  the  WINSDK,  WINADV  and  MSWIN32  forums  on
  84. CompuServe, and the IBMPC, WINDOWS and BORLAND forums on GEnie.  It is also
  85. available on America Online in the Programming library.   On Internet, it's
  86. available on WSMR-SIMTEL20.ARMY.MIL and FTP.CICA.INDIANA.EDU.  We upload it
  87. by  the 1st of each  month and it  is usually available by  the 3rd or 4th,
  88. depending on when the sysops receive it.
  89.  
  90. -  The Windows Programmer's Journal takes no responsibility for the content
  91. of  the   text  within  this  document.  All   text  is  the  property  and
  92. responsibility of the individual authors. The Windows Programmer's  Journal
  93. is solely a vehicle for allowing  articles to be collected and  distributed
  94. in a common and easy to share form. 
  95.  
  96. -  No part  of  the Windows  Programmer's  Journal may  be re-published  or
  97. duplicated in  part or whole, except in the complete and unmodified form of
  98. the Windows Programmer's Journal, without the express written permission of
  99. each  individual author. The Windows  Programmer's Journal may  not be sold
  100. for  profit without the express written permission of the Publishers, Peter
  101. Davis  and  Michael  Wallace,  and  only  then  after  they  have  obtained
  102. permission from the individual authors.
  103.  
  104.  
  105.  
  106.  
  107.  
  108.  
  109.  
  110.  
  111.  
  112.  
  113.  
  114.  
  115.  
  116.  
  117.  
  118.  
  119.  
  120.  
  121.  
  122.  
  123.  
  124.  
  125.  
  126.  
  127.  
  128.  
  129.  
  130.  
  131.                     Table of Contents
  132.  
  133. Subject                                        Page Author(s)
  134. -----------------------------------------------------------------
  135. WPJ.INI .......................................  4  Pete Davis
  136.  
  137. Letters .......................................  7  Readers
  138.  
  139. Beginner's Column .............................  10  Dave Campbell
  140.  
  141. Install Program Part III ......................  20  Pete Davis
  142.  
  143. Home Cooking - C++ From Scratch ...............  22  Andrew Bradnan
  144.  
  145. Creating and Using Owner Draw Buttons .........  26  Todd Snoddy 
  146.  
  147. Hacker's Gash .................................  30  Mike and Pete
  148.  
  149. Special News ..................................  32 Mike Wallace
  150.  
  151. Windows 3.1: Using Version Stamping Library ...  33  Alex Fedorov
  152.  
  153. Book Review ...................................  36  Pete Davis
  154.  
  155. Book Review ...................................  38  Mike Wallace
  156.  
  157. Printing in Windows ...........................  40  Pete Davis
  158.  
  159. Advanced C++ and Windows ......................  45  Andrew Bradnan
  160.  
  161. Trials and Tribulations Part 1 ................  54  Jim Youngman
  162.  
  163. Getting in Touch with Us .....................   57  Pete & Mike
  164.  
  165. Last Page ....................................   58  Mike Wallace
  166.  
  167.  
  168.  
  169.  
  170. Windows Programmer's Journal Staff:
  171.  
  172. Publishers ......................... Pete Davis and Mike Wallace
  173. Editor-in-Chief .................... Pete Davis
  174. Managing Editor .................... Mike Wallace
  175. Contributing Editor ................ David Campbell
  176. Contributing Editor ................ Andrew Bradnan
  177.                                      
  178.  
  179. Contributing Writer ................ Dave Campbell
  180. Contributing Writer ................ Alex Federov
  181. Contributing Writer ................ Andrew Bradnan
  182. Contributing Writer ................ Jim Youngman
  183. Contributing Writer ................ Todd Snoddy
  184.  
  185.  
  186.  
  187.  
  188.  
  189.  
  190.  
  191.  
  192.  
  193.  
  194.                                   WPJ.INI
  195.                                by Pete Davis
  196.  
  197.      Issue #3, wow... So far things have been going really  well. The third
  198. issue is almost as big as the first and second put together! We've even got
  199. some extra  articles for next month. We're getting a good response from the
  200. readers and the contributions are coming in. It seems like every day we get
  201. a letter  from someone  in another country.  We've been  getting mail  from
  202. people in  Romania, Czechoslovakia, Russia, Hong  Kong, Australia, England,
  203. Germany, etc... In fact, the amount of mail we're getting is getting pretty
  204. high. We try  to answer it all, but we don't  always get a chance to. Also,
  205. some of the replies  don't make it back. I've noticed  this more often with
  206. certain Internet sights in the U.K. I don't know why that is, but  the ones
  207. that have something  like ac.uk in them  don't seem to make it  back. Sorry
  208. about that. The list goes on and on.  Keep the mail coming, we love to  get
  209. it.
  210.  
  211.      I  mentioned in  the  last issue  about  readers suggesting  different
  212. formats for the text. So far, the biggest response has been in favor of the
  213. WINHELP format  and the  plain text format.  That way you  can read  it on-
  214. screen and,  if you want, print it out. Speaking  of printing it out, we've
  215. had  some negative  responses about  the printing  format. I  have to  take
  216. responsibility for that. I had the page length a bit too long and a  lot of
  217. you  were getting pages with three  or four lines of text.  I'll try not to
  218. let that happen again.
  219.  
  220.      As far as the Help format, because we can do bitmaps now, it  would be
  221. nice  if we  could get  someone who  is artistically  inclined (Not  us, as
  222. you'll  see in  this  first issue)  to  give us  a hand  with  some of  the
  223. graphics. You  don't have to be a  Renoir, just better than  us. If you can
  224. draw stick figures, you probably qualify.
  225.  
  226.      So, this  issue we'll be starting with the WINHELP format. We hope you
  227. like it. We're pretty pleased with the idea. Right now  we're using minimal
  228. tools for getting  it into the help format, but  we're looking into getting
  229. some better ones later on.
  230.  
  231.      We'd like to thank the guys at  America Online for giving us some time
  232. each month to get on and be able  to stay in touch with our readers  there.
  233. Mike will have  more to  say about all  this in  the Last Page.   [See  the
  234. "Special News" column - MW]
  235.  
  236.      This month I'm going to  be doing my article on printing that I should
  237. have done last month. Sorry for the  delay, but we've just been real  busy.
  238. I'm  also going  to  do the  third article  in  the series  on the  install
  239. program. This article is basically going to give some insight into the data
  240. structure that we're going to use for storing data about each of  the files
  241. for the install. 
  242.  
  243.      This month David Campbell will be taking  over the Beginners Column in
  244. C and we have  Andrew Bradnan taking over the Beginners  Column in C++. (We
  245. could still use someone to do the Beginner's Column in Pascal for Windows).
  246.  
  247.                                    - 4 -
  248.  
  249.  
  250.  
  251.  
  252.  
  253.  
  254.  
  255.  
  256.  
  257. This brings me  to another point.  Beginner's Column  might be a  bit of  a
  258. misnomer. Yes, they'll  all be basic, but like anything  else, as time goes
  259. on,   the  articles  will  progress.  They  will  all  be,  eventually,  an
  260. intermediate programmer's column.  There's only  so much that  you can  say
  261. about the basics and eventually you have to move on.
  262.  
  263.      We're starting a new feature this month called Hacker's Gash. Hacker's
  264. Gash is going to be a bunch of  little tips and tricks that you can use  in
  265. Windows.  This is  where the  readers can  really get  in on the  fun. Read
  266. through it, get an idea  of what kinds of things we're doing and  send in a
  267. list of your own tricks.
  268.  
  269.      Ah, good news, the BBS is finally up. Haven't replaced the hard drive,
  270. but I've done some serious work on it and did some pretty strenuous testing
  271. on it and it  seems to be ok for now. I will need to replace it eventually,
  272. but we  don't have the  money right now  for that. Besides  being the first
  273. place to get the Windows Programmer's Journal, the WPJ BBS has the SIMTEL20
  274. CD-ROM which has 600+ megs of public domain and shareware software. We will
  275. try to add a second CD-ROM in the near future and add some new disks. We'll
  276. probably try to  add the CICA disk next, which has  a very large library of
  277. Windows  public domain  and shareware  software.  The number's  around here
  278. somewhere, but to make  it easier, and as long as you're reading here, it's
  279. (703) 503-3021. (That's in the U.S. for you guys overseas.)
  280.  
  281.      Mike and  I have debated doing  this, but we're really  getting to the
  282. point  where we  don't have  much choice.  Seeing as  we're going  into the
  283. Windows Help format, which will allow us to support graphics, we've decided
  284. that we're going to start allowing some  advertisers in the magazine. We're
  285. going to keep the ads to a  minimum, but, even though the magazine is free,
  286. there  are some costs  involved in putting  it together and  we really need
  287. some reimbursement  for it. Right now, our Compuserve and Genie bills alone
  288. are costing us a  fair amount. Running  the BBS will  cost us $25/month  in
  289. phone bills  alone,  not to  mention possible  hardware maintenance.  We're
  290. hoping to get a few advertisers to help offset some of those costs. The ads
  291. will  be, most  likely,  from  shareware  authors. If  you  are  a  Windows
  292. shareware author and you're interested in advertising, get in touch with us
  293. and we'll discuss the costs, size, design, etc.
  294.  
  295.      One last thing before I wrap up. We've gotten a lot  of comments about
  296. the format of the magazine. Most  of these were in reference to the  format
  297. we're going to  distribute it in. If you have  preferences about the layout
  298. or other stylistic concerns, we'd like to hear them.
  299.  
  300.      That's really  about it  for this  month, I guess.  We just  wanted to
  301. thank  all of  you for  reading and  to keep  the comments  and suggestions
  302. coming. We'd  also like to thank  those of you who  have submitted articles
  303. and we ask that you please keep the articles coming in. It would be nice if
  304. we got a few more submissions each month so we could get the magazine up to
  305. the  size that  we'd really like.  I think 50  pages would be  a good size,
  306. although, with something  like this, I suppose,  the bigger the better,  so
  307. more than  50 pages would be  fantastic. Well, I've said  enough. Enjoy the
  308. magazine.
  309.  
  310.                                    - 5 -
  311.  
  312.  
  313.  
  314.  
  315.  
  316.  
  317.  
  318.  
  319.  
  320.                                              _Pete Davis
  321.  
  322.  
  323. P.S. I just read Mike's Last Page (He does it last, so I didn't see it when
  324. I was writing the WPJ.INI) You  might want to read that and then  come back
  325. to this, but  I wanted to give my own thoughts  on what Mike said regarding
  326. the WPJ as a  source of information. We are not here to be a sole source of
  327. information,  nor will we  always be correct.  We do our  best to make sure
  328. that the information we  give out is correct. The only people who check the
  329. submissions  are Mike and  myself. Neither one  of us has a  PhD in Windows
  330. Programming.  We both make mistakes. The WPJ is going to have errors in it.
  331. We're  going to give  out completely wrong information  at times. When that
  332. happens, we will  try to correct  ourselves by the  next issue (usually  in
  333. response to someone saying "You guys screwed up!").
  334.  
  335.      What I'm saying is that there are a lot of sources out there. You need
  336. to check out  as many as you  can. Cross-reference the information  between
  337. different books and  magazines and  when you see  an inconsistency,  that's
  338. probably a fault.
  339.  
  340.      Mike and I  have several reasons for doing  the WPJ. First of  all, we
  341. felt  beginners weren't  being addressed  well enough.  Also, we  wanted to
  342. cover as many  Windows programming languages and  environments as possible.
  343. (We're getting there,  but slowly.) Most  important, I think, we  wanted to
  344. address Windows programming and Windows programming only. 
  345.  
  346.      Windows is  an enormous  programming environment  and there's  tons of
  347. information to absorb.  No one  publication can even  approach covering  it
  348. all. I like to  think that one of our  advantages is that, unlike a  lot of
  349. other programming magazines,  we don't center on a  topic each month. Other
  350. magazines  might have,  say, an  issue on  Custom Controls  or an  issue on
  351. Multi-media. Well, that's great, but what  about those of us who don't work
  352. with Custom  Controls or Multi-media?  There are  two issues that  we don't
  353. care for. We try to keep a variety of topics each month so we can appeal to
  354. as many people as possible with each issue, but I digress. 
  355.  
  356.      To  wrap up, I just want to say,  (this feels like deja vu, I'll check
  357. later, but I think  I said this in the  last issue also) don't count  on us
  358. being right  every time. If something  we wrote about doesn't  work, try it
  359. again. If it still doesn't work,  we probably screwed up. Let us know,  and
  360. we'll try to correct it by the next  issue. I know, I'm getting repetitive.
  361. I know, I'm getting repetitive. Until the next issue, peace.
  362.  
  363.  
  364.  
  365.  
  366.  
  367.  
  368.  
  369.  
  370.  
  371.  
  372.  
  373.                                    - 6 -
  374.  
  375.  
  376.  
  377.  
  378.  
  379.  
  380.  
  381.  
  382.  
  383.                                   Letters
  384.  
  385. Date:  10-Feb-93 07:04 EST
  386. From:  Chris Newham [100027,16]
  387. Subj:  Windows Programmer's Journal
  388.  
  389. Hi,
  390. I would just like to say that I have enjoyed the two  issues of WPJ so far.
  391. You  can add  at least  1 to  your readership  numbers as  my friend  and I
  392. download it once between us.
  393.  
  394. The sections on DLLs & install progs are of particular interest to us as we
  395. have just finished work on a DLL and are now working on the install prog. A
  396. point that I think you might mention and give some consideration to for the
  397. install prog is this:
  398.  
  399. If  you are installing DLLs then you need to check if the file exists first
  400. (easy); if it exists you then need to check its version control information
  401. to determine  if the copy you  wish to install is newer  (also quite easy);
  402. the difficult part comes when you  have determined that you need to replace
  403. the existing DLL but the DLL is already in use by Windows.
  404.  
  405. Windows  will not let  you delete or replace  the file. Microsoft's install
  406. program  works around this but we haven't yet worked out how; we think that
  407. it copies the new DLL to a temp dir  or hides it somewhere then replaces it
  408. when Windows next starts up.
  409.  
  410. It's an interesting little problem  and has at present got us  stumped.  If
  411. you know how to  solve it let's see it published.  If we find a solution we
  412. will let you know. [We're working on this problem, too. - MW]
  413.  
  414. I am very impressed by the quality of the journal and would like to  see it
  415. remain  in either text  or Windows  write format  as my time  on the  PC is
  416. limited and so what I do is take a hard copy of the journal to work to read
  417. at lunch time so Winhelp format would not suit me.
  418.  
  419. Keep up the good work Best wishes Chris.
  420.  
  421.  
  422.  
  423.  
  424.  
  425.  
  426.  
  427.  
  428.  
  429.  
  430.  
  431.  
  432.  
  433.  
  434.  
  435.  
  436.                                    - 7 -
  437.  
  438.  
  439.  
  440.  
  441.  
  442.  
  443.  
  444.  
  445.  
  446. Date:  12-Feb-93 17:29 EST
  447. From:  Tammy Steele
  448. Subj:  WPJ
  449.  
  450. Hi Mike,
  451.      I just  downloaded my mail  today.   I generally only  read it  once a
  452. month. So, sorry for the delay in my comments about WPJ.  I had a chance to
  453. skim the  first issue  and have  just spent some  time reading  through the
  454. second issue.  Of course I cannot make official "Microsoft" comments, but I
  455. can give you my opinion.
  456.  
  457.      Generally speaking,  I think the WPJ will be another good place for us
  458. to point  people to  for  information.   One problem  will  be getting  the
  459. Microsoft  support engineers  familiar  with the  content  of the  journal.
  460. Another problem is the accuracy of the articles.  In order for Microsoft to
  461. point people to the articles, we need review the accuracy of  them.  We are
  462. pretty  swamped now with too much  to do, not enough people.   I am sending
  463. mail to my group about the journal.  So we'll see what happens.
  464.  
  465.      Specifically,  I thought it was  good that you  discussed the feedback
  466. you received from the  first journal.  Especially  the Linked List  sample.
  467. If the sample  were used for a  large number of nodes, as  I'm sure someone
  468. probably mentioned, it  would not  be a "cooperative"  windows app  because
  469. each globalalloc  eats a selector and the selectors (8K of them) are shared
  470. between *all* windows apps and the Windows system itself.
  471.  
  472.      Also, it  would be useful  for samples  to be updated  to the  current
  473. software although there is still value to having the code and comments (ie,
  474. the DLL article.)
  475.   
  476.      People who  submit articles  should check  the MSKB and  if they  have
  477. areas that they  are uncertain  about, should post  questions in the  forum
  478. before they submit articles.  For example, the DLL article talks about WEPS
  479. and  says  something  like  "the  documentation  says  the  WEP  is  called
  480. once...but I haven't seen  it be called in Codeview in Windows 3.0."  There
  481. is an article that  discusses the reason why you can't see  it be called in
  482. Codeview  under Windows  3.0 and/or  this question  could have  been easily
  483. answered in the  DLL section on WINSDK. [This question  is answered farther
  484. down in this column. - MW]
  485.  
  486.      I like the coding  style and the  sample makefiles (the makefiles  are
  487. easy to read.)
  488.   
  489.      I'm sure you've  had lots of comments, as you  mentioned, about how to
  490. format  this.  I  personally think you  should offer it  in helpfile format
  491. *and* text format so that people have the option. [editors note: This seems
  492. to be the most common opinion.]
  493.  
  494.      Overall,  I see  this journal as  a really  great place  for people to
  495. compile information and share it.
  496.  
  497.      Thanks for all your work,     Tammy
  498.  
  499.                                    - 8 -
  500.  
  501.  
  502.  
  503.  
  504.  
  505.  
  506.  
  507.  
  508.  
  509. Editor's  Note: We got  a letter from Craig  Derouen (CompuServe ID: 75136,
  510. 1261) of Seattle.   I'll reprint  his letter here and  let him explain  his
  511. BBS:
  512.  
  513. I've  looked at  your 2  first issues  of WPJ  and am  impressed. I  run an
  514. extensive  programmer's BBS out here in Seattle, primarily Windows code but
  515. also C, C++ and 8086 code for DOS.  I carry both your issues. I am also  in
  516. Fidonet and  have all the listings for WPJ available  for Frequest.  Also I
  517. am  a support BBS for WinTech,Windows/DOS,CUJ and PC Techniques Magazine. I
  518. have  a LOT of code on line. Anyways here's the details:
  519.  
  520.         Cornerstone BBS    (2400,9600 baud)
  521.         (206) 362-4283
  522.         Fidonet 1:343/22
  523.  
  524. All  magazine listings are available for first time callers; free downloads
  525. and some file requests are also available.
  526.  
  527. ---------------
  528.  
  529. Editor's Note: In  our last issue, we had an article by Rod Haxton on DLLs.
  530. In the article, he  mentioned that he had  never seen WEP get called  under
  531. CodeView  in Windows 3.0.  So, I posed  the question to the WINSDK forum on
  532. CompuServe and  got a  response  from Brian  Scott of  Microsoft.   Thanks,
  533. Brian.
  534.  
  535.  
  536. Mike,
  537.  
  538.      If you  implicitly link to  a DLL in  Windows 3.0,  the WEP is  called
  539. after the application has  unloaded.  Since Codeview stops  debugging after
  540. the application terminates, it  does not see  the WEP get  called.  If  you
  541. need to  debug the  WEP, you can  load it using  LoadLibrary() and  free it
  542. using  FreeLibrary(), instead of  linking to the  import library.   In this
  543. case, you will  see the WEP get called when you call FreeLibrary().  If you
  544. need to link to the DLL using an import library, then you can debug the WEP
  545. using something  like WDEB386,  putting  OutputDebugString() statements  in
  546. your  WEP, or moving the code  in the WEP into a  cleanup function that you
  547. call just before the application terminates.
  548.  
  549. Hope this helps,
  550. Brian Scott - Microsoft
  551.  
  552.  
  553.  
  554.  
  555.  
  556.  
  557.  
  558.  
  559.  
  560.  
  561.  
  562.                                    - 9 -
  563.  
  564.  
  565.  
  566.  
  567.  
  568.  
  569.  
  570.  
  571.  
  572. Editor's Note: Pete and I started a beginner's column with the first issue,
  573. and Dave Campbell offered last month to take it over.   Dave is a confirmed
  574. Windows hacker and so will be writing this column starting with this issue.
  575. We hope  you like  it.  Questions  should be  directed to  Dave.  Means  of
  576. reaching him are given at the end of his column.
  577.  
  578.                              Beginner's Column
  579.                               By Dave Campbell
  580.  
  581.      My  name is  Dave Campbell  and   I write  Windows software  under the
  582. business name WynApse. I am going to be writing the beginner's column until
  583. I  get buried  in work  (wouldn't that  be too  bad?) or  I get  usurped by
  584. someone who wants to do this more than I do. 
  585.  
  586.      Up until recently, I have been a Borland programmer, so the tools I am
  587. most  familiar with are the Borland C++  suite, 3.0 and 3.1, in particular.
  588. Pete and Mike's previous two columns  have been Microsoft, and I am leaning
  589. that way  myself right now,  so I  will continue that.  My intention is  to
  590. provide code and ideas that aren't tied to Borland or  Microsoft, but could
  591. be used  with either compiler. A  major difference between the  two is make
  592. files, so I will stay away from those as much as possible.
  593.  
  594.      My intention is  to write code that is useable  on 3.0/3.1 Windows. If
  595. enough questions come in for 3.1-specific applications, we will cover them.
  596. This leads directly into the request for requests. I would  like to present
  597. code  that is useful to  all the readers,  and the best way  would be to be
  598. responsive to readers questions. Please send questions! I will list various
  599. ways of reaching me at the end of each article.
  600.  
  601.      Now on  to the fun stuff...Mike and Pete  have been working on a Hello
  602. World program for a few issues, and I am going to add some configuration to
  603. that code and demonstrate some basic uses for radio buttons and INI files.
  604.  
  605.  
  606. Radio Buttons
  607.  
  608.      A radio button is one of several control types available to Windows
  609. programmers inside dialog  boxes. They work similar to the  buttons on your
  610. car  radio...only  one  may  be  pressed  at  a  time,  at  least  in  this
  611. application. There  are other controls  available in the basic  set, and we
  612. will look into  them later. For  now, let's look a  little deeper into  the
  613. declaration of a radio button.
  614.  
  615.      Radio button declarations are made in the .RC file, for example:
  616.  
  617. CONTROL "Hello", IDM_HELLO, "BUTTON",
  618.      BS_AUTORADIOBUTTON  | WS_CHILD | WS_VISIBLE |  WS_TABSTOP, 20, 15, 42,
  619. 12
  620.  
  621.      We will  use  one  more button  in  our  dialog  box, and  that  is  a
  622. pushbutton. The  ever-present OK button  to be  precise. The  control is  a
  623. graphical
  624.  
  625.                                    - 10 -
  626.  
  627.  
  628.  
  629.  
  630.  
  631.  
  632.  
  633.  
  634.  
  635. representation  of  a 3D  button  with  text written  on  it.  By making  a
  636. pushbutton an "OK button", we  are really painting the word OK  on the face
  637. of a 3D pushbutton. The button definition we are going to use is:
  638.  
  639. CONTROL "OK", IDOK, "BUTTON",
  640.      BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 29, 57, 24, 14
  641.  
  642.      The explanation of  the fields  in these two  declarations is  covered
  643. later in this article.
  644.  
  645.  
  646. INI files
  647.  
  648.      INI files are used  by Windows to control the  environment under which
  649. Windows executes. Windows programs use INI files to manage the individual
  650. environmental changes available  to users  to customize the  system to  his
  651. needs. There are two ways to read a variable from an INI file, but only one
  652. way to write information into one:
  653.  
  654.    Reading:
  655.  
  656.       ReadPrivateProfileString
  657.       ReadPrivateProfileInt
  658.  
  659.    Writing:
  660.  
  661.       WritePrivateProfileString
  662.  
  663.      This does seem  clunky, but it's what we've got to  work with. When we
  664. get to that point, I'll demonstrate that it's not that big of a problem. 
  665.  
  666.      Windows handles the INI files very nicely, in that you will always get
  667. something for your efforts. In the Read calls, a default value is given, in
  668. case the variable  does not exist in the INI file  (or the INI file doesn't
  669. exist, for that  matter). In the Write call, if  the variable doesn't exist
  670. (or even if the INI file doesn't exist) prior to the call, it will when the
  671. call returns.  This means  that you  needn't be  concerned  about the  pre-
  672. existence of an INI file before trying to use one.
  673.  
  674.      Let's consider an INI file for our application, HELLO.INI:
  675.  
  676. [Hello]
  677. Setup=0
  678.  
  679.      This  is  the simplest  form  of  an  INI  file:  there  is  a  single
  680. 'Application  Name',  [Hello],  and  a  single 'Keyname',  Setup,  that  is
  681. associated  with it.  The value  for Setup  is 0.  During execution  of our
  682. program, if we  had a need for picking up the  Setup value, we would simply
  683. use the line:
  684.  
  685. nDflt = ReadPrivateProfileInt("Hello", "Setup", 0, "hello.ini");
  686.  
  687.  
  688.                                    - 11 -
  689.  
  690.  
  691.  
  692.  
  693.  
  694.  
  695.  
  696.  
  697.  
  698.      where nDflt is defined:
  699.  
  700. int nDflt;
  701.  
  702.      This reads from hello.ini,  looking for the 'Setup' keyword  under the
  703. 'Hello' application name, and assigning the value equivalence listed there,
  704. or 0 for default, to the variable nDflt.
  705.  
  706.      If, however, the INI file was defined as:
  707.  
  708. [Hello]
  709. Setup=Hello World
  710.  
  711.      you  now cannot read the file with ReadPrivateProfileInt. Now the line
  712. to read the value is:
  713.  
  714. char szDflt[25];
  715.  
  716.      ReadPrivateProfileString("Hello",  "Setup",  szDflt, "Goodbye  World",
  717. 13,          "hello.ini");
  718.  
  719.      This has some similarities,  and some differences from the  one above.
  720. The "Hello", "Setup", and "hello.ini"  are self-explanatory, but the string
  721. read also adds other parameters.  The string name to store the  result into
  722. is listed as the third parameter, in this case szDflt.  The fifth parameter
  723. is  an integer variable declaring the maximum number of characters accepted
  724. from the  INI file, and the fourth parameter, in this case "Goodbye World",
  725. is the default value. If this line were to be executed and the INI file not
  726. even  exist, szDflt would contain the  string "Goodbye World". This is also
  727. the case  if the file exists but does  not contain either [Hello] or Setup,
  728. or both.
  729.  
  730.      To change an INI file, the WritePrivateProfileString  function must be
  731. called:
  732.  
  733. char szDflt[25];
  734. int  nDflt;
  735.  
  736. wsprintf(szDflt, "%d", nDflt);
  737. WritePrivateProfileString("Hello", "Setup", (LPSTR) szDflt, "hello.ini");
  738.  
  739.      'wsprintf' should  be used in place of 'sprintf' because it is already
  740. in    Windows, and  will  not cause  the  linking to  another  library. The
  741. downside  is the need to cast the string  as a LPSTR. wsprintf will build a
  742. string, in this  case, containing  the ASCII representation  of the  single
  743. integer nDflt.  That string is then passed to the INI file using the syntax
  744. shown. If  the Setup variable  were a string,  the variable to  be inserted
  745. would  be given in the WritePrivateProfileString call rather than using the
  746. intermediate wsprintf step.
  747.  
  748.      Whew, I'm glad that's over. 
  749.  
  750.  
  751.                                    - 12 -
  752.  
  753.  
  754.  
  755.  
  756.  
  757.  
  758.  
  759.  
  760.  
  761. Dialog Box
  762.  
  763. The dialog box  we're going to display will have two  radio buttons, and an
  764. OK  button. Typically,  the buttons  to complete  a dialog box  are located
  765. either along the bottom, or if the dialog is  very busy, they can be placed
  766. along the  right edge. Of course,  this has nothing to  do with programming
  767. Windows. This is all aesthetics and being kind to users. Look at a thousand
  768. Windows applications, and  you'll get used to seeing  things a certain way.
  769. Users  get used to them being that way,  and you will lose some people just
  770. with your user interface, or lack thereof.
  771.  
  772.      The code I propose for the dialog box is:
  773.  
  774. Hello DIALOG 63, 56, 83, 77
  775. STYLE WS_POPUP | WS_CAPTION
  776. CAPTION "Hello Setup"
  777. FONT 8, "Helv"
  778. BEGIN
  779.      CONTROL "Hello",    IDM_HELLO,   "BUTTON", 
  780.        BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 20, 15, 42,
  781. 12   CONTROL "Goodbye",  IDM_GOODBYE, "BUTTON",
  782.        BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 20, 28, 42,
  783. 12   CONTROL "OK", IDOK, "BUTTON",
  784.          BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 29, 57, 24,
  785. 14 END
  786.  
  787.      This code can be produced either  by hand with any text editor, or  by
  788. graphically placing the  objects with Borland's  Resource Workshop or  some
  789. similar tool.  I usually start  with the Resource Workshop,  and make small
  790. tweeks  by hand.  Large changes  are  best done  graphically to  ensure the
  791. placement, and logical usefulness of the dialog box is alright. 
  792.  
  793.      I am  going to take  a break right here  and talk about  coding style.
  794. This is something that is as peculiar to a person as their  name. Everybody
  795. has their own, and I wouldn't think of trying to force my ideas on someone.
  796. But...(have you ever noticed  there's always a big But  around somewhere?),
  797. if you don't have your mind made up yet about those long Windows lines like
  798. the 100+ character  ones above,  please split them  onto succeeding  lines!
  799. Line-wrap  on listings  is UGLY!! OK,  that's over,  I've got it  out of my
  800. system. But (another one), since I  am writing this, you are going to  have
  801. to put up with my style...
  802.  
  803.      I'm  done now.   That  short dissertation  was free,  now back  to the
  804. program. The field explanations are as follows:
  805.  
  806. Hello DIALOG 63, 56, 83, 77
  807.  
  808.      This line defines the name, "Hello" of the dialog, and the coordinates
  809. of the box  relative to the client area of the parent window. The units are
  810. NOT pixels,  and beyond that, I  don't want to  get into it now.  Just play
  811. with it for now until you get it  where you like it. The first two  numbers
  812. are the x,y coordinates, and the second two are the width and height of the
  813.  
  814.                                    - 13 -
  815.  
  816.  
  817.  
  818.  
  819.  
  820.  
  821.  
  822.  
  823.  
  824. dialog.
  825.  
  826. STYLE WS_POPUP | WS_CAPTION
  827.  
  828.      The STYLE  line defines the manner  in which the dialog  box is built.
  829. WS_POPUP  is pretty standard for  dialog boxes. There  are real differences
  830. between  POPUP windows  and  OVERLAPPED windows,  the  main one  being  the
  831. CAPTION  on a POPUP is  an option. Because  we are going to  call this as a
  832. modal dialog box, we are going to give  it a caption bar to allow it to  be
  833. moved. 'Modal' dialog boxes,  as opposed to 'modeless', disable  the parent
  834. window, and demand attention until closed. Without the caption bar, the box
  835. cannot be moved.
  836.  
  837. CAPTION "Hello Setup"
  838.  
  839.      The CAPTION line declares  the caption used with the  WS_CAPTION style
  840. parameter.
  841.  
  842. FONT 8, "Helv"
  843.  
  844.      This  defines the  default font  used throughout  the dialog  box. You
  845. could pick any  conceivable font here, but  it is best to be  kind and only
  846. use those shipped to Windows users, or you are going to get some nasty mail
  847. messages.
  848.  
  849. BEGIN
  850.  
  851.      BEGIN..END or {..} define the body of the dialog box.
  852.  
  853. CONTROL "Hello",    IDM_HELLO,   "BUTTON",
  854.   BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 20, 15, 42, 12 
  855. CONTROL "Goodbye",  IDM_GOODBYE, "BUTTON",
  856.   BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 20, 28, 42, 12
  857.  
  858.      The  two radio  button definitions  following the  declaration CONTROL
  859. are:  
  860. - The text of the control, in our case "Hello" or "Goodbye"
  861.  
  862. -  The  ID value  to  be  returned  to  the  parent  window,  IDM_HELLO  or
  863. IDM_GOODBYE are defined in our ".H" file.
  864.  
  865. -  BUTTON declares  the control  class. Buttons  are generally  small child
  866. windows.
  867.  
  868. - BS stands for  BUTTON STYLE, and BS_AUTORADIOBUTTON declares  the buttons
  869. will  only  be pressed  one  at a  time,  and the  button  is automatically
  870. checked.
  871.  
  872. - WS_CHILD declares the dialog box as a child window. This means it resides
  873. within the boundaries of the parent window.
  874.  
  875. - WS_VISIBLE applies to overlapped and popup windows. The initial condition
  876.  
  877.                                    - 14 -
  878.  
  879.  
  880.  
  881.  
  882.  
  883.  
  884.  
  885.  
  886.  
  887. is visible.
  888.  
  889. -  WS_TABSTOP specifies that the user may step through the control sequence
  890. with the tab key.
  891.  
  892. - 20, 28, 42,  12 are the coordinates and  size of the control,  similar to
  893. that of the dialog box itself.
  894.  
  895. CONTROL "OK", IDOK, "BUTTON",
  896.    BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 29, 57, 24, 14
  897.  
  898.      The  OK  button  declaration:  everything  should be  self-explanatory
  899. except the  BS_DEFPUSHBUTTON style.  DEFPUSHBUTTON means  that this is  the
  900. default PUSHBUTTON. Pushbuttons are: OK, CANCEL, ABORT, RETRY, IGNORE, YES,
  901. NO, and HELP. Only  one may be the default, and it has a dark border around
  902. it, so that if the Enter key is  pressed, that is the one you get. IDOK  is
  903. defined in windows.h, so we don't have to declare it.
  904.  
  905.      The user will get this dialog box on the screen, and select one of the
  906. radio buttons, or toggle  them back and forth a few  times, then select OK.
  907. The  dialog box procedure  should read the  INI file and  preset the proper
  908. radio button to  show the user  the current value.  Upon selecting OK,  the
  909. procedure will have to set the selected information into the INI file. 
  910.  
  911.      That completes the dialog box procedure discussion. The code follows:
  912.  
  913. BOOL FAR PASCAL HelloDlgProc (HWND hDlg, WORD message, WORD wParam,
  914.   LONG lParam) {
  915.  
  916. switch (message)
  917.    {
  918.    case WM_INITDIALOG :
  919.       CheckRadioButton(hDlg, IDM_HELLO, IDM_GOODBYE, InitSettings);
  920.       return TRUE;
  921.  
  922.    case WM_COMMAND :
  923.       switch (wParam)
  924.          {
  925.          case IDM_HELLO : 
  926.                   WritePrivateProfileString("Hello", "Setup", "1",         
  927.                          "Hello.ini");
  928.                   InitSettings = wParam;
  929.                   break;
  930.  
  931.          case IDM_GOODBYE : 
  932.                   WritePrivateProfileString("Hello", "Setup", "0",         
  933.                          "Hello.ini");
  934.                   InitSettings = wParam;
  935.                   break;
  936.  
  937.          case IDOK :
  938.                   EndDialog(hDlg, wParam);
  939.  
  940.                                    - 15 -
  941.  
  942.  
  943.  
  944.  
  945.  
  946.  
  947.  
  948.  
  949.  
  950.                   return TRUE;
  951.  
  952.          }
  953.       break;
  954.  
  955.    }
  956. return FALSE;
  957. }      /* HelloDlgProc */
  958.  
  959.  
  960.      Notice the WM_INITDIALOG call:
  961.  
  962. CheckRadioButton(hDlg, IDM_HELLO, IDM_GOODBYE, InitSettings);
  963.  
  964.      An  assumption is being made  here that the  variable InitSettings has
  965. been read into our  program somewhere, and is set  to (in our case)  either
  966. IDM_HELLO or  IDM_GOODBYE. CheckRadioButton uses the dialog box handle hDlg
  967. to  identify a numerical sequence of buttons from IDM_HELLO to IDM_GOODBYE,
  968. and ensures that only InitSettings is set.
  969.  
  970.      More advanced Windows programmers will probably want to skip the 
  971.  
  972.    case IDM_HELLO
  973.    .
  974.    case IDM_GOODBYE
  975.  
  976. statements altogether, and wait until  OK is pressed to check the  state of
  977. the buttons. That  is fine, and  more streamlined, but  for now, let's  not
  978. leave  anyone behind. I want all of us to learn this stuff. 
  979.  
  980.      Let's  change one more function, or else  the whole idea of the dialog
  981. box selecting radio buttons is wasted.  Let's read the INI file in WndProc,
  982. and change the text displayed based upon the Select value.
  983.  
  984.      This is  going to  take two  steps, just  like the  two  parts of  the
  985. previous sentence:
  986.  
  987.    case WM_CREATE :
  988.      InitSettings = GetPrivateProfileInt("Hello", "Setup", 1, "Hello.ini");
  989.      InitSettings = InitSettings ? IDM_HELLO : IDM_GOODBYE;
  990.      hdc = GetDC(hWnd);
  991.      InvalidateRect(hWnd, NULL, TRUE);
  992.      ReleaseDC(hWnd, hdc);
  993.  
  994. and:
  995.  
  996. /*-------------------------------------------------------------------------
  997.  
  998. fall through to WM_PAINT...
  999. --------------------------------------------------------------------------
  1000. */    case WM_PAINT :
  1001.      hdc  =  BeginPaint(hWnd,&ps);             /*  returns  pointer  to hdc
  1002.  
  1003.                                    - 16 -
  1004.  
  1005.  
  1006.  
  1007.  
  1008.  
  1009.  
  1010.  
  1011.  
  1012.  
  1013. */        GetClientRect(hWnd, &rect);
  1014. /*
  1015.   -1 tells  the DrawText function to  calculate length of string  based on 
  1016. NULL-termination
  1017. */
  1018.  
  1019.     DrawText(hdc, (InitSettings == IDM_HELLO) ? "Hello Windows!" :
  1020.       "Goodbye   Windows!",  -1,   &rect,  DT_SINGLELINE   |  DT_CENTER   |
  1021. DT_VCENTER);      EndPaint(hWnd,&ps);
  1022.     return 0;
  1023.  
  1024.  
  1025.      The WM_CREATE  case reads  the  INI file  to find  out  which we  want
  1026. printed and  sets up the  'InitSettings' variable used  in the dialog  box.
  1027. Then we get a "handle to a device context" or hdc for our window. 
  1028.  
  1029.      A device  context is the area into which  we "paint" text in a window.
  1030. In a  dialog  box,  we  can use  wsprint,  but  in a  window,  we  have  to
  1031. "DrawText", and we draw it into a device context. A device context could be
  1032. a window  or a printer  page, Windows doesn't care.  At this point,  we are
  1033. getting  the device  context for  our main  window's client  area.  We then
  1034. report to Windows that the entire area is invalid, which will set us up for
  1035. a repaint.
  1036.  
  1037.      In handling the WM_PAINT case, we again  need an hdc, and this time do
  1038. it with a call to BeginPaint, passing a pointer to a  structure variable of
  1039. type PAINTSTRUCT. BeginPaint has Windows fill in the ps for us, and is then
  1040. available for our use. We aren't going to use it, however. 
  1041.  
  1042.      We call GetClientRect to  get the dimensions  of the client area  into
  1043. the rect structure.
  1044.  
  1045.      DrawText  uses the hdc, the rect structure, and the InitSettings value
  1046. to decide what to paint and where. Ultimately,  either "Hello Windows!", or
  1047. "Goodbye Windows!" is printed  on a single line, centered  horizontally and
  1048. vertically: DT_SINGLELINE |  DT_CENTER | DT_VCENTER. Notice  the note above
  1049. the DrawText line.  Instead of telling Windows  how many characters  we are
  1050. painting, let Windows do it for us!
  1051.  
  1052.      EndPaint closes the ps structure, and finishes our WM_PAINT case.
  1053.  
  1054.  
  1055. The Setup dialog
  1056.  
  1057.      I almost forgot, we do  need to get the dialog box onto  the screen. I
  1058. have added three lines to the hello.c file:
  1059.  
  1060. hMenu = GetSystemMenu(hWndMain, FALSE);
  1061. AppendMenu(hMenu, MF_SEPARATOR, 0, NULL);
  1062. AppendMenu(hMenu, MF_STRING,    IDM_SETUP, "Setup...");
  1063.  
  1064.      These get a handle to the system menu of the window, and insert a menu
  1065.  
  1066.                                    - 17 -
  1067.  
  1068.  
  1069.  
  1070.  
  1071.  
  1072.  
  1073.  
  1074.  
  1075.  
  1076. separator followed by the  word "Setup...". When "Setup..." is  chosen, the
  1077. value IDM_SETUP is sent to our windows message loop:
  1078.  
  1079.    case WM_SYSCOMMAND :
  1080.       switch (wParam)
  1081.          {
  1082.          case IDM_SETUP :
  1083.             lpfnHelloDlgProc = MakeProcInstance(HelloDlgProc, hInst);      
  1084.             DialogBox(hInst, "Hello", hWnd, lpfnHelloDlgProc);
  1085.             FreeProcInstance(lpfnHelloDlgProc);
  1086.             return 0;
  1087.          }
  1088.  
  1089.       break;
  1090.  
  1091.      This is handled  as WM_SYSCOMMAND, because the system menu  is the one
  1092. used.
  1093.  
  1094.      We must get a long pointer to a function, lpfnHelloDlgProc,  to use in
  1095. the DialogBox function call. The parameter "Hello" in the call is the title
  1096. of the  dialog box we built.  Because of the 'DialogBox'  call, this dialog
  1097. box will be modal, and control will  not return to the main window until OK
  1098. is pressed in the dialog box.
  1099.  
  1100.      Don't forget the classical Windows programmer's bug...any ideas??  Ok,
  1101. since nobody is raising their hand...it's  exporting the dialog box in  the
  1102. .def file. I have forgotten this so many times, I hesitate to admit  it. If
  1103. you don't  list the dialog  box in the .def  file, it is  guaranteed not to
  1104. work in Windows 3.0, and will be extremely unreliable in 3.1. The next time
  1105. you   get  an  unreliable  dialog  box,  remember  the  "classical  Windows
  1106. programmer's bug".
  1107.  
  1108.      The  files are  archived  with the  magazine. I  have  made one  other
  1109. excursion, and  that is  the dialog box  code is  in a separate  file named
  1110. HELLO.DLG. The file  HELLO.RC contains  the following line  to include  the
  1111. .DLG file:
  1112.  
  1113. rcinclude Hello.dlg
  1114.  
  1115.      This is pretty  standard among Windows  programmers, because it  keeps
  1116. the dialog box code in its own file.
  1117.  
  1118.  
  1119. 3.0/3.1/Borland
  1120.  
  1121.      I just checked and  found that I had produced great  3.1 code, but the
  1122. thing  wouldn't run  under  3.0. If  you  are programming  for  the general
  1123. public, you better keep at least a 'virgin' copy of 3.0 around on your hard
  1124. disk  for this sort  of checking. Users  tend to get a  little touchy about
  1125. that  sort of  thing. The  fix  is really  easy for  Microsoft people.  The
  1126. problem lies in the Resource Compilation stage.  If you type 'RC -?' at the
  1127. command line, you will see a '-30' switch listed. This is what will get you
  1128.  
  1129.                                    - 18 -
  1130.  
  1131.  
  1132.  
  1133.  
  1134.  
  1135.  
  1136.  
  1137.  
  1138.  
  1139. 3.0+ executables. That is the way I have it in the make file.
  1140.  
  1141.      Borland 3.1 programmers have  a couple more lines. In  addition to the
  1142. RC file change, you must also add the line:
  1143.  
  1144. #define WINVER 0x0300
  1145.  
  1146. ahead of  your include  of windows.h  in your hello.c  file. Also  you must
  1147. change the 
  1148.  
  1149. wc.lpfnWndProc   = WndProc;             /* Name of proc to handle window */
  1150.  
  1151. line to be:
  1152.  
  1153. wc.lpfnWndProc   = (WNDPROC)WndProc;    /* Name of proc to handle window */
  1154.  
  1155.      I  may have left  off some stuff  here, if so,  let me know  about it.
  1156. We're all in this thing together.
  1157.  
  1158.      Please hang  in there. If  you are beyond  the scope of  this article,
  1159. stick with  me we are  going to go places  together. If you  are way beyond
  1160. this, write  us an article. If  you are bogged  down, just compile  it, and
  1161. stare at the source. If you want help with something, send me a note.
  1162.  
  1163.      That's it for  this time. Next month  I plan on building  an About box
  1164. with live  compile date  inserted, and  I'll discuss  Icons  and Menus.  In
  1165. coming  issues my  intention is  to discuss  File Open  boxes, Help  files,
  1166. Dialog  Boxes as  main  windows,  Obscure  Dialog  Box  uses,  Timers,  and
  1167. debugging. Feel free to contact me in any of the ways below. I want  to rat
  1168. out  the things other  people are having  questions about, not  just what I
  1169. think people want to hear.
  1170.  
  1171. Dave Campbell WynApse PO Box 86247 Phoenix, AZ 85080-6247 (602)863-0411    
  1172.          CIS: 72251, 445
  1173.               Phoenix ACM BBS (602) 970-0474 - WynApse SoftWare forum
  1174.  
  1175.  
  1176.  
  1177.  
  1178.  
  1179.  
  1180.  
  1181.  
  1182.  
  1183.  
  1184.  
  1185.  
  1186.  
  1187.  
  1188.  
  1189.  
  1190.  
  1191.  
  1192.                                    - 19 -
  1193.  
  1194.  
  1195.  
  1196.  
  1197.  
  1198.  
  1199.  
  1200.  
  1201.  
  1202.                          Install Program Part III:
  1203.                              The SETUP.INF File
  1204.                                by Pete Davis
  1205.  
  1206.      Before I get started this month, there are a couple of things I wanted
  1207. to talk about. First  of all, because of some  stuff coming up in  the near
  1208. future, I won't be able to do Part IV of the install program next month. It
  1209. will, however,  continue in May. Sorry about this, but  Part IV is going to
  1210. be a big one and I'm not going to have the time to do it yet.
  1211.  
  1212.      I'd  also like  to  respond to  Chris  Newham's letter  (found,  oddly
  1213. enough, in  the  Letters  section)  regarding  installing  DLLs  which  are
  1214. currently active.  I have to admit I  haven't yet tried this,  but plan, by
  1215. Part  IV to  have a  solution. I  went through  Microsoft's code  for their
  1216. install program and I couldn't find anything that seemed to make exceptions
  1217. for DLLs.  This leads me to  believe that the solution  is something fairly
  1218. simple. If worst comes to worst, you could always just find out who's using
  1219. it and shut them down.  I doubt this is a very good way  to do it, but it's
  1220. just a thought. Like I said, I'm going to  look into it and I can hopefully
  1221. have a solution to it by Part IV. (I better have a solution by then, 'cause
  1222. Part IV is going to handle copying the files over.)
  1223.  
  1224.      Ok, this one's going to  be short and sweet.  There's not much to  it.
  1225. We're using a SETUP.INF file which is going to tell us what the name of our
  1226. application is, how big it is, what  files have to be installed, what disks
  1227. they're on, whether or not they're  executables, etc. I had two options for
  1228. doing   this.   I  could   have   used   the  GetPrivateProfileString   and
  1229. GetPrivateProfileInt functions and make  it into a .INI file, but  I wanted
  1230. to  maintain Windows  2.0  compatibility. (Just  kidding :-)  Actually, one
  1231. reason I didn't  is because I  didn't think of  it until  it was too  late.
  1232. Actually, there are some problems  with that approach. The problem  is that
  1233. we're dealing with multiple files and  they're going to have the same entry
  1234. names. I'm sure you understand completely now, right? Ok, here's an example
  1235. of a SETUP.INF file and then I'll explain it again.
  1236.  
  1237. ; Semicolons, as is typical for these kinds of files, mean comments follow
  1238. ; and the line is ignored.
  1239. Application=My Application
  1240. AppSize=1204558
  1241. DefaultDir=\MYAPP
  1242. ;
  1243. ; Now we'll have information about each file
  1244. ;
  1245. ; CompName = Name of file compressed on install disk
  1246. ; UCompName = Name of the file when we uncompress it.
  1247. ; FileType = 0 - EXE   1 - DLL   2 - Other (Other is the default)
  1248. ; FileSize = Uncompressed file size
  1249. ; AddDir = Sub-directory name if it's a sub-dir of the DefaultDir
  1250. ; (i.e. AddDir=\DATA would mean the file is in \MYAPP\DATA
  1251. CompName=MYAPP.EX0
  1252. UCompName=MYAPP.EXE
  1253. FileType=0
  1254.  
  1255.                                    - 20 -
  1256.  
  1257.  
  1258.  
  1259.  
  1260.  
  1261.  
  1262.  
  1263.  
  1264.  
  1265. FileSize=10294
  1266. CompName=DATA1.DA0
  1267. UCompName=DATA1.DAt
  1268. FileType=2
  1269. AddDir=\DATA
  1270.  
  1271.      Ok, that should be enough for a sample. Now our code is going to start
  1272. a separate node in our linked list of files to install each time it  hits a
  1273. CompName= statement.  This is  harder to  do with  the GetPrivateProfile...
  1274. functions. We could throw in a new section like [FILE1] for the first file,
  1275. [FILE2] for the second, etc. But back to the topic, we're not doing it that
  1276. way. I just wanted to give you ideas of how it could  be done if you choose
  1277. to do it that way.
  1278.  
  1279.      All right, well, that was all simple enough. Did I mention linked list
  1280. in the  last paragraph? Yup,  that nasty  phrase!!! Ok, it's  pretty simple
  1281. linked list.  To make it easier, it's essentially  a stack, so each time we
  1282. get a new file, we just add it to the front of the list. The code is in the
  1283. READSET.C file and the FILES.H file. I've commented it pretty heavily, so I
  1284. won't go in depth here. It's all very simple. 
  1285.  
  1286.      In Part IV, which will be in May, we're going to do  a lot of the real
  1287. work.  Like I said, it's going to be a big one, and we're going to be tying
  1288. in all the  stuff from the first  three parts. I  might have to finish  the
  1289. entire thing  in June, just  because what's left is  so big. I've  tried to
  1290. break this  series up into easily  recognizable parts, First I  had the DDE
  1291. with Program Manager, then I  had the LZEXPAND.DLL part, and this  month we
  1292. had the READSET  stuff. I feel like what's left all goes in the category of
  1293. 'the rest of  it', but there's so much, that I'll  have to break it up into
  1294. two hard  do divide  sections. I'll  probably just  do a  few of the  small
  1295. things  in each one, like  the Progress Bar,  creating directories, copying
  1296. files, etc...
  1297.  
  1298.      Oh well, that's  it for this month. Sorry it was  so short and sorry I
  1299. can't do it next month. If I can, I might try to do all of  the rest in May
  1300. to  make up  for  not doing  it  at all  in April.  We'll  see. Until  next
  1301. time......
  1302.  
  1303.  
  1304.  
  1305.  
  1306.  
  1307.  
  1308.  
  1309.  
  1310.  
  1311.  
  1312.  
  1313.  
  1314.  
  1315.  
  1316.  
  1317.  
  1318.                                    - 21 -
  1319.  
  1320.  
  1321.  
  1322.  
  1323.  
  1324.  
  1325.  
  1326.  
  1327.  
  1328. [Editor's  Note: Last month, I started a  Beginner's column on C++.  Andrew
  1329. Bradnan was kind  enough to  offer to  take over  the column,  and will  be
  1330. writing it  starting  with this  issue.   Any  questions  you have  can  be
  1331. directed to him.  His CompuServe ID is  given at the end of his column.   -
  1332. MW]
  1333.  
  1334.  
  1335.                       Home Cooking - C++ from Scratch
  1336.                              by Andrew Bradnan
  1337.  
  1338.      This month I'll be starting a new column WPJ will be doing every month
  1339. to introduce people to  C++ and Windows.  I  am going to try and  keep this
  1340. simple enough  for a programmer new  to Windows programming and  C++.  Your
  1341. brain  may melt if you  are at this stage,   but give it  a try and it will
  1342. sink in  after a  while.   Feel free to  send me  some email  on Compuserve
  1343. [70204,63].  I'll answer any questions and put the good ones  at the end of
  1344. next month's article.
  1345.  
  1346.      I've read many books  on C++ and  I considered all  but a few  totally
  1347. confusing.   (I  don't plan  on adding  my name  to this  list.)   There is
  1348. another rumor  that C++ is really slow because it  writes all sorts of code
  1349. behind your back.  This is kind  of like complaining that C writes all kind
  1350. of assembly behind your back.   The compiler is supposed to write  code for
  1351. you.  The great thing about C++ is that you can forget many of the details.
  1352. I usually forget them all  by myself.  Instead of me rambling how great C++
  1353. is,  let's find an  example of exactly how  we can forget  some things.  On
  1354. purpose.   The simplest place to start is constructors and destructors.  Of
  1355. all  the  confusing  C++  terms  (abstraction,  encapsulation,  modularity,
  1356. hierarchy, typing,  concurrency, and  persistence), constructors  fall into
  1357. the abstraction and  encapsulation category.  More later.   Since we almost
  1358. always  want  to initialize  our data  structures  (objects) to  some known
  1359. state, C++ will automatically  call your own constructor  every time.   The
  1360. converse is also true.   When you delete an object, or it goes out of scope
  1361. (falls out of   a curly braces), you also  want to free up memory,  and set
  1362. your variables to a used state.  Where in the bleep do they dome from?  You
  1363. only have to do two things.  First declare  your member functions, and then
  1364. define them.   Piece of cake.   For an example,  Windows is kind  enough to
  1365. require us to  create and destroy all  sorts of stuff.   There are over  63
  1366. calls to  create something in the Windows API.  All these objects (bitmaps,
  1367. etc.)  have to be closed, deleted,  freed, or destroyed (pick your favorite
  1368. synonym).  Since we almost always  trap the WM_CREATE message lets create a
  1369. PAINTSTRUCT object.   We'll call it a PAINT. If you want to put anything on
  1370. the screen, you have to ask permission. Since Windows is kind enough to let
  1371. many programs run  at once, they  have to share the  screen.  Kind  of like
  1372. kindergarten, and  Windows is the teacher.   To get permission  you have to
  1373. ask  for a display context (think constructor).   Your piece of the screen.
  1374. A display  context is provided  when we call  BeginPaint(...).  Due  to the
  1375. limitations of DOS and/or Windows you can only have five DC in use at once.
  1376. So after you  are done you have to  release the DC so that  another program
  1377. can draw in  his corner of the  sand box (think destructor).   This is done
  1378. with  EndPaint(...).   BeginPaint (...)  also fills  in a  PAINTSTRUCT data
  1379. structure.  The only additional information filled in is whether you should
  1380.  
  1381.                                    - 22 -
  1382.  
  1383.  
  1384.  
  1385.  
  1386.  
  1387.  
  1388.  
  1389.  
  1390.  
  1391. paint the background,  and the smallest rectangle you have to paint.  In my
  1392. tradition, let's  write some code  that uses our  PAINT object.   This will
  1393. clarify what advantages we'll  gain and what member functions  we are going
  1394. to have to write.
  1395.  
  1396. //
  1397. //   PAINT Object Test
  1398. //   Written by Andrew Bradnan (c) 1993
  1399. //   Note:  The only  C++ code  is in  the WM_PAINT  case statement  and in
  1400. PAINT.H
  1401.  
  1402. #define STRICT
  1403. #include <windows.h>
  1404. #include "paint.h"
  1405.  
  1406. char szAppName[] = "DC Test" ;
  1407. long FAR PASCAL _export WndProc (HWND, UINT, UINT, LONG) ;
  1408.  
  1409. int PASCAL  WinMain  (HINSTANCE hInstance,  HINSTANCE hPrevInstance,  LPSTR
  1410. lpszCmdParam, int nCmdShow) {
  1411.      HWND        hWnd ;
  1412.      MSG         msg ;
  1413.      WNDCLASS    wc ;
  1414.  
  1415.      if (!hPrevInstance) {
  1416.           wc.style         = CS_HREDRAW | CS_VREDRAW ;
  1417.           wc.lpfnWndProc   = WndProc ;
  1418.           wc.cbClsExtra    = 0 ;
  1419.           wc.cbWndExtra    = 0 ;
  1420.           wc.hInstance     = hInstance ;
  1421.           wc.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
  1422.           wc.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
  1423.           wc.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; 
  1424. wc.lpszMenuName  = NULL ;
  1425.           wc.lpszClassName = szAppName ;
  1426.  
  1427.           RegisterClass (&wc) ;
  1428.      };
  1429.  
  1430.      hWnd    =    CreateWindow     (szAppName,    "DC    Test     Program",
  1431. WS_OVERLAPPEDWINDOW,    CW_USEDEFAULT,     CW_USEDEFAULT,    CW_USEDEFAULT,
  1432. CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
  1433.  
  1434.      ShowWindow (hWnd, nCmdShow);
  1435.      UpdateWindow (hWnd);
  1436.  
  1437.      while (GetMessage (&msg, NULL, 0, 0)) {
  1438.           TranslateMessage (&msg) ;
  1439.           DispatchMessage (&msg) ;
  1440.      };
  1441.      return msg.wParam ;
  1442. };
  1443.  
  1444.                                    - 23 -
  1445.  
  1446.  
  1447.  
  1448.  
  1449.  
  1450.  
  1451.  
  1452.  
  1453.  
  1454. long  FAR PASCAL  _export WndProc  (HWND, UINT  message, UINT  wParam, LONG
  1455. lParam) {
  1456.       switch (message) {
  1457.           case WM_PAINT:
  1458.           {
  1459.                PAINT     Paint (hWnd);  //    PAINT   constructor    called
  1460. here!!              DrawText   (Paint,   "PAINT    Test!!",   -1,    Paint,
  1461. DT_SINGLELINE | DT_CENTER | DT_VCENTER);          //  BOTH  cast  operators
  1462. called!        };                  // PAINT destructor called here!!
  1463.           return 1;      // Everything Created OK
  1464.      case WM_DESTROY:
  1465.           PostQuitMessage (0);
  1466.           return 0;
  1467.      };
  1468.  
  1469.      return DefWindowProc (hWnd, message, wParam, lParam) ;
  1470. };
  1471.  
  1472.      As you can see we have gone from  four lines of code to two.  Not  too
  1473. bad.  The interesting part is that we no longer have to call  BeginPaint or
  1474. EndPaint,  and we no longer care what  those pesky PAINTSTRUCT members are.
  1475. The compiler  grabs it from the  PAINT object for us,  calling our declared
  1476. explicit cast operators.  There is also sort of an intentional bug.  If you
  1477. cover  up  only  part  of  the client  window  and  then  bring  the sample
  1478. application  back  to the  foreground, "PAINT  Test!"  doesn't draw  in the
  1479. middle of the client window.  "PAINT Test!" will  draw in the middle of the
  1480. rectangle that  was covered up.   Windows is  kind enough to  let you  do a
  1481. little optimization should you want to.  From  the sample code above we can
  1482. determine that  we need  a PAINT  object with four  member functions.   One
  1483. constructor, a destructor, a cast to an HDC, and a cast to a LPRECT.
  1484.  
  1485. //
  1486. //   PAINT Object Header
  1487. //   Written by Andrew Bradnan (c) 1993
  1488. //
  1489.  
  1490. #ifndef __PAINT_H
  1491. #define __PAINT_H
  1492.  
  1493. #ifndef __WINDOWS_H
  1494. #include <windows.h>
  1495. #endif
  1496.  
  1497. class PAINT {
  1498. public:
  1499.      PAINT (HWND p_hWnd) : hWnd (p_hWnd) {BeginPaint (hWnd, &ps); }; 
  1500. ~PAINT () { EndPaint (hWnd, &ps); };
  1501.  
  1502.      operator HDC ()          { return ps.hdc; };
  1503.      operator RECT FAR * ()   { return &ps.rcPaint; };
  1504.      operator BOOL ()    { return ps.fErase; };
  1505.  
  1506.  
  1507.                                    - 24 -
  1508.  
  1509.  
  1510.  
  1511.  
  1512.  
  1513.  
  1514.  
  1515.  
  1516.  
  1517. protected:
  1518.      HWND hWnd;
  1519.      PAINTSTRUCT ps;
  1520. };
  1521.  
  1522. #endif // __DC_H
  1523.  
  1524.      As you can see, strangely enough all the code is written in the header
  1525. file.   This is called inlining.   The short story is  that this allows the
  1526. compiler  to optimize  your source  code.   It does  this by  replacing the
  1527. function call  with the code you have written.   So our call to DrawText ()
  1528. really  won't  call  two  functions to  get  the  parameters  it  will just
  1529. reference the members in our PAINTSTRUCT ps.  It is  essentially like using
  1530. a  macro except  you get all  the type checking  thrown in for  free.  Some
  1531. statement will  not inline but your  compiler will let you  know what these
  1532. are.  You will also note that the constructor looks a little weird.  In the
  1533. constructor,  we can  optionally tell  the compiler  how to  initialize our
  1534. members hWnd and ps.  If we do  not the compiler will create them, set  all
  1535. the members to  zero, and then  we would initialize  the member within  the
  1536. curly braces.  Obviously  one more step than you want.   To initialize hWnd
  1537. we use  the parameter  passed in.   Memory for  hWnd is allocated  and then
  1538. filled  with the  parameter passed in.   Which  is exactly  what we wanted.
  1539. Space for ps is allocated,  set to zero, and  then we initialize it,  using
  1540. BeginPaint, in the function body.  An extra step but it can't be avoided in
  1541. this case.     So the moral of the story is that C++ code can be quite easy
  1542. to read when you are using the object.  Writing the actual code is a little
  1543. messier.  Just remember you only have to get it right once.  You can forget
  1544. two function calls and three PAINTSTRUCT member names.  You can even forget
  1545. about PAINTSTRUCT.  Readers   familiar  with   some  of   the  brand   name
  1546. applications frameworks  may be wondering why I did not add DrawText() as a
  1547. member function to our PAINT object.   Doing this we could just remove  the
  1548. two cast operators and  call DrawText with new parameters - DrawText (LPSTR
  1549. szText, int cb, UINT fuFormat).  The only problem  is we have to learn more
  1550. than we forget.  Now you have two versions of DrawText().  This is supposed
  1551. to be  easier not more complicated.   The second  reason is that  you can't
  1552. DrawText a PAINT.   English wise, this  makes no sense.  You  can draw some
  1553. text on  a DC.  That  would make sense.   You would also end  up with every
  1554. drawing  function  listed under  the PAINT  and/or  DC object.   Definitely
  1555. confusing.  Just look at Microsoft's AFX.  Yuck!  I  have  built the  above
  1556. example using BCW 3.1.  With minimal changes it ought to work fine with MSC
  1557. 7.0 and with  Windows NT.  Next month I'll start  a little earlier so I can
  1558. test it on all three platforms and send the appropriate make files to  make
  1559. your life easier.   Again if you have any problems, questions, suggestions,
  1560. or answers send me a note on Compuserve [70204,63].
  1561.  
  1562. Andrew  Bradnan  is  president of  Erudite  Software,  Inc.   Their  latest
  1563. offering, which  he wrote using C++,  is called Noise for  Windows, a sound
  1564. utility for Windows 3.1.  Feel free to contact him for information.
  1565.  
  1566.  
  1567.  
  1568.  
  1569.  
  1570.                                    - 25 -
  1571.  
  1572.  
  1573.  
  1574.  
  1575.  
  1576.  
  1577.  
  1578.  
  1579.  
  1580.                    Creating And Using Owner Draw Buttons
  1581.                                By Todd Snoddy
  1582.  
  1583.      Many of  you may  be wondering how  some Windows programs  display the
  1584. fancy bitmapped buttons.  Although these are not standard controls, Windows
  1585. does provide the capability  to utilize controls that are  custom designed.
  1586. Many times  a properly displayed graphic  can mean much more  than a simple
  1587. text word.   I will try to explain how you  can use your own custom buttons
  1588. in your programs.
  1589.  
  1590.      My sample  code is written in  Turbo Pascal for Windows,  although the
  1591. techniques apply just the  same to Turbo C++ for Windows.   I use Borland's
  1592. Object Windows Library in my examples, but if you use  Microsoft Foundation
  1593. Classes it should still be possible to understand the basic concepts.
  1594.  
  1595.      My  code emulates some of  the functions of  Borland's BWCC.DLL, which
  1596. many people use  to enhance the visual appearance of  their programs.  This
  1597. DLL provides  an assortment of  functions that can  give your  program that
  1598. extra visual  3D look without too  much work on your  part.  Unfortunately,
  1599. the size of the DLL can be a  big factor in deciding whether or not to  use
  1600. it, especially if you are writing a shareware program and want to  keep its
  1601. size down.  You may  also have your own reasons for not wanting  to use the
  1602. DLL, and this may cause you to look for other solutions.
  1603.  
  1604.      I will demonstrate how to use what is called owner drawn controls.  My
  1605. examples show how  to simulate the BWCC  style buttons from  BWCC.DLL using
  1606. owner  drawn buttons.   If  you want  to use  owner  drawn buttons  in your
  1607. programs, it should be rather straightforward to  use my code "straight out
  1608. of the box" or modify it to suit your needs.
  1609.  
  1610.      We'll start with the obvious question.  What are owner drawn controls?
  1611. An owner  drawn control is a  special type of control which  is designed to
  1612. allow the program  to specify custom behavior.   It is most  often used for
  1613. listboxes with graphics or for custom buttons.
  1614.  
  1615.      Whenever a user clicks on  an owner drawn control or it  changes state
  1616. in some other way, like getting the focus, a special message is sent to the
  1617. owning window of this control.  This message is called WM_DRAWITEM, and its
  1618. purpose is to  let the window  know that one  of it's owner drawn  controls
  1619. needs attention.   Along with  this message,  a pointer  to an  information
  1620. structure is sent to the window.  This structure contains information about
  1621. what exactly  happened with the  control, and  which control it  was.   The
  1622. pointer  to this  structure is  passed in  lParam.   In Pascal  format, the
  1623. structure looks like:
  1624.  
  1625.   TDrawItemStruct = record
  1626.     CtlType: Word;       { Control  type, can be  odt_Button, odt_ComboBox,
  1627. odt_ListBox, odt_Menu}
  1628.      CtlID: Word;             { ID of Control }
  1629.     itemID: Word;             {  Not  used  for  buttons.    Has  different
  1630. meanings for other types of controls }
  1631.      itemAction: Word;   { What happened.  For buttons, tells if gained  or
  1632.  
  1633.                                    - 26 -
  1634.  
  1635.  
  1636.  
  1637.  
  1638.  
  1639.  
  1640.  
  1641.  
  1642.  
  1643. lost focus, or selected }
  1644.      itemState: Word;    {  What  state control  should  be  in after  this
  1645. drawing.  Buttons only use  ods_Disabled, ods_Focused, and ods_Selected }
  1646.     hwndItem: HWnd; { Window handle for the control }
  1647.     hDC: HDC;       { Display context to be used for drawing the control } 
  1648.     rcItem: TRect;  { Defines clipping boundary rectangle for control }    
  1649.     itemData: Longint;   { Not  used for buttons.  Only  used for listboxes
  1650. and comboboxes }
  1651.   end;
  1652.  
  1653.      The  owning window can examine this structure and determine what needs
  1654. to be done with the control.  By looking in the CtlType field, it will know
  1655. what type of control  the message is for.   For a owner drawn  button, this
  1656. will be odt_Button.  The CtlID field  contains the ID of the control.   The
  1657. itemID field  is not used  for owner  draw buttons.   The itemAction  field
  1658. tells what happened with the control to cause this WM_DRAWITEM message.  It
  1659. can contain oda_DrawEntire, oda_Focus,  or oda_Select.  Only oda_Focus  and
  1660. oda_Select are relevant for owner drawn buttons.  If oda_Focus is set, then
  1661. the  focus for  the button  changed, and  you must  check itemState  to see
  1662. whether or not the control gained or lost the focus.  If oda_Select is set,
  1663. the selection  state of the button changed, and you must check itemState to
  1664. know what the new state is.
  1665.  
  1666.      The itemState field specifies  what state the control should  be drawn
  1667. in next.  To check the values of itemAction and itemState, you must use the
  1668. logical  AND operation  since they  can contain  more than  one value.   If
  1669. (itemState AND  ods_Focused) = TRUE,  then the  button has the  focus.   If
  1670. (itemState  AND ods_Selected)  =  TRUE, then  the  button is  selected,  or
  1671. pushed.
  1672.  
  1673.      The hwndItem field specifies the  window handle for the control.   You
  1674. can use this to send  messages to the control's window procedure.   The hDC
  1675. field is  the display context that should be used when drawing the control.
  1676. The rcItem field  defines the clipping  rectangle for the  control.  It  is
  1677. used mainly with owner  drawn menus.  The  itemData field is only used  for
  1678. listboxes and comboboxes.
  1679.  
  1680.      There are a couple of ways  that your window procedure can process the
  1681. WM_DRAWITEM message.  It can either draw the control itself, or it can pass
  1682. the message on to the  window procedure of the  control.  This will use  an
  1683. object oriented technique and  let the control  draw itself instead of  the
  1684. main window procedure having to worry about how to draw each control.  This
  1685. is the technique that I used in  my example code.  The dialog window merely
  1686. passes the WM_DRAWITEM message along to the control.
  1687.  
  1688.      The control reacts to  this message by looking at  the TDrawItemStruct
  1689. record and determining what state it should draw, and then draws the button
  1690. using StretchBlt.   I originally wrote  this to draw with  BitBlt, but when
  1691. the program  was tested under the  1024 x 768 resolution  while using large
  1692. fonts, it became obvious that hardcoding the size of the bitmap didn't work
  1693. properly when the dialog sizes were increased.  This  problem has basically
  1694. two solutions.  Either use StretchBlt to draw the bitmap button at a larger
  1695.  
  1696.                                    - 27 -
  1697.  
  1698.  
  1699.  
  1700.  
  1701.  
  1702.  
  1703.  
  1704.  
  1705.  
  1706. than normal size, or have separate bitmaps depending on the resolution.
  1707.  
  1708.      Both  of these methods have their pros and cons, and in the long run I
  1709. decided  to just  use StretchBlt.   You  will notice  a degradation  in the
  1710. quality of the bitmaps if  you do run in  the high resolutions and use  the
  1711. large fonts because StretchBlt can't do a perfect job scaling an image up.
  1712.  
  1713.      That's the  basic idea  for using owner  drawn buttons.   Things  will
  1714. probably be much clearer after  actually looking at the source code.   I'll
  1715. briefly describe how to use it.
  1716.  
  1717.      My code is primarily designed to be  used for owner drawn buttons in a
  1718. dialog  box, although there's no reason why you  can't use them in a normal
  1719. window.   There are several  steps that you will  have to take  to use this
  1720. code in your own programs.
  1721.  
  1722. 1.   DESIGN YOUR  DIALOG.   When designing  your dialog,  you will need  to
  1723. create a  standard  button  for  each owner  draw  button  in  the  dialog.
  1724. Remember  what the  button ID  is, as  that's what you'll  need to  know to
  1725. associate your owner draw control object to  this button.  You will need to
  1726. set the  size of the button to  be 32 x 20,  which is half the  size of the
  1727. actual bitmap  for Borland's standard VGA  bitmaps.  I'm assuming  that you
  1728. are  using Borland's Resource  Workshop to design  your dialog.   After you
  1729. have all of your buttons positioned where you want them and sized properly,
  1730. bring  up the control attributes dialog by double clicking on each control,
  1731. and set the control type  to owner draw button.   After this, you won't  be
  1732. able to  see the button  anymore, but it  will still be  there.   Save your
  1733. dialog.
  1734.  
  1735. 2.   DESIGN THE BUTTON  BITMAPS.  You can  use any Windows graphics program
  1736. that can save in BMP format to design the actual bitmaps.  The bitmaps will
  1737. use the  BWCC numbering scheme for  their names.  The  numbering scheme is:
  1738. normal = button ID + 1000, pressed = button ID + 3000, and focused = button
  1739. ID + 5000.  This means that if your button ID is 500, the bitmap number for
  1740. the  normal  button without  focus  will 1500,  for  pressed 3500,  and for
  1741. focused it  will be 5500.   These are the names  that you will  give to the
  1742. bitmaps when you  save them.   There is a  shareware program written  by N.
  1743. Waltham called Buttons that will automate this task for you by creating all
  1744. of the necessary bitmaps from one main bitmap.  It automatically  adds a 3D
  1745. shadow effect  and  has  some  other useful  features.    This  program  is
  1746. available  on  Compuserve  in the  BPASCAL  forum, and  the  author  can be
  1747. contacted at  100013.3330@compuserve.com.  Although you  must register with
  1748. the  author,  I don't  mind  sending copies  of  it via  Internet  email to
  1749. interested users.
  1750.  
  1751. 3.  ADD NECESSARY  CODE TO YOUR PROGRAM.   You will need to add  OwnDraw to
  1752. your USES  statement in  your program.   You will  also need  to make  your
  1753. dialog object a  descendant of TOwnerDialog.   The sample program  shows an
  1754. example of doing this.   The last thing to do will be to  use the NewButton
  1755. method in your  dialog's constructor  for each  owner draw  button in  that
  1756. dialog.  The NewButton  method is called with the button  ID, and a Boolean
  1757. True  or False depending on whether  or not you want that  button to be the
  1758.  
  1759.                                    - 28 -
  1760.  
  1761.  
  1762.  
  1763.  
  1764.  
  1765.  
  1766.  
  1767.  
  1768.  
  1769. default button in the dialog.  If this is  True, then it will be the button
  1770. drawn with focus when your dialog is initially created.
  1771.  
  1772.      That's all  there is to it.  When  your dialog is displayed, the owner
  1773. draw  buttons will  be  displayed along  with it.   Of  course, to  get the
  1774. buttons to do some useful function, you will need procedures in your dialog
  1775. object  to  respond  to   each  button  selection.    The   sample  program
  1776. demonstrates this too.
  1777.  
  1778.      As you can see, using bitmapped buttons in your programs  is not quite
  1779. as  difficult as it  may at  first look.   When properly  used, bitmaps can
  1780. really make a big difference in the look of your program.
  1781.  
  1782.      I welcome any comments you may have, good or bad.  I can be reached on
  1783. Compuserve at 71044,1653, on America OnLine at TSnoddy, and on the Internet
  1784. at tsnoddy@nyx.cs.du.edu.
  1785.  
  1786.  
  1787.  
  1788.  
  1789.  
  1790.  
  1791.  
  1792.  
  1793.  
  1794.  
  1795.  
  1796.  
  1797.  
  1798.  
  1799.  
  1800.  
  1801.  
  1802.  
  1803.  
  1804.  
  1805.  
  1806.  
  1807.  
  1808.  
  1809.  
  1810.  
  1811.  
  1812.  
  1813.  
  1814.  
  1815.  
  1816.  
  1817.  
  1818.  
  1819.  
  1820.  
  1821.  
  1822.                                    - 29 -
  1823.  
  1824.  
  1825.  
  1826.  
  1827.  
  1828.  
  1829.  
  1830.  
  1831.  
  1832.                                Hacker's Gash
  1833.                        by Mike Wallace and Pete Davis
  1834.  
  1835.      This is  our first attempt at  a tips/tricks column.   If you couldn't
  1836. figure out what the title meant, blame  Pete.  Here are three tricks  we've
  1837. come up with.  Hope  you like them.  If you have any you want to share with
  1838. our readers,  send them in!   Full  credit will  be given for  all tips  we
  1839. publish, of course.
  1840.  
  1841. 1) Menu bar on a dialog  box:  We spent a lot of time on  this one, but the
  1842. solution  turned out to be  a simple one.  In  the dialog box definition in
  1843. either (a) the .RC  file, or (b) a .DLG  file you include in the  .RC file,
  1844. throw in the lines:
  1845.  
  1846. STYLE WS_OVERLAPPEDWINDOW
  1847. MENU <menu name>
  1848.  
  1849. where <menu name> is  a menu you have defined  in the .RC file.   The style
  1850. WS_OVERLAPPEDWINDOW  combines  WS_OVERLAPPED,  WS_CAPTION,  WS_SYSMENU  and
  1851. WS_THICKFRAME, and is a standard parent window.
  1852.  
  1853.  
  1854. 2) Highlighting a  button on a  dialog box when the  cursor is over  it: By
  1855. highlight, I  mean the button moves  "in" slightly, and it's  a cool effect
  1856. that can  look pretty impressive.   Add the  following declarations to  the
  1857. function that controls the dialog box:
  1858.  
  1859.      static BOOL    ButtnDn;
  1860.      static int     LastDown;
  1861.      static int     IDNumber=0;
  1862.      int            counter;
  1863.  
  1864. Next, assume you have  5 buttons on your dialog box,  and these buttons are
  1865. identified by  the constants IDB_BUTNx (where  x is a number  between 1 and
  1866. 5),  which are  defined in  the .H  file for  the .DLG file  containing the
  1867. definition for  your dialog  box.   Also  assume these  five constants  are
  1868. defined  sequentially, say, 101 to 105.   The variable "hDlg" is the handle
  1869. to the dialog box,  and "message" is the message passed  to the function by
  1870. Windows  (these are, respectively,  the first and  second parameters passed
  1871. into a   standard window-handling  function).  Add the  following two cases
  1872. inside the "switch(message) {}" structure:
  1873.  
  1874. case WM_SETCURSOR:
  1875.  
  1876.      ButtnDn= TRUE;
  1877.      LastDown= IDNumber;
  1878.      IDNumber= GetDlgCtrlID(wParam);
  1879.      for(counter=IDB_BUTN1; counter<=IDB_BUTN5; counter++)
  1880.           if(counter==IDNumber) 
  1881.                SendDlgItemMessage(hDlg, counter, BM_SETSTATE, TRUE, 0L);
  1882.      if(IDNumber != LastDown)
  1883.           SendDlgItemMessage(hDlg, LastDown, BM_SETSTATE, FALSE, 0L);
  1884.  
  1885.                                    - 30 -
  1886.  
  1887.  
  1888.  
  1889.  
  1890.  
  1891.  
  1892.  
  1893.  
  1894.  
  1895.      break;
  1896.  
  1897. case WM_NCHITTEST:
  1898.  
  1899.      if (ButtnDn) {
  1900.           ButtnDn = FALSE;
  1901.           SendDlgItemMessage(hDlg, LastDown, BM_SETSTATE, FALSE, 0L);
  1902.      }
  1903.      break;
  1904.  
  1905.  
  1906. 3) Using  DlgDirList  for a  subdirectory:    I recently  tried  using  the
  1907. DlgDirList  function passing a pathname  like "DATA\\*.DAT" to  fill a list
  1908. box with all  the files in the DATA directory ending with .DAT (by the way,
  1909. the double backslash  ("\\") is needed because  the backslash is  a special
  1910. character in a C string).  Afterwards, I found out that if this function is
  1911. successful,  it changes  the  current  working  directory  to  whatever  is
  1912. specified in the string (here, the "DATA" directory).   Afterwards, I tried
  1913. changing the directory  back to the original working  directory, but if the
  1914. function got called again,  chaos ensued.  I managed to  get around this by
  1915. including in the program the following line:
  1916.  
  1917. #include <direct.h>
  1918.  
  1919.      The  direct.h  file  has  prototypes  for  the  directory-manipulating
  1920. functions,  including the one needed here: chdir, which changes the current
  1921. working directory.   I replaced the  call to DlgDirList with  the following
  1922. code:
  1923.  
  1924. chdir("DATA");
  1925. DlgDirList(hDlg, "*.DAT", IDL_POPUP, IDL_DIRSTRING, 0);
  1926. chdir("..");
  1927.  
  1928.      The  "hDlg" variable is  the handle to the  dialog box, "IDL_POPUP" is
  1929. the dialog  box ID value for  the list box control,  "IDL_DIRSTRING" is the
  1930. dialog box ID value for a static text control that will be updated with the
  1931. current path name, and 0 is the DOS file attribute used  to retrieve a list
  1932. of read/write files  with no other attributes set.   There are other values
  1933. you can  use for the  DOS file  attribute value to  retrieve, for  example,
  1934. subdirectories and drives.  This code simply changes to the DATA directory,
  1935. gets a list  of the .DAT files in that directory  and puts that list in the
  1936. IDL_POPUP list box and the  path in the IDL_DIRSTRING static  text control,
  1937. and  then  changes  back  to  the  parent  directory.    You  can  use  the
  1938. DlgDirSelect function to retrieve a selected filename from the list box.
  1939.  
  1940.  
  1941.  
  1942.  
  1943.  
  1944.  
  1945.  
  1946.  
  1947.  
  1948.                                    - 31 -
  1949.  
  1950.  
  1951.  
  1952.  
  1953.  
  1954.  
  1955.  
  1956.  
  1957.  
  1958.                                 Special News
  1959.                               by Mike Wallace
  1960.  
  1961.      We recently  got a letter from Todd Snoddy  offering to put WPJ on the
  1962. America Online  information system.  We  said "Sure", and a  couple of days
  1963. later got a phone call from a sysop  for the board.  He liked the  magazine
  1964. (all contributing authors can now pat themselves on the back) and asked how
  1965. we would feel  about holding an on-line conference on  AO, giving readers a
  1966. chance to talk directly to us and ask us any questions  they have about the
  1967. journal.  Sounded good to  us, so we agreed, and now hope to  bring this to
  1968. you  AO subscribers soon.  The details are still being worked out, so watch
  1969. the AO Programming Library for updates.  
  1970.  
  1971.      For  the benefit of our readers without access to America Online, I'll
  1972. try to write down what questions get asked and our answers and include them
  1973. in our next issue.
  1974.  
  1975.      Thanks, Todd, and the great folks at America Online!
  1976.  
  1977.  
  1978.  
  1979.  
  1980.  
  1981.  
  1982.  
  1983.  
  1984.  
  1985.  
  1986.  
  1987.  
  1988.  
  1989.  
  1990.  
  1991.  
  1992.  
  1993.  
  1994.  
  1995.  
  1996.  
  1997.  
  1998.  
  1999.  
  2000.  
  2001.  
  2002.  
  2003.  
  2004.  
  2005.  
  2006.  
  2007.  
  2008.  
  2009.  
  2010.  
  2011.                                    - 32 -
  2012.  
  2013.  
  2014.  
  2015.  
  2016.  
  2017.  
  2018.  
  2019.  
  2020.  
  2021.                 Windows 3.1: Using Version Stamping library
  2022.                               By Alex Fedorov
  2023.  
  2024.      Windows 3.1 introduces  the new resource type with ID number 16 called
  2025. VS_FILE_INFO.   This  resource contains  the symbolic  data about  the file
  2026. version, its description, the company name and so on.  Such information can
  2027. be used to determine the file type/subtype  and its version and can be used
  2028. in installation  programs.  Resource  data lives as  a set of  blocks; each
  2029. block  contains information of its own.   The first one (with a fixed size)
  2030. describes the TVS_FixedFileInfo  structure.  The  fields of this  structure
  2031. contain  the  following information:  file  version,  environment type  and
  2032. version, file type and subtype. There are several subblocks, which contains
  2033. symbolic  information with the following IDs (the table contains only those
  2034. which must present):
  2035.  
  2036.  
  2037.      ID                       Contents
  2038.  
  2039.      CompanyName              Company name
  2040.      FileDescription          File description
  2041.      FileVersion              File version
  2042.      InternalName             Internal file name
  2043.      ProductName              Product name
  2044.      ProductVersion           Product version
  2045.  
  2046.  
  2047.      Resource of  this type can  be created  with resource editor,  such as
  2048. Resource Workshop (version 1.02  or higher), or with resource  compiler (RC
  2049. or BRC).   The latter  needs the resource template.   VS_FILE_INFO resource
  2050. for resource compiler looks like the following:
  2051.  
  2052.  
  2053.       1 VERSIONINFO LOADONCALL MOVEABLE
  2054.  
  2055.       FILEVERSION        3,10,0,61
  2056.  
  2057.    PRODUCTVERSION     3,10,0,61
  2058.       FILEFLAGSMASK      VS_FF_DEBUG | VS_FF_PATCHED
  2059.       FILEFLAGS          VS_FF_DEBUG
  2060.       FILEOS             VOS__WINDOWS16
  2061.       FILETYPE           VFT_APP
  2062.       FILESUBTYPE        VFT2_UNKNOWN
  2063.       BEGIN
  2064.           BLOCK "StringFileInfo"
  2065.           BEGIN
  2066.               BLOCK "040904E4"
  2067.               BEGIN
  2068.                VALUE "CompanyName", "Microsoft Corporation\0"
  2069.                VALUE   "FileDescription",  "Windows   Terminal  application
  2070. file\0"
  2071.                VALUE "FileVersion", "3.10.061\0"
  2072.                VALUE "InternalName", "Terminal\0"
  2073.  
  2074.                                    - 33 -
  2075.  
  2076.  
  2077.  
  2078.  
  2079.  
  2080.  
  2081.  
  2082.  
  2083.  
  2084.                VALUE "LegalCopyright","Copyright \251Microsoft Corp.1991\0"
  2085.                VALUE "OriginalFilename", "TERMINAL.EXE\0"
  2086.                VALUE  "ProductName","Microsoft\256   Windows\231  Operating
  2087. System\0"
  2088.                VALUE "ProductVersion",  "3.10.061\0"
  2089.               END
  2090.           END
  2091.       END
  2092.  
  2093.  
  2094.      To get access to VS_FILE_INFO resource  data you can use the functions
  2095. from VER Dynamic Link Library included with Windows 3.1.
  2096.  
  2097.      The example below shows how to use some of those functions.
  2098.  
  2099.  
  2100.      {------------------------------------------------------
  2101.             FileVer: An example of VER.DLL functions usage
  2102.                       (c) Alex G. Fedorov, 1993
  2103.      ------------------------------------------------------}
  2104.  
  2105.      Program FileVer;
  2106.      uses WinCrt,WinTypes,WinProcs,Ver,Strings;
  2107.      Const
  2108.       {Any file name here}
  2109.       FileName = 'C:\WINDOWS\SYSTEM\VGA.DRV';
  2110.  
  2111.      Type
  2112.       PBuff    =   ^TBuff;
  2113.       TBuff    =   Array[0..127] of Char;
  2114.  
  2115.      Var
  2116.       Size     :   LongInt;
  2117.       hVer     :   LongInt;
  2118.       VerData  :   Array[0..463] of Byte;    {Avg. resource size}
  2119.       Buff     :   PBuff;
  2120.       NameStr  :   Array[0..127] of Char;
  2121.       Block    :   Array[0..7] of Char;
  2122.  
  2123.  
  2124.      Procedure ShowString(Name : PChar);
  2125.      {
  2126.        Show the string with description
  2127.      }
  2128.      Begin
  2129.      {
  2130.        Create query string
  2131.      }
  2132.       StrCopy(NameStr,'\StringFileInfo\');StrCat(NameStr,Block);
  2133.       StrCat(NameStr,'\'); StrCat(NameStr,Name);
  2134.      {
  2135.        Query for string
  2136.  
  2137.                                    - 34 -
  2138.  
  2139.  
  2140.  
  2141.  
  2142.  
  2143.  
  2144.  
  2145.  
  2146.  
  2147.      }
  2148.       If VerQueryValue(@VerData,NameStr,Pointer(Buff),Word(Size))
  2149.      {
  2150.        If string was found, show it
  2151.      }
  2152.        Then Writeln(Name,' ':20-StrLen(Name),Buff^)
  2153.      End;
  2154.  
  2155.      Begin
  2156.       {
  2157.         Get the resource size. hVer - Resource handle
  2158.       }
  2159.       Size := GetFileVersionInfoSize(FileName,hVer);
  2160.       {
  2161.         Get VS_VERSION_INFO block
  2162.       }
  2163.       GetFileVersionInfo(FileName,hVer,Size,@VerData);
  2164.       {
  2165.         Get the symbolic information block
  2166.         The default name for it: 040904E4
  2167.         The result is in Buff
  2168.       }
  2169.  
  2170.      VerQueryValue(@VerData,'\VarFileInfo\Translation',Pointer(Bu
  2171.      ff),Word(Size));
  2172.       {
  2173.         Translate the block name
  2174.       }
  2175.       wvsPrintf(Block,'%04X%04X',Buff^);
  2176.       If Size <> 0 Then
  2177.        Begin
  2178.         Writeln(^M^J'Version information for ',FileName);
  2179.         Writeln;
  2180.       {
  2181.         Query for the following IDs
  2182.       }
  2183.         ShowString('CompanyName');
  2184.         ShowString('FileDescription');
  2185.         ShowString('FileVersion');
  2186.         ShowString('ProductName')
  2187.        End;
  2188.       Readln; DoneWinCrt
  2189.      End.
  2190.  
  2191.  
  2192.  
  2193.  
  2194.  
  2195.  
  2196.  
  2197.  
  2198.  
  2199.  
  2200.                                    - 35 -
  2201.  
  2202.  
  2203.  
  2204.  
  2205.  
  2206.  
  2207.  
  2208.  
  2209.  
  2210.                                 Book Review
  2211.                                by Pete Davis
  2212.  
  2213.             Microsoft Windows 3.1 Programmer's Reference Library
  2214.  
  2215.      If  you're  a   serious  Windows  programmer,   you've  probably   got
  2216. Microsoft's series of books on Windows programming. The series is broken up
  2217. into 4 volumes and two extra books, as follows:
  2218.  
  2219. - Volume 1: Overview
  2220.      This  is, as it says, an overview. It covers a lot of different topics
  2221. from  how windows are  managed, graphics, and  how to  write extensions for
  2222. Control Panel and File Manager.
  2223.  
  2224. - Volume 2: Functions
  2225.      This is an alphabetical listing of all the Windows 3.1 functions. It's
  2226. a big one.
  2227.  
  2228. - Volume 3: Messages, Structures and Macros
  2229.      Like the previous  two, this one is  exactly what it says  it is. it's
  2230. got  some great  information on  all the  structures, which  comes  in real
  2231. handy.
  2232.  
  2233. - Volume 4: Resources
  2234.      This one  has a  lot of  information on  file formats  for a  bunch of
  2235. different Windows files (.GRP files, Meta Files,  etc.) It's also got a bit
  2236. of  information on creating Help files, using assembly language in Windows,
  2237. and more.
  2238.  
  2239. - Programming Tools
  2240.      Not  listed as  a Volume. This  is more  of an  additional Microsoft C
  2241. reference. It covers a lot of  the SDK tools, debugging with Codeview, data
  2242. compressions and that kind of stuff.
  2243.  
  2244. - Guide to Programming
  2245.      Also not listed as  a Volume, but I would have made  it one. It covers
  2246. different types of resources like Icons, Dialog boxes, etc. Printing, DLLs,
  2247. MDI, Fonts, Assembly and C, and more.
  2248.  
  2249.      Ok, so there's our list. I would say, all-in-all, this is probably the
  2250. most  definitive  resource  on  programming for  Windows  and  that  anyone
  2251. planning on doing a lot of Windows programming should get the whole series.
  2252. It's a big ticket  item. The least expensive book is $23  (US) and the most
  2253. expensive is $39 (US). 
  2254.  
  2255.      I  suggest you  get some other  books and  not limit  yourself to just
  2256. these.  Although they cover most of the topics most programmer's will need,
  2257. the books  are lacking in some  areas and there  are some typos  which have
  2258. slowed this programmer down on more than one occasion. Every book has typos
  2259. and that's why,  as a  serious programmer,  your library  should come  from
  2260. several sources so you can check the  books against each other when you run
  2261. into problems.
  2262.  
  2263.                                    - 36 -
  2264.  
  2265.  
  2266.  
  2267.  
  2268.  
  2269.  
  2270.  
  2271.  
  2272.  
  2273.      These books are, however, well worth the cost. There's a  lot of stuff
  2274. covered in these books that you won't find  in other sources. The detail on
  2275. different file formats, for example, I haven't seen in any other books. The
  2276. structures list  is very  complete and is  a very handy  reference. There's
  2277. also some good information on memory management and DLLs.
  2278.  
  2279.                           What these books aren't
  2280.  
  2281.      These books aren't everything and, like I said, don't limit yourselves
  2282. to just these.  The Windows API  bible (which Mike  will be reviewing)  has
  2283. some great information too and it's a good one to check against Microsoft's
  2284. Function  reference when  you get  suspicious that  a function  isn't doing
  2285. something it's supposed  to do.  Also, because it's  Microsoft, you're  not
  2286. going to get the wealth of inside, under-the-hood, information like you get
  2287. from Undocumented  Windows (Andrew  Schulman,  David Maxey,  Matt  Pietrek.
  2288. Addison-Wesley. See the review in WPJ Vol.1 No.1)
  2289.  
  2290.      Unfortunately,  because Windows  is  such an  enormous  system and  no
  2291. single book can even begin to cover every aspect of it, we programmers have
  2292. to get  a lot  of different  books. If you're  serious, though,  you should
  2293. consider setting aside the money to get this collection.
  2294.  
  2295.  
  2296.  
  2297.  
  2298.  
  2299.  
  2300.  
  2301.  
  2302.  
  2303.  
  2304.  
  2305.  
  2306.  
  2307.  
  2308.  
  2309.  
  2310.  
  2311.  
  2312.  
  2313.  
  2314.  
  2315.  
  2316.  
  2317.  
  2318.  
  2319.  
  2320.  
  2321.  
  2322.  
  2323.  
  2324.  
  2325.  
  2326.                                    - 37 -
  2327.  
  2328.  
  2329.  
  2330.  
  2331.  
  2332.  
  2333.  
  2334.  
  2335.  
  2336.                                 Book Review
  2337.                               by Mike Wallace
  2338.  
  2339.                     The Waite Group's Windows API Bible
  2340.                              by James L. Conger
  2341.  
  2342.      Earlier  in  this  issue  Pete  reviewed  the  Microsoft  Windows  3.1
  2343. Programmer's Reference  Library.   Now it's my  turn to  review my  Windows
  2344. programming reference book of  choice: The Waite Group's Windows  API Bible
  2345. by James Conger.   Pardon my  French, but this  book kicks serious  "lune".
  2346. While the Microsoft books  spread out everything across several  books, the
  2347. Waite Group threw it all into one book, making anything you want to look up
  2348. easy to find.  This book is a must-have  for anyone wanting to learn how to
  2349. program Windows.  Here are the details:
  2350.  
  2351.      The book is divided into 30  chapters, each discussing a distinct area
  2352. (e.g., creating windows, menus, windows messages, metafiles).  Each chapter
  2353. starts off  with a discussion  of its topic  (usually several  pages long),
  2354. then a  summary of the associated  functions (a list of  the function names
  2355. with  a one  line purpose),  and then  a complete  description of  the same
  2356. functions  in the  summary.   Their descriptions  are detailed  and include
  2357. suggested uses  for the function, a  great "see also" reference  list (more
  2358. complete  than the Microsoft books),  an explanation of  all the parameters
  2359. and  the return value, and  code (real programs) that shows  how to use the
  2360. function.   It's my one  stop for looking up a  function.  Plus, the inside
  2361. front cover contains a list of all the API  functions with a page number so
  2362. I can quickly jump to the function I want to look up, followed by a list of
  2363. messages grouped  by category  (e.g.,  button notification  codes,  windows
  2364. messages).
  2365.  
  2366.      But wait, there's  more...the book also  includes a pullout  reference
  2367. card containing a list of all  functions with the type declarations for all
  2368. parameters and the return value,  plus the same list of messages  that's on
  2369. the inside  cover.  The function list in the  pullout card is organized the
  2370. same  way as the chapters, so that  all the functions listed in the chapter
  2371. on, say,  sound functions, are  grouped together in  the pullout under  the
  2372. heading "Sound  Functions."   I couldn't  ask for a  better list.   In  the
  2373. Microsoft books, you have  to know the name of the  function before you can
  2374. look it up,  and there  have been many  times when I  didn't know what  the
  2375. function was called, but  I knew what it did.   This book lets me  find the
  2376. name of the function quickly, and then shows me how to use it.
  2377.  
  2378.      In  the  appendices,  there is  a  discussion  of  useful macros  from
  2379. WINDOWS.H, mouse  test hit codes,  and a  printout of WINDOWS.H,  which has
  2380. been  very helpful to me on  occasion.  The book ends  with a very complete
  2381. index, making it extremely easy to look up related subjects.
  2382.  
  2383.      There are very few faults with this book, but there a few.  One is the
  2384. strange absence of the Windows API  functions starting with the letter "N".
  2385. I am not making  this up.  The Microsoft reference book  on functions lists
  2386. four functions starting  with "N": NetBIOSCall,  NotifyProc, NotifyRegister
  2387. and NotifyUnRegister.  None of these functions are in the API Bible.  There
  2388.  
  2389.                                    - 38 -
  2390.  
  2391.  
  2392.  
  2393.  
  2394.  
  2395.  
  2396.  
  2397.  
  2398.  
  2399. is also no discussion of OLE or True Type fonts, although the author writes
  2400. (in the introduction) these will be covered in a separate  volume currently
  2401. under development.
  2402.  
  2403.  
  2404.      Also, the Microsoft volume on  programming tools (those included  with
  2405. the SDK, such as  Spy) covers an area not  addressed by the API Bible.   If
  2406. you want to use the Microsoft SDK, the API Bible won't be of much help, but
  2407. this isn't  much of a fault,  because Microsoft isn't the  only producer of
  2408. Windows SDKs, and you're going  to get manuals for any SDK you  buy, so why
  2409. include a lot of information that some readers won't need to know?
  2410.  
  2411.      To  me,  the faults  of  this book  do  not detract  from  its overall
  2412. usefulness.  It has paid for  itself many times over, and when compared  to
  2413. the  Microsoft reference books, the price seems insignificant.  Total price
  2414. for  the  six books  in the  Microsoft  Windows 3.1  Programmer's Reference
  2415. Library: around  $170-180.  The Windows  API Bible: $40.   I recommend this
  2416. book.  You'll thank me for it.
  2417.  
  2418.  
  2419.  
  2420.  
  2421.  
  2422.  
  2423.  
  2424.  
  2425.  
  2426.  
  2427.  
  2428.  
  2429.  
  2430.  
  2431.  
  2432.  
  2433.  
  2434.  
  2435.  
  2436.  
  2437.  
  2438.  
  2439.  
  2440.  
  2441.  
  2442.  
  2443.  
  2444.  
  2445.  
  2446.  
  2447.  
  2448.  
  2449.  
  2450.  
  2451.  
  2452.                                    - 39 -
  2453.  
  2454.  
  2455.  
  2456.  
  2457.  
  2458.  
  2459.  
  2460.  
  2461.  
  2462.                             Printing in Windows
  2463.                                by Pete Davis
  2464.  
  2465.      Ok, I  failed to get this article out  last time and I've been feeling
  2466. really guilty  about it, so  here it is.  Writing this article  hasn't been
  2467. easy. Writing about  printing reminds me  of my  first attempt at  printing
  2468. under Windows. It wasn't a very good one.
  2469.  
  2470.      Actually,  printing under  Windows  isn't a  big  deal and  if  you're
  2471. printing graphics, it's a lot easier than you'd think. My real problem with
  2472. printing  the first  time was  incomplete/incorrect documentation.  I won't
  2473. mention names, but I saw at least 3 examples of printing in different books
  2474. on  Windows programming and every one of them  either misled me or had code
  2475. that didn't work.
  2476.  
  2477.      Here's the deal. When you print, you MUST, and I mean MUST (I'm really
  2478. serious  about  this),  have  an  abort  procedure.  This  is  an  absolute
  2479. necessity. You  MUST have it. You can't do without it. You'll have problems
  2480. if you don't have an abort procedure, so write one. Are you starting to get
  2481. the picture here? An abort procedure is NOT optional, you MUST have it. 
  2482.  
  2483.      Now, you might be wondering why I'm emphasizing this point. Every book
  2484. I read on printing made it sound like an option. I needed to print one page
  2485. of text  (and not much text  at that) and didn't  have a need  for an abort
  2486. procedure, so I didn't include  one. I kept getting UAEs as soon as I tried
  2487. to print. It even occurred to me that I might need the Abort procedure, and
  2488. I specifically looked  for a phrase which said something  along those lines
  2489. in every bit of documentation  I had. Finally, 4 days later,  I decided I'd
  2490. just pop an abort procedure in. (I had tried just about everything else  at
  2491. that point,  why not?) Of  course, it worked,  and I  had wasted four  days
  2492. because I HAD TO HAVE AN ABORT PROCEDURE!!!! 
  2493.  
  2494.      So, I'm  assuming that, after reading  this, none of you  are going to
  2495. spend four days  trying to figure out why  your print routine UAEs  only to
  2496. find that you  forgot your abort procedure. If  I hear of any of  you doing
  2497. this after reading this article, I'm going to come over and personally give
  2498. you a whuppin (as we say down in Arkansas). If you  haven't, at this point,
  2499. realized that an  abort procedure might possibly be a  good idea to include
  2500. in  your  print  procedure,  you  fit  into  one  of  the  following  three
  2501. categories:
  2502.  
  2503.      1> You program on the cutting edge of Atari 2600 technology.
  2504.         (or some other cutting edge of obsolescence)
  2505.      2> Blind and not able to read any of this anyway.
  2506.      3> Just plain stupid
  2507.  
  2508.      There, so I have that  off my chest, on  to the meat of this  article,
  2509. which is how to print. It's real easy!!!
  2510.  
  2511.      The first thing  we'll look at is our Abort  procedure. (Did I mention
  2512. that you need this part?)  The abort procedure is real simple.  It receives
  2513. the device  context of the printer and  an error code. In  our example, the
  2514.  
  2515.                                    - 40 -
  2516.  
  2517.  
  2518.  
  2519.  
  2520.  
  2521.  
  2522.  
  2523.  
  2524.  
  2525. nCode is the error code and if it's non-zero, you have an error.
  2526.  
  2527.      Basically all  your abort procedure has  to do have a  message loop. I
  2528. don't do anything else with it, myself. It should look something like this.
  2529.  
  2530.  
  2531. BOOL FAR PASCAL AbortProc(HDC hdcPrint, short nCode) {
  2532.  
  2533. MSG msg;
  2534.  
  2535.    while(!bPrintAbort && PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
  2536.        if(!hDlgPrint || !IsDialogMessage (hDlgPrint, &msg)) {
  2537.            TranslateMessage(&msg);
  2538.            DispatchMessage(&msg);
  2539.        }
  2540.    }
  2541.    return !bPrintAbort;
  2542. }
  2543.  
  2544.  
  2545.      The next thing on  our agenda is the  PrintDlgProc procedure. This  is
  2546. basically  the dialog  box procedure  for our  cancel printing  dialog box.
  2547. Although there  is a cancel button  in our dialog box,  instead of checking
  2548. specifically for the cancel button, our only concern is with  a WM_COMMAND.
  2549. (We only have  one button, so what  other WM_COMMANDS are they  going to be
  2550. sending?) If  we get  the WM_COMMAND, we  basically abort the  printout (by
  2551. setting bPrintAbort = TRUE) and destroy our dialog box. Pretty darn simple.
  2552.  
  2553.  
  2554. BOOL FAR  PASCAL PrintDlgProc(HWND  hDlg, WORD message,  WORD wParam,  LONG
  2555. lParam)
  2556. {
  2557.      if (message == WM_COMMAND) {
  2558.           bPrintAbort = TRUE;
  2559.           EnableWindow(GetParent(hDlg), TRUE);
  2560.           DestroyWindow(hDlg);
  2561.           hDlgPrint = 0;
  2562.           return TRUE;
  2563.      }
  2564.  
  2565.      return FALSE;
  2566.  
  2567. }
  2568.  
  2569.  
  2570.      The next procedure  is our printing  procedure. There are a  couple of
  2571. neat  things here that  I'd like to  talk about. First  of all, there's the
  2572. four lines involved with getting information about our printer driver. What
  2573. we're doing here  is just getting  the default printer  device. We get  the
  2574. information via a GetProfileString command. Under the section "windows" and
  2575. then  the  line  starting  with  "device".  That's  our  printer.  All  the
  2576. information  goes into  szPrinter  which we  then  break into  the  Device,
  2577.  
  2578.                                    - 41 -
  2579.  
  2580.  
  2581.  
  2582.  
  2583.  
  2584.  
  2585.  
  2586.  
  2587.  
  2588. Driver, and PortName using  the strtok function. This basically  breaks out
  2589. our string into  separate strings. The  second parameter of  strtok is  the
  2590. thing we want to break our string apart at. In this case, we want it broken
  2591. up at  the commas. Also, notice how we only  pass szPrinter once. If we use
  2592. NULL in  the next  two occurrences  of strtok, it  knows to  continue using
  2593. szPrinter and to continue from where we left off which, in this case, is at
  2594. the last comma.
  2595.  
  2596.  
  2597. GetProfileString("windows", "device", ",,,", szPrinter, 80);
  2598. lpDevice = strtok(szPrinter, ",");
  2599. lpDriver = strtok(NULL, ",");
  2600. lpPortName = strtok(NULL, ",");
  2601.  
  2602.  
  2603.      Our  next job is pretty simple, we  just create a device context based
  2604. on the information about our driver. 
  2605.  
  2606. /* Create the device context. */
  2607. if ((hdcPrint = CreateDC(lpDriver, lpDevice, lpPortName, NULL)) == NULL) {
  2608.       MessageBox(hWnd, "Could not assign printer.", "ERROR", MB_OK);
  2609.       return FALSE;
  2610. }
  2611.  
  2612.      Here's  another thing you don't really have  to deal with in DOS. With
  2613. Windows, you need to know where you are on the page. The reason is that you
  2614. have to tell Windows when you're done with the current page. This means you
  2615. need to  know the size of a page and the size of the text on the page. Now,
  2616. this can be a  bit of a pain, but it  also allows for a lot  of interesting
  2617. stuff. For example, you don't have to start from the top of the page and go
  2618. down. You can print the bottom part  of the page, and then print  something
  2619. in the middle  of the page, and so on. Then  you just tell Windows to eject
  2620. the page. In our  case, we're going to be  assuming 1 page or less  of text
  2621. and not really  deal with that. (As they say in  school books, this is left
  2622. as an exercise for the reader.) All we're going to do is use the  text size
  2623. to tell us  where to  print the next  line of  text. First we  have to  use
  2624. CurYPos as our current Y position on the page. (I love descriptive variable
  2625. names.) Then  we need to  find out how tall  a single character  is. That's
  2626. done from  getting a text metric for the printer and adding the character's
  2627. height, plus  the external leading, which  is kind of like  the blank space
  2628. between lines.
  2629.  
  2630. CurYPos = 1;
  2631. GetTextMetrics(hdcPrint, &tm);
  2632. yChar = tm.tmHeight + tm.tmExternalLeading;
  2633.  
  2634.  
  2635.      The next step is  to create our dialog box and abort procedure. By the
  2636. way, the abort procedure is not optional.
  2637.  
  2638. lpfnPrintDlgProc = MakeProcInstance (PrintDlgProc, hInst);
  2639. hDlgPrint = CreateDialog(hInst, "PrintDlgBox", hWnd, lpfnPrintDlgProc);
  2640.  
  2641.                                    - 42 -
  2642.  
  2643.  
  2644.  
  2645.  
  2646.  
  2647.  
  2648.  
  2649.  
  2650.  
  2651. lpfnAbortProc = MakeProcInstance(AbortProc, hInst);
  2652. bPrintAbort = FALSE;
  2653.  
  2654.      Now, the secret  to printing is all in the Escape commands. (Actually,
  2655. in Windows 3.1, they have commands you can use instead of Escape, but since
  2656. we try to  maintain our  3.0 backwards  compatibility, we're  going to  use
  2657. Escape.) The Escape  is the heart of  the printing routines. You  use it to
  2658. send different commands and/or  data to the  printer driver. The first  one
  2659. we'll  send is  our SETABORTPROC  command. This  sets up  our all-important
  2660. abort procedure. 
  2661.  
  2662. Escape(hdcPrint, SETABORTPROC, 0, (LPSTR) lpfnAbortProc, NULL);
  2663.  
  2664.      The next  Escape we're going  to send is  our STARTDOC command.  We're
  2665. going to pass  the name of our  document and the length of  the string that
  2666. has the name of our document. Pretty straight-forward.
  2667.  
  2668. Escape(hdcPrint, STARTDOC, strlen(szMsg), (LPSTR) szMsg, NULL);
  2669.  
  2670.      Since our  example involves reading from  a file, we're  just going to
  2671. read one line at  a time from the file. We use a TextOut function to output
  2672. our string to the  printer driver. We need to give X  and Y coordinates. In
  2673. our  case, the  X is always  0 and  the Y  position is calculated  from the
  2674. GetTextMetric  which  we did  above. We  just add  the  text height  to the
  2675. current Y position after each line.
  2676.  
  2677. while (fgets(CurLine, 132, inFile)!=NULL) {
  2678.     TextOut(hdcPrint, 0, CurYPos, CurLine, strlen(CurLine)-2);
  2679.     CurYPos += yChar;
  2680. }
  2681.  
  2682.      When we're  done, we need  to send a  NEWFRAME command via  the Escape
  2683. function. This  basically means to advance to the next page. Now, if you're
  2684. printing multiple  pages, you need to  do a NEWFRAME between  each page. In
  2685. our case, we're only allowing one pages, so we do it right at the end.
  2686.  
  2687. Escape(hdcPrint, NEWFRAME, 0, NULL, NULL);
  2688.  
  2689.      The  last thing  to do  is send  an ENDDOC.  This basically  tells the
  2690. printer driver we're done and it can begin printing the document.
  2691.  
  2692. Escape(hdcPrint, ENDDOC, 0, NULL, NULL);
  2693.  
  2694.      After all that,  we just close the file and  delete the device context
  2695. for our printer, then get a cup of coffee and a smoke and relax.
  2696.  
  2697. fclose(inFile);
  2698. DeleteDC(hdcPrint);
  2699.  
  2700.      Ok, so  it's a little more  complex than printing text  under DOS, but
  2701. the  thing I didn't mention is the graphics.  Now, I'm not going to go into
  2702. great  detail about it, because it is a  little more complex, but not much.
  2703.  
  2704.                                    - 43 -
  2705.  
  2706.  
  2707.  
  2708.  
  2709.  
  2710.  
  2711.  
  2712.  
  2713.  
  2714. Essentially, all you have to do is  route all of your graphics to a printer
  2715. device context instead of a screen device context. The complexity  comes in
  2716. when you're trying to  figure out pages sizes and  that kind of stuff,  but
  2717. for the most part, printing graphics is as easy as printing text.
  2718.  
  2719.      There are some things  we didn't cover  here. One of  them is a  thing
  2720. called banding, which is a way of printing your page in parts called bands.
  2721. This  is  particularly  useful  for  dot-matrix  and  other  non-postscript
  2722. printers. Banding  makes it a bit  faster to do the  printing. Some printer
  2723. drivers have the banding built-in and I, personally, have never had to work
  2724. in  an environment  where printing  had to  be particularly  fast, so  I've
  2725. avoided banding. If someone feels that banding is of particular importance,
  2726. they're more than welcome to write an article on it. 
  2727.  
  2728.      That just about wraps up printing.  It's really not all that  complex,
  2729. and as  you can see, Windows gives  you a lot of power  as to how to handle
  2730. it.  There are all kinds of  neat and nifty things you  can do, like mixing
  2731. graphics  and text, which is  a cinch. Try doing that  in DOS. And last but
  2732. not least, please, don't forget your abort procedure.
  2733.  
  2734.  
  2735.  
  2736.  
  2737.  
  2738.  
  2739.  
  2740.  
  2741.  
  2742.  
  2743.  
  2744.  
  2745.  
  2746.  
  2747.  
  2748.  
  2749.  
  2750.  
  2751.  
  2752.  
  2753.  
  2754.  
  2755.  
  2756.  
  2757.  
  2758.  
  2759.  
  2760.  
  2761.  
  2762.  
  2763.  
  2764.  
  2765.  
  2766.  
  2767.                                    - 44 -
  2768.  
  2769.  
  2770.  
  2771.  
  2772.  
  2773.  
  2774.  
  2775.  
  2776.  
  2777.                           Advanced C++ and Windows
  2778.                              By Andrew Bradnan
  2779.  
  2780. Talking to the User
  2781. Overview
  2782.  
  2783. While Windows offers a great variety of  ways to talk to the user, none  of
  2784. them are very easy.  Even fewer are quick to implement.  In this article we
  2785. will look at some C++ classes to make things a little easier.
  2786.  
  2787. Output
  2788. Introduction
  2789.  
  2790. Let's look at a few ways to talk to the user.  The simplest way to tell the
  2791. user something  is to create  a message  box.  This  is one of  the easiest
  2792. things to  do in Windows.  Luckily,  Windows does most of  the work for us.
  2793. We are  going to make  it even easier.   With the  classes we are  going to
  2794. create you  will never have to remember what the parameters are, what order
  2795. they go  in, and exactly how  they spelled all the constant  values you can
  2796. pass in and that MessageBox() passes back.  We also need a good way to tell
  2797. the user and ourselves about error messages (God forbid).
  2798.  
  2799. Message Boxes
  2800.  
  2801. If you  are at all familiar with the MessageBox  () call you are well aware
  2802. that there are  several different incantations.   Each has its  own purpose
  2803. and can  be used for several  circumstances.  Its function  prototype looks
  2804. like  this:   int  MessageBox  (HWND  hwndParent,   LPSTR  lpszText,  LPSTR
  2805. lpszTitle,   UINT  fuStyle);  You  can  pass  it  18  different  flags  (or
  2806. combinations) and it will return seven different flags to tell you what the
  2807. user did.  Not as ugly as CreateWindow() but not easy either.
  2808.  
  2809. OKBOX
  2810.  
  2811. A stream is an abstraction referring to the flow of data from a producer to
  2812. a  consumer. The first  output object we  are going to create  is an OKBOX.
  2813. Once again it does exactly what it sounds like.  It  displays a message box
  2814. with  one  "OK"  button.   The  OKBOX  will  allow  us to  use  C++  stream
  2815. conventions.  Let's first look at  how we would like to  use an OKBOX so we
  2816. can write the appropriate member functions.
  2817.  
  2818. OKTEST.H
  2819.  
  2820. //
  2821. //   OKBOX Test Header
  2822. //   Andrew Bradnan (c) 1992
  2823. //   C++ Windows Version
  2824. //
  2825.  
  2826. #ifndef __OBJECTS_H
  2827. #include <objects.h>
  2828. #endif
  2829.  
  2830.                                    - 45 -
  2831.  
  2832.  
  2833.  
  2834.  
  2835.  
  2836.  
  2837.  
  2838.  
  2839.  
  2840. class WINDOW : public BASEWINDOW {
  2841. public:
  2842.      WINDOW    (LPCSTR    lpcszClass,   HINSTANCE    hInstance,   HINSTANCE
  2843. hPrevInstance,                     LPSTR lpszCmdLine, int nCmdShow)   :
  2844. BASEWINDOW (lpszClass, hInstance, hPrevInstance, lpszCmdLine, nCmdShow) {};
  2845.  
  2846.      BOOL OnCreate (CREATESTRUCT FAR *lpCreateStruct);
  2847. };
  2848.  
  2849. OKTEST.CPP
  2850.  
  2851. //
  2852. //   OKBOX Test Module
  2853. //   Andrew Bradnan (c) 1992
  2854. //   C++ Windows Version
  2855. //
  2856.  
  2857. #include <oktest.h>
  2858.  
  2859. BOOL WINDOW::OnCreate:: (CREATESTRUCT FAR *lpCreateStruct)
  2860. {
  2861.      OKBOX msg ("OK Test Caption");
  2862.      msg << "User notification."
  2863.      return TRUE;
  2864. };
  2865.  
  2866. int  PASCAL   WinMain  (HANDLE   hInstance,  HANDLE   hPrevInstance,  LPSTR
  2867. lpszCmdLine, int nCmdShow) {
  2868.      WINDOW  Window  ("OK  Test",  hInstance,  hPrevInstance,  lpszCmdLine,
  2869. nCmdLine);
  2870.      return Window.MessageLoop ();
  2871. };
  2872.  
  2873.      We  are  using  our WINDOW  virtual  function  to  trap the  WM_CREATE
  2874. message.  Here we define msg as an OKBOX with the caption "OK Test Caption"
  2875. When we want to send a message to the user we just  stream some text to it.
  2876. This  will invoke the message box with the "User notification."  It is much
  2877. easier to read. Now that we have an easy way to send  a message to the user
  2878. or ourselves let's look in the "black box" to see how OKBOX works.
  2879.  
  2880. OKBOX.H
  2881.  
  2882. //
  2883. //   OKBOX Header
  2884. //   Andrew Bradnan (c) 1992
  2885. //   C++ Windows Version
  2886. //
  2887.  
  2888. #ifndef __OKBOX_H
  2889. #define __OKBOX_H
  2890.  
  2891. #ifndef __WINSTR_H
  2892.  
  2893.                                    - 46 -
  2894.  
  2895.  
  2896.  
  2897.  
  2898.  
  2899.  
  2900.  
  2901.  
  2902.  
  2903. #include <winstr.h>
  2904. #endif
  2905.  
  2906. class OKBOX {
  2907. public:
  2908.      OKBOX (LPSTR lpszCaption);
  2909.      int operator<< (STRING& strMessage);
  2910. private:
  2911.      STRING strCaption;
  2912. };
  2913.  
  2914. #endif // __OKBOX_H
  2915.  
  2916. OKBOX.CPP
  2917.  
  2918. //
  2919. //   OKBOX Module
  2920. //   Andrew Bradnan (c) 1992
  2921. //   C++ Windows Version
  2922. //
  2923.  
  2924. #include <okbox.h>
  2925.  
  2926. OKBOX::OKBOX (LPSTR lpszCaption)
  2927. : strCaption (lpszCaption)
  2928. {};
  2929.  
  2930. int OKBOX::operator<< (STRING strMessage)
  2931. {
  2932.      return MessageBox (NULL, strMessage, strCaption, MB_OK);
  2933. };
  2934.  
  2935. Short and sweet.  We have  a constructor that takes a string which  we will
  2936. save as the caption for the message box.  Our  other function, operator<<()
  2937. is called by the compiler when ever it sees an OKBOX object on the  left of
  2938. the << and a STRING (or an object that can construct a STRING like a LPSTR)
  2939. object on the right side.
  2940.  
  2941. ERRORS
  2942.  
  2943. Unfortunately, errors  are bound to  occur.  We  will assume these  are all
  2944. going to be user errors.  If we think a bit, an ERROR object is going to be
  2945. coded  almost exactly  like an  OKBOX.   In fact,  let's rewrite  our OKBOX
  2946. object so that we can take advantage of this.  Quality if free, but we have
  2947. to pay for  it up front.  So that  we can reuse  some of the  code we  will
  2948. create an object call  MSGBOX.  MSGBOX will contain the  code shared by the
  2949. OKBOX and the ERROR object.
  2950.  
  2951. MSGBOX.H
  2952.  
  2953. //
  2954. //   MSGBOX Object Header
  2955.  
  2956.                                    - 47 -
  2957.  
  2958.  
  2959.  
  2960.  
  2961.  
  2962.  
  2963.  
  2964.  
  2965.  
  2966. //   Andrew Bradnan (c) 1992
  2967. //   C++ Windows Version
  2968. //
  2969.  
  2970. #ifndef __MSGBOX_H
  2971. #define __MSGBOX_H
  2972.  
  2973. #ifndef __WINSTR_H
  2974. #include <winstr.h>
  2975. #endif
  2976.  
  2977. class MSGBOX {
  2978. public:
  2979.      MSGBOX (LPSTR lpszCaption)
  2980.      : strCaption (lpszCaption), fuStyle (Style)
  2981.      {};
  2982.      int operator<< (STRING& strMessage)
  2983.      {
  2984.           return MessageBox (NULL, strOutput, strCaption, fuStyle);
  2985.      };
  2986. private:
  2987.      STRING strCaption;
  2988.      UINT fuStyle;
  2989. };
  2990.  
  2991. #endif // __MSGBOX_H
  2992.  
  2993.      The  MSGBOX object only adds  fuStyle so that  different message boxes
  2994. can be created. Now we can rewrite OKBOX to use the code in MSGBOX.   OKBOX
  2995. will be  inherited from  MSGBOX.    Since  we will  inherit all the  public
  2996. member functions, we will only have to write a short constructor.
  2997.  
  2998. OKBOX.H
  2999.  
  3000. //
  3001. //   OKBOX Object Header
  3002. //   Andrew Bradnan (c) 1992
  3003. //   C++ Windows Version
  3004. //
  3005.  
  3006. #ifndef __OKBOX_H
  3007. #define __OKBOX_H
  3008.  
  3009. class OKBOX : public MSGBOX {
  3010. public:
  3011.      OKBOX (LPSTR lpszCaption) : MSGBOX (lpszCation, MB_OK) {};
  3012. };
  3013.  
  3014. #endif // __OKBOX_H
  3015.  
  3016. The code  for an ERROR object is  exactly the same except  for the value of
  3017. fuStyle.
  3018.  
  3019.                                    - 48 -
  3020.  
  3021.  
  3022.  
  3023.  
  3024.  
  3025.  
  3026.  
  3027.  
  3028.  
  3029. ERROR.H
  3030.  
  3031. //
  3032. //   ERROR Object Header
  3033. //   Andrew Bradnan (c) 1992
  3034. //   C++ Windows Version
  3035. //
  3036.  
  3037. #ifndef __ERROR_H
  3038. #define __ERROR_H
  3039.  
  3040. class ERROR : public MSGBOX {
  3041. public:
  3042.      ERROR (LPSTR lpszCaption) : MSGBOX (lpszCation, MB_ICONSTOP | 
  3043.                MB_SYSTEMMODAL | MB_OK) {}; };
  3044.  
  3045. #endif // __ERROR_H
  3046.  
  3047. Great!  Now we can notify the user using the OKBOX object and tell the user
  3048. about errors using the ERROR object.
  3049.  
  3050. Questions
  3051.  
  3052. Your programs often need to ask the user  a question.  If this is a yes  or
  3053. no affair we can use a message box.  Let's look at how we would like to use
  3054. an object like this.  Then  we will write the member functions to  fill out
  3055. the class.
  3056.  
  3057. QTEST.H
  3058.  
  3059. //
  3060. //   QUESTION Test Header
  3061. //   Andrew Bradnan (c) 1992
  3062. //   C++ Windows Version
  3063. //
  3064.  
  3065. #ifndef __OBJECTS_H
  3066. #include <objects.h>
  3067. #endif
  3068.  
  3069. class WINDOW : public BASEWINDOW {
  3070. public:
  3071.      WINDOW    (LPCSTR    lpcszClass,   HINSTANCE    hInstance,   HINSTANCE
  3072. hPrevInstance,                     LPSTR lpszCmdLine, int nCmdShow)   :
  3073. BASEWINDOW (lpszClass, hInstance, hPrevInstance, lpszCmdLine, nCmdShow) {};
  3074.  
  3075.      BOOL OnCreate (CREATESTRUCT FAR *lpCreateStruct);
  3076. };
  3077.  
  3078. QTEST.CPP
  3079.  
  3080. //
  3081.  
  3082.                                    - 49 -
  3083.  
  3084.  
  3085.  
  3086.  
  3087.  
  3088.  
  3089.  
  3090.  
  3091.  
  3092. //   QUESTION Test Header
  3093. //   Andrew Bradnan (c) 1992
  3094. //   C++ Windows Version
  3095. //
  3096.  
  3097. #include <qtest.h>
  3098.  
  3099. BOOL OnCreate (CREATESTRUCT FAR * lpCreateStruct)
  3100. {
  3101.      OKBOX ("Question Test");
  3102.      if ((QUESTION) "Do you like the object?")
  3103.           msg << "Great, I hope your programming is easier."
  3104.      else
  3105.           msg >> "What the hell do you know!";
  3106.  
  3107.      return TRUE;
  3108. };
  3109.  
  3110. int   PASCAL  WinMain  (HANDLE   hInstance,  HANDLE   hPrevInstance,  LPSTR
  3111. lpszCmdLine, int nCmdShow) {
  3112.      WINDOW Window ("Question Test", hInstance, hPrevInstance, lpszCmdLine,
  3113. nCmdLine);
  3114.      return Window.MessageLoop ();
  3115. };
  3116.  
  3117. Once  again  we are  trapping  the  WM_CREATE message  and  trying out  our
  3118. QUESTION object.  The  code is kind of sneaky.   I know one thing,  you can
  3119. actually read C++ code if  you write your classes carefully.   I would hate
  3120. to  write those four lines  of code in  C.  They certainly  would not be as
  3121. easy to read. Let's look at how the  C++ compiler helps us.  First of  all,
  3122. when  the compiler sees that we would like to cast the string to a QUESTION
  3123. it creates a temporary QUESTION object using our string.  Then since the if
  3124. statement really  wants a BOOL  the compiler  will cast the  QUESTION to  a
  3125. BOOL.  It is  in this explicit cast, operator  BOOL (), that will  make the
  3126. message box call.   The cast member function will "look" to see if the user
  3127. hit the "Yes" or "No" button, and return TRUE or FALSE.
  3128.  
  3129. If you  didn't understand  that, let me  explain it another  way.   The C++
  3130. compiler did all the work. Seeing as how I'm trying to  champion code reuse
  3131. let's try and use  the MSGBOX object.  We  are only going to have  to write
  3132. two member functions for this object.   The constructor and the cast member
  3133. function.
  3134.  
  3135. QUESTION.H
  3136.  
  3137. //
  3138. //   QUESTION Object Header
  3139. //   Andrew Bradnan (c) 1992
  3140. //   C++ Windows Version
  3141. //
  3142.  
  3143. #ifndef __QUESTION_H
  3144.  
  3145.                                    - 50 -
  3146.  
  3147.  
  3148.  
  3149.  
  3150.  
  3151.  
  3152.  
  3153.  
  3154.  
  3155. #define __QUESTION_H
  3156.  
  3157. class QUESTION : public QUESTION {
  3158. public:
  3159.      QUESTION (LPSTR lpszOutput)
  3160.      : MSGBOX ("Warning!", MB_QUESTION  | MB_YESNO), strOutput (lpszOutput)
  3161. {};
  3162.  
  3163.      operator BOOL (void) 
  3164.      {
  3165.           if (IDYES == operator<< (strOutput))
  3166.                return TRUE;
  3167.           else 
  3168.                return FALSE;
  3169.      };
  3170. private:
  3171.      STRING strOutput;
  3172. };
  3173.  
  3174. #endif // QUESTION_H
  3175.  
  3176.      As  you can  see the only  action really  takes place  in the explicit
  3177. cast, operator  BOOL  ().   It is  here that  we call  the member  function
  3178. operator<<  () to invoke the message  box call.  We didn't  do too much but
  3179. transform one  call to the  MSGBOX class.  Since  we declared it  inline we
  3180. won't even gain any function call overhead.  The C++ compiler will put this
  3181. ugly  code right where  our beautiful code  exists right now.   Pretty damn
  3182. cool.
  3183.  
  3184. Warnings
  3185.  
  3186.      We  also might  want to  warn the  user of impending  doom.   The File
  3187. Manager uses this to confirm that you really want  to overwrite a file.  If
  3188. works  almost like a QUESTION object except  it can return whether the user
  3189. chose "Yes", "No" or "Cancel".
  3190.  
  3191. WARNING.H
  3192.  
  3193. //
  3194. //   WARNING Object Header
  3195. //   Andrew Bradnan (c) 1992
  3196. //   C++ Windows Version
  3197. //
  3198.  
  3199. #ifndef __WARNING_H
  3200. #define __WARNING_H
  3201.  
  3202. class WARNING : public MSGBOX {
  3203. public:
  3204.      WARNING (LPSTR lpszOutput)
  3205.      : MSGBOX ("Warning!", MB_EXCLAMATION | MB_YESNOCANCEL) {};
  3206.  
  3207.  
  3208.                                    - 51 -
  3209.  
  3210.  
  3211.  
  3212.  
  3213.  
  3214.  
  3215.  
  3216.  
  3217.  
  3218.      operator BOOL (void) 
  3219.      {
  3220.           BOOL fUserChoice;
  3221.           fUserChoice = operator<< (strOutput);
  3222.           if (fUserChoice == IDYES)
  3223.                return TRUE;
  3224.           else if (fUserChoice == IDNO)
  3225.                return FALSE;
  3226.           else // if (fUserChoice == IDCANCEL)
  3227.                return -1;
  3228.      };
  3229. private:
  3230.      STRING strOutput;
  3231. };
  3232.  
  3233. #endif // WARNING_H
  3234.  
  3235.      Wondrous.
  3236.  
  3237.      Why, you  ask, go  through all  this trouble just  for a  MessageBox()
  3238. call?  Well, there are plenty of  reasons.  Let's review them.  First,  who
  3239. can remember all those flag variables?  Not me.  Does the caption go first,
  3240. like I know it  should (and always write)?  No!  No matter how many times I
  3241. write it the correct  way, Windows just will not learn.   Second, the folks
  3242. at Microsoft  just love to change  things.  The MessageBox()  call may gain
  3243. new  functionality.   Why romp  through all  the code  you have  written to
  3244. change it for the latest greatest  MessageBoxEx()?  It would be a pain  and
  3245. you surely  would miss some of  your old code.   Now all you need  to do is
  3246. make  a change  to the  MSGBOX class.    Third,  you can  add all  sorts of
  3247. checking to the base class.  This really doesn't apply in  this case but in
  3248. more  complicated classes  this  will  be important.    We even  gain  some
  3249. checking from  the string class. "How?" you ask.  There is nothing to screw
  3250. up with constant strings.   I'm afraid not, my friend.  Once upon a time, I
  3251. forgot to export one of my functions (good thing you have never  done that)
  3252. that used one  of these classes.  My data segment was pointing off to never
  3253. never land and luckily my STRING's told me  as much.   They didn't even  GP
  3254. fault,  just quietly told me that I had screwed up. Windows 3.1 has brought
  3255. multimedia to our clutches so let's look  at a real example of how powerful
  3256. C++ inheritance can be.  We will be updating MSGBOX.
  3257.  
  3258. MSGBOX.H
  3259.  
  3260. //
  3261. //   MSGBOX Object Module
  3262. //   Andrew Bradnan (c) 1992
  3263. //   C++ Windows Version
  3264. //
  3265.  
  3266. #ifndef __MSGBOX_H
  3267. #define __MSGBOX_H
  3268.  
  3269. #ifndef __WINSTR_H
  3270.  
  3271.                                    - 52 -
  3272.  
  3273.  
  3274.  
  3275.  
  3276.  
  3277.  
  3278.  
  3279.  
  3280.  
  3281. #include <winstr.h>
  3282. #endif
  3283.  
  3284. class MSGBOX {
  3285. public:
  3286.      MSGBOX (LPSTR lpszCaption);
  3287.      : strCaption (lpszCaption), fuStyle (Style)
  3288.      {};
  3289.      int operator<< (STRING& strMessage);
  3290.      {
  3291.           MessageBeep (fuStyle);
  3292.           return MessageBox (NULL, strOutput, strCaption, fuStyle);
  3293.      };
  3294. private:
  3295.      STRING strCaption;
  3296.      UINT fuStyle;
  3297. };
  3298.  
  3299. #endif // __MSGBOX_H
  3300.  
  3301.      You have now  added multimedia  to everywhere you  use message  boxes.
  3302. All by  changing one measly line.   You're a  fast worker.  Your  boss will
  3303. probably give you a raise. We have just implemented  some easy ways to have
  3304. dialog  with the user.  They are by no means complicated, but just think of
  3305. the complicated things you can make this  easy, even fun to program.  There
  3306. are plenty of  other nasty constants  you can play  with to create you  own
  3307. classes.    Then  you  can  rip  those  pages  right  out  of  the  Windows
  3308. Programmer's Reference.
  3309.  
  3310.  
  3311.  
  3312.  
  3313.  
  3314.  
  3315.  
  3316.  
  3317.  
  3318.  
  3319.  
  3320.  
  3321.  
  3322.  
  3323.  
  3324.  
  3325.  
  3326.  
  3327.  
  3328.  
  3329.  
  3330.  
  3331.  
  3332.  
  3333.  
  3334.                                    - 53 -
  3335.  
  3336.  
  3337.  
  3338.  
  3339.  
  3340.  
  3341.  
  3342.  
  3343.  
  3344.   The Trials and Tribulations of an Antipodean Abecedarian who uses Turbo
  3345.                          Pascal to Program Windows
  3346.  
  3347.                                    Part 1
  3348.                   They Also Serve Who Only Stand and Wait
  3349.  
  3350.                                 Jim Youngman
  3351.                            [CompuServe 100250,60]
  3352.  
  3353.      I  live in  Melbourne, Australia and  I work  in a  one man Operations
  3354. Research department.   The programming that  I do is  in order to  solve OR
  3355. problems that I encounter.  I am very much isolated in my job although I do
  3356. my best to  keep in touch  with others through  professional societies  and
  3357. computer user  groups.  e-mail  has proved invaluable  since I had  a modem
  3358. installed a short time ago.
  3359.  
  3360.      The problems  I have struck are often simple  in the end, but they can
  3361. be  confusing for  a  beginning Windows  programmer  such as  myself.   The
  3362. documentation  for the various programming languages does not tell you what
  3363. you need to do; rather it tells you how to do something when you  know what
  3364. it is  you need to  do.  Perhaps sharing  some of these  trivial trials and
  3365. tribulations  in a  series  of  short  articles  might  just  help  another
  3366. beginner.
  3367.  
  3368.      I have the job of writing a  user friendly front end to a mathematical
  3369. programming package.  The end users will be line managers with a background
  3370. in mechanics rather  than computers.  This  led me to decide that  the best
  3371. medium  for them  to use  the final  application would  be Windows  (we are
  3372. standardized on IBM compatible machines).
  3373.  
  3374.      One of  the first  things  I needed  to do  after  setting up  various
  3375. screens using  the Borland Resource Workshop was to find out how to run the
  3376. mathematical  programming  package  from  within a  Windows  program.   The
  3377. package is DOS based  and uses the Phar Lap  DOS extender to run in  32 bit
  3378. mode.
  3379.  
  3380.      How could I do this?   I could not find any clues in  the manuals, nor
  3381. in the only book on TPW that I had at the time: Tom Swan's Turbo Pascal for
  3382. Windows  3.0 Programming (1991) which  is an excellent  introduction to the
  3383. subject.
  3384.  
  3385.      The books being  no help, I then went on to  search the Borland Pascal
  3386. Help file.   Eventually I tried a  search on Load  and soon discovered  the
  3387. function LoadModule.  This was described as loading and executing a Windows
  3388. application, but there was  a cross-reference to WinExec.   The description
  3389. of  this  function  did not  specifically  mention  either  Windows or  DOS
  3390. applications, so I tried it:
  3391.  
  3392. procedure RunIPOPT;
  3393. begin
  3394.    WinExec(hWindow,'c:\xp\ipopt', sw_Show)
  3395. end;
  3396.  
  3397.                                    - 54 -
  3398.  
  3399.  
  3400.  
  3401.  
  3402.  
  3403.  
  3404.  
  3405.  
  3406.  
  3407.      This  worked  OK.   However,  the  IPOPT  program  displays masses  of
  3408. mathematical  detail on screen as it is running.   I do not want to confuse
  3409. the  end users with this detail, so I want to eventually hide the window in
  3410. which  it runs  and bring  up a  message box  to inform  the user  when the
  3411. program has run to completion.  For the time  being I will leave the window
  3412. visible (sw_Show) so that I can monitor progress:
  3413.  
  3414. procedure RunIPOPT;
  3415. begin
  3416.    WinExec(hWindow,'c:\xp\ipopt',sw_Show);
  3417.    MessageBox(hWindow, 'Program Completed','IPOPT', mb_Ok)
  3418. end;
  3419.  
  3420.      Surely this should  work.  But, no!  The message box came up on screen
  3421. before IPOPT had even begun to run.
  3422.  
  3423.      Again all  my books failed  me, as did  the Help  file too this  time.
  3424. Borland's Windows  API guide had  a description of  the command,  but there
  3425. were still no examples that showed how it might be used in practice.  I was
  3426. stuck!
  3427.  
  3428.      All fairy tales have happy endings, but they have to  begin with "Once
  3429. upon a time ...".
  3430.  
  3431.      Once upon a time I was browsing in my favorite computer book store and
  3432. discovered a copy of  Neil Rubenking's Turbo Pascal for  Windows Techniques
  3433. (1992).   A cursory  look at  the index  found a reference  to the  WinExec
  3434. function.  I looked it up. It had  exactly what I wanted.  I bought a  copy
  3435. immediately.  The book is another excellent reference that I now have on my
  3436. shelf and reach for often.
  3437.  
  3438.      It seems that the crucial point I was missing was the need to create a
  3439. loop to  test whether the  DOS program was still  running.  I  adapted Neil
  3440. Rubenking's code to produce the following procedure:
  3441.  
  3442. procedure    RunDos(hWindow:    hWnd;    CommandLine,    CompletionMessage,
  3443. CompletionTitle: PChar);
  3444.  
  3445. var IH : word;
  3446.     M  : TMsg;
  3447.  
  3448. begin
  3449.   IH := WinExec(CommandLine, SW_Show);
  3450.   if IH <= 32 then
  3451.           MessageBox(hWindow, CommandLine,
  3452.                'Failed to run'+chr(13)+chr(10)+'Execution Error'+          
  3453.                chr(13)+chr(10)+'Contact Jim Youngman for Help',
  3454.                mb_Ok + mb_IconHand)
  3455.         else
  3456.           begin
  3457.             repeat
  3458.               while PeekMessage(M, 0, 0, 0, PM_REMOVE) do
  3459.  
  3460.                                    - 55 -
  3461.  
  3462.  
  3463.  
  3464.  
  3465.  
  3466.  
  3467.  
  3468.  
  3469.  
  3470.                 begin
  3471.                   if M.Message = WM_QUIT then
  3472.                     begin
  3473.                       PostQuitMessage(M.wParam);
  3474.                       Exit;
  3475.                     end
  3476.                   else
  3477.                     begin
  3478.                       TranslateMessage(M);
  3479.                       DispatchMessage(M);
  3480.                     end;
  3481.                 end; {end do}
  3482.             until GetModuleUsage(IH) = 0;
  3483.             MessageBox(HWindow, CompletionMessage, CompletionTitle,
  3484.               mb_Ok + mb_IconInformation);
  3485.           end; {else}
  3486. end;
  3487.  
  3488. I lived happily ever after, at least until the next problem.
  3489.  
  3490.  
  3491.  
  3492.  
  3493.  
  3494.  
  3495.  
  3496.  
  3497.  
  3498.  
  3499.  
  3500.  
  3501.  
  3502.  
  3503.  
  3504.  
  3505.  
  3506.  
  3507.  
  3508.  
  3509.  
  3510.  
  3511.  
  3512.  
  3513.  
  3514.  
  3515.  
  3516.  
  3517.  
  3518.  
  3519.  
  3520.  
  3521.  
  3522.  
  3523.                                    - 56 -
  3524.  
  3525.  
  3526.  
  3527.  
  3528.  
  3529.  
  3530.  
  3531.  
  3532.  
  3533.                          Getting in touch with us:
  3534.  
  3535. Internet and Bitnet:
  3536.  
  3537. HJ647C at GWUVM.GWU.EDU -or- HJ647C at GWUVM.BITNET (Pete)
  3538.  
  3539. GEnie: P.DAVIS5 (Pete)
  3540.  
  3541. CompuServe: 71141,2071 (Mike)
  3542.  
  3543. WPJ BBS (703) 503-3021 (Mike and Pete)
  3544.  
  3545. You can also send paper mail to:
  3546.  
  3547. Windows Programmer's Journal
  3548. 9436 Mirror Pond Drive
  3549. Fairfax, VA   22032
  3550.       U.S.A.
  3551.  
  3552.      In future issues we  will be posting e-mail addresses  of contributors
  3553. and columnists who  don't mind  you knowing their  addresses. We will  also
  3554. contact any writers from  the first two issues  and see if they want  their
  3555. mail addresses  made available for  you to respond  to them. For  now, send
  3556. your comments to us and we'll forward them.
  3557.  
  3558.  
  3559.  
  3560.  
  3561.  
  3562.  
  3563.  
  3564.  
  3565.  
  3566.  
  3567.  
  3568.  
  3569.  
  3570.  
  3571.  
  3572.  
  3573.  
  3574.  
  3575.  
  3576.  
  3577.  
  3578.  
  3579.  
  3580.  
  3581.  
  3582.  
  3583.  
  3584.  
  3585.  
  3586.                                    - 57 -
  3587.  
  3588.  
  3589.  
  3590.  
  3591.  
  3592.  
  3593.  
  3594.  
  3595.  
  3596.                                The Last Page
  3597.                               by Mike Wallace
  3598.  
  3599.      Things  have been  hectic in  the WPJ  offices (i.e., our  basement) -
  3600. you've been sending us great articles and mail with lots  of input (see the
  3601. Letters column).  It's been great - you seem to like what we're  doing, and
  3602. we're having a good time.  This month a tips/tricks  column (Hacker's Gash)
  3603. makes its debut  in WPJ (Official slogan: "The original  party snack").  It
  3604. was written by us, but we're always glad  to hear from you people.  If  you
  3605. have any tricks you want to share, let  us know about them!  We seem to  be
  3606. accomplishing our  basic goal  of helping  people learn  how to  program in
  3607. Windows, but this goal brings up an issue I want to write a bit about.
  3608.  
  3609.      In the Letters column,  there is a letter from  Tammy Steele, formerly
  3610. of the  SDK  forum on  CompuServe,  and in  her  letter she  discusses  the
  3611. accuracy of  WPJ.   If the  magazine isn't  accurate, the  Microsoft sysops
  3612. aren't going to recommend WPJ as a source of helpful info.  When we started
  3613. this  effort, I didn't envision Microsoft referring people needing help our
  3614. way.  Don't  ask me  why, I  guess it  just never  occurred to  me, but  it
  3615. illustrated to  me the  point that  the available sources  of help  on this
  3616. matter shouldn't  be exclusive  - that is,  no one  has a monolopy  on this
  3617. field  (nor should  anyone), because  no one  source can  be everything  to
  3618. everybody.  I'm not going  to tell you that  WPJ can answer every  question
  3619. you  ever had about programming in Windows, because different sources offer
  3620. different things to their readers.  We  write about the topics we think the
  3621. most people can  benefit from, but in some ways  we're much different than,
  3622. say,  "Windows/DOS Developer's  Journal".   Not to  say we're  better, just
  3623. different.  Don't limit yourself to just one source of information.
  3624.  
  3625.      If  you have a specific  question about Windows  programming, you have
  3626. several options: write us a  letter and we'll try to answer it,  or, if you
  3627. have a  ID on CompuServe, America  Online, GEnie, etc., post it  and see if
  3628. you  get a  response.   Microsoft  also puts  out  a Developer  CD full  of
  3629. articles (yes, I  have this CD and it's a great  source).  What I'm leading
  3630. up to is that the sysops for these services (and their members) help people
  3631. learn  this stuff, but sometimes  a complete answer  isn't possible (due to
  3632. space constraints), so why  not refer someone to WPJ if we  have an article
  3633. that  answers the  question?   Much like  I frequently  refer to  The Waite
  3634. Group's  Windows API Bible (see review in this  issue), I also read some of
  3635. the other Windows  programming magazines.   Sometimes they help,  sometimes
  3636. not.    As  long as  WPJ  is  useful and  offers  something  you can't  get
  3637. elsewhere, we'll stay around.  A magazine shouldn't exist for its own sake,
  3638. and judging from your response, we're succeeding in filling a  niche.  Pete
  3639. and I  didn't start this because we  were bored; we saw  an area we thought
  3640. wasn't getting  addressed, namely, help  beginners learn the  basics, while
  3641. offering advanced articles for the people who have been around for a while.
  3642. If  you don't like what  we're doing, send  us a note.   If we keep getting
  3643. positive  letters and don't hear  anything to the  contrary, we'll continue
  3644. what we're doing.  Tammy's concerns about accuracy are well-founded.  We'll
  3645. be the first  ones to admit it if we make a mistake.   I learn just as much
  3646. from our contributing authors as you do,  but if a mistake slips through, I
  3647. want to know about it.  With your help, we can make WPJ a helpful source of
  3648.  
  3649.                                    - 58 -
  3650.  
  3651.  
  3652.  
  3653.  
  3654.  
  3655.  
  3656.  
  3657.  
  3658.  
  3659. info.  'Nuff said.
  3660.  
  3661.  
  3662.  
  3663.  
  3664.  
  3665.  
  3666.  
  3667.  
  3668.  
  3669.  
  3670.  
  3671.  
  3672.  
  3673.  
  3674.  
  3675.  
  3676.  
  3677.  
  3678.  
  3679.  
  3680.  
  3681.  
  3682.  
  3683.  
  3684.  
  3685.  
  3686.  
  3687.  
  3688.  
  3689.  
  3690.  
  3691.  
  3692.  
  3693.  
  3694.  
  3695.  
  3696.  
  3697.  
  3698.  
  3699.  
  3700.  
  3701.  
  3702.  
  3703.  
  3704.  
  3705.  
  3706.  
  3707.  
  3708.  
  3709.  
  3710.  
  3711.  
  3712.                                    - 59 -
  3713.  
  3714.  
  3715.  
  3716.  
  3717.  
  3718.