home *** CD-ROM | disk | FTP | other *** search
/ CD Actual 15 / CDACTUAL15.iso / cdactual / program / c / WPJ1_3.ZIP / WPJV1N3.TXT < prev   
Encoding:
Text File  |  1993-03-02  |  123.2 KB  |  3,718 lines

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