
   -Released February 1st, 1995-------------------------------------Free-
               COMP.LANG.BASIC.VISUAL.* DIGEST (CLBV DIGEST)
                   Edited and Published by Jakob Faarvang
   -Issue 1, Volume 1-----------------------------------------------1995-

     - a newsletter for all Visual Basic(tm) programmers on the
                 Usenet Comp.Lang.Basic.Visual.* newsgroups

                   (C) Copyright 1995 by Jakob Faarvang.
                            All Rights Reserved.

        This newsletter may be freely distributed in electronic form
      as long as it and the files accompanying it are left unchanged.

      Reprint in other magazines, etc (electronic or paper) is strictly
           prohibited without specific permission from publisher.

                You are allowed to use all code in your own
                     programs unless otherwise stated.

                                DISCLAIMER:
         Even though we try to check for all errors, etc we cannot
    guarantee that any of the information in this newsletter is correct.
          We will not be held responsible for any of these errors.
            All copyrights, trademarks, etc. are acknowledged by
                          their respective owners.
    [Visual Basic is a registered trademark of Microsoft(R) Corporation]

   ----------------------------------------------------------------------


                            1. TABLE OF CONTENTS

                            Adresses of interest

                        Editorial by Jakob Faarvang

          Discussions from the Comp.Lang.Basic.Visual.* newsgroups

                  Using Metafiles with VB by Joe Oliphant

                      My favorite tools by Ian Lynagh

               Dynamic Table Attaching with VB by Les Gainous

                    When I'm Calling You by Luke Webber

               Windows API for Screensavers by Elijah Meeker

                  Dearming the time bomb by Chad Z. Hower

                          Cursor DLL by David Bold

               Information on VB Talk via Internet Relay Chat
                               by Ryan Heldt

                           Contacting the editor



------------------------------------------------------------------------------
                            ADRESSES OF INTEREST
------------------------------------------------------------------------------

Jakob Faarvang (the editor) can be contacted at jakobf@colossus.ping.dk ... If
you wish to write to him via "snail-mail", please write to the following
address:

                               Jakob Faarvang
                                Kirkebjerg 2
                               5690 Tommerup
                                  DENMARK

CLBV Digest (All issues) can be found via FTP at ftp.apexsc.com:
in the /pub/clbv-digest/ directory. You can also access it via World Wide
Web (!) by setting your viewer to go to
http://www.apexsc.com/vb/clbv-digest/index.html and going from there.

If you have *anything* to submit to CLBV Digest (articles, questions,
discussion, reviews, etc), then please send them to the editor c/o the
address above. [Remember: CLBV Digest is based on people submitting articles.
Please consider writing an article and contact the editor.]

An interesting World Wide Web Page dedicated to Visual Basic can be seen
by setting your view to go to http://www.apexsc.com/vb/. This will take you
straight to "Carl 'n' Gary's VB Home Page", where you'll find hotlinks to
other VB WWW pages (including CLBV Digest), CLBV messages, Knowledge Base
articles, and much more.

------------------------------------------------------------------------------
                                 EDITORIAL
                by Jakob Faarvang <jakobf@colossus.ping.dk>
------------------------------------------------------------------------------

[Jakob Faarvang is a student in Denmark. He has programmed in Basic since 1986,
starting on a Commodore Vic 20, and moving up to the Commodore 64, the Commodore
Amiga, IBM PC/Dos (QuickBasic 4.5) before finally settling on Visual Basic
3.0/Pro. In his spare time, he likes to play the saxophone and the piano (when
away from the computer<g>).]

Welcome to the first issue of CLBV Digest!

        Believe it or not: This newsletter hasn't even been a month under
way! Clay Smith (Purelogic@aol.com) posted to the comp.lang.basic.visual (now
dead) newsgroup suggesting that he would start a newsletter for all parties
interested. After talking with a few people, he decided that he did not have
the time for it. I let it go by until I saw a letter from Robert Wallace
(robertw@netcom.com). After reading his letter, I wrote to him and decided to
start the newsletter. I've spent January contacting the people that have
written the articles in this issue (a big thanks to them!), saving interesting
discussions, etc from CLBV for the newsletter and putting it all together.

        As this is the first issue, I had to have some help. Thanks to
Robert Wallace for being very incouraging at the start of the project,
as well as all the people that have written articles for this first issue:
Luke Webber, Chad Z. Hower, Ian Lynagh, Les Gainous, Joe Oliphant and
Elijah Meeker! A big thanks goes to Gary Wisniewski (President of Apex Software)
for providing the newsletter with a directory and WWW page at Apex's FTP and
WWW site (all for free).
        Before starting this newsletter project, I had one big consideration
to make: was there a need for a Visual Basic newsletter? I am aware of
two active VB newsletters in electronic form: Basic.news by Ross Lambert
of Ariel Publishing (rlambert@arielpub.com) and VB Tips & Tricks by David
McCarter (74227.1557@Compuserve.com). Basic.news features excellent
editorials/comments by Ross Lambert as well as product release reports
for new VB programs and utilities. VB Tips & Tricks is a lot like the
Microsoft Knowledge Base (on ftp.microsoft.com - VBKB.EXE), except that
tips and tricks are submitted by the VB community.
        The goal of CLBV Digest is not to be the same as the above two.
CLBV Digest will feature articles (written by people like yourself),
interesting discussions from the Comp.Lang.Basic.Visual.* newsgroups, reviews
of VB products (want to review a VB utility you like? Go ahead! - but try to
make it unbiased (i.e. review two competing products side by side)), and
almost anything else that people want to submit - including discussions on VB
related topics, etc.
        To make CLBV Digest succesful, people have to submit articles. As
mentioned above, I've spent January contacting the people that have written
the articles in this newsletter. Already, I have four people lined up to
write articles for issue two, so it looks like the newsletter is off to a
pretty good start. If you have anything to submit, please contact me. Deadline
will be on the 25th of each month (I will attempt to make CLBV Digest a
monthly newsletter).
        Issue 1 of this newsletter will be the _only_ issue in text format.
Fred Bunn, President of Teletech Systems, has donated a copy of his "VB
HelpWriter Pro".  As I did not have time to convert the magazine to help format
this issue, all subsequent issues will be in help format.  Remember: In
the future, if you want to submit an article, you can either do it in
text format (via a message, etc) or preferably via a Word/WordPerfect/etc
document - this makes it much easier for me to put your article in the
newsletter. Thanks!
        If anyone (private/company) would like to place an add in the
newsletter, that is also a possibility (for a fee, of course). This will be
possible as of issue 2, when I convert to the WinHelp format (as said above).
Contact me for further details.
        Anyway, I hope you enjoy this issue, and that you will consider
writing an article for a future issue. Also: I need your feedback! Please
write and tell me what you think of the newsletter!

                              Yours sincerely,
                               Jakob Faarvang
                                   Editor
                         (jakobf@colossus.ping.dk)

------------------------------------------------------------------------------
          Discussions from the Comp.Lang.Basic.Visual.* newsgroups
------------------------------------------------------------------------------

[Editor's note: This section of the newsletter is meant to reflect interesting
discussions from the Comp.Lang.Basic.Visual.* newsgroups - not to repeat
Questions/Answers, etc. Should be fun to look back at in a year or two.]

                          DISCUSSION ON GIF RIGHTS

--> From: CZ_HOWER@Delphi.com (Chad Z. Hower)

Galenr@gr.hp.com,

  >Actually it is not the GIF format itself they are claiming rights
  >to but the LZW compression scheme used in GIF. As was explained by
  >CompuServe this doesn't affect users of GIF files but does affect
  >developers of GIF encoders. A license is being offered by CompuServe
  >for developers of GIF encoders

            This will never go over. GIF has been promoted as a FREE
standard for how many years now?

            I don't think this would even hold up in court if any one
challenged it. From what I have seen, most developers are going to
ignore it because:

            1) It is rididculous to "change on the fly".  I can see if
they came out with GIF95, and wanted something for that, but changing
a horse in midstream?

            2) They don't think it can be upheld legally.

            I am not involved in graphics, so it makes no difference to me
now, but I think their action is a desparate and ridiculous one.

--> From: jseach@ix.netcom.com (Jim Seach)

Compuserve is not trying to change anything.  Unisys got a patent
on the LZW compression algorithm which is used in the GIF standard.
If the LZW patent holds up in court, Unisys could go after anyone
who used it.  Compuserve is trying to help its developers avoid
problems with Unisys.  Also, if one of the developers was sued by
Unisys, maybe the developer could turn around and sue Compuserve
for telling them it was free.  Who knows how things will turn out
in court?  Other software patents involving algorithms have been
recognized (at least by industry, I don't know if there have been
any court cased), for example the Unix set UID bit and the RSA
public key encryption algorithm.

Sorry, I guess we are getting off-topic here.

--> From: webber@werple.mira.net.au (Luke Webber)

Actually, it scares the pants off me. How does this affect small 
operators like Phil Katz, author of the PKZip toolset, and that kind 
gentleman Yoshi whose LHARC program is in the public domain. Surely these 
product both use LZW (Lempel-Ziv-Welch) algorithms.

Is Unisys only going after the large commercial users of LZW, or are we 
about to see all of the Lempel-Ziv tool providers slapped with claims? 
Does Unisys also own the Huffman algorithms on which LZW is based, or 
would slight departures from the LZW standard put us all outside your 
reach? If you do own the Huffman patent, are you going after Microcom, 
Rockwell, Hayes, et al for V.42 and MNP5?

This could be *big*.

--> From: perew@kaiwan009.kaiwan.com (Mark Perew)

I believe that the PKZIP tools are already covered by a license agreement with
Unisys over the use of LZW.

--> From: jakobf@colossus.ping.dk (Jakob Faarvang)

There has been a lot of hype regarding zip files lately.. This message
should either calm you down or make you worry even more :-)

l8r - Jakob

-<snip>-
From: zap@lysator.liu.se (H}kan Andersson)
Newsgroups: comp.graphics,comp.infosystems.www.providers
Subject: CALM DOWN! GIF/Unisys situation completely misunderstood!!!!!
Date: 5 Jan 1995 08:32:14 GMT

People, people, CALM DOWN!
 
This all originates from a huge misunderstanding!
 
From reading the available documents CORRECTLY (and not like Satan reads 
the Bible) and listening to Tim Oren, and trying to use my HeAD, I get
the following results out of it, in chronological order:
 
-1.1984: Unisys patents LZW
 
0. 1987: Compuserve defines GIF, unknowing about the Unisys patent.
 
1. 1994: Unisys attacks Compuserve (CIS) for using the LZW patent. Agreed, 
   this is a Foul Trick. The reasons I leave for you to speculate. But they
   targeted the most Obvious Violator of their Patent.
 
2. Unisys sits down with CIS and negotiates
    a) An agreement between CiS and Unisys. (Which is higly confidential)
    b) An agreement for CIS DEVELOPERS and people making CIS-RELATED-
       PRODUCTS. CIS does this as a SERVICE TO THEIR OWN DEVELOPERS, so
       THEY WILL NOT HAVE TO HAVE THE BATTLE WITH UNISYS THEMSELVES!

2.5 Read 2 again, until you got it.
 
3. Late 1994: The agreement b) escapes out on the net.
 
4. Agreement b) is misinterpreted. People believe it applies to ALL GIF
   DEVELOPERS IN THE WHOLE WORLD. This is wrong. The intention of this
   document is for the CIS RELATED DEVELOPERS ONLY. (Read it! Does it any-
   where say it applies to YOU? No. It applies to the Signed Party. Meaning,
   it doesn't apply unless you sign it. However, CIS has other agreements
   with their own developers ALREADY SIGNED. What CIS wanted to do was to
   put them "under their protective wing" and negotiate for them. THIS
   DOCUMENT IS FOR THEM - NOT FOR YOU! This is the NEW AGREEMENT FOR THE
   CIS RELATED DEVELOPERS *ONLY*)
 
5. UNISYS only holds a patent for COMPRESSING LZW
 
Results or All Of The Above, the BOTTOM LINE:
 
A: If you have made a GIF *decoding* sofware - do absolutely NOTHING.
B: If you have made a GIF *ENcoding* software, you have three choices:
   a) Wait until UNISYS tries to get to you. (CIS will not do anything to
      you. They are covering their ass, and their friens ass, not yours)
      But remember, UNISYS start from biggest to smallest. Unisys targeted
      CIS first. Then, they will go to next biggest, and so on. (IF they
      go on at all!) So, it will be next decade before they get to
      "John Doe, maker of TARGA2GIF.EXE" and try to sue him for $5.
      And the LZW patent expires in two years, so go figure!
   b) Negotiate for youself with UNISYS
   c) Sign the CIS agreement. But then you will shoot yourself in the foot
      because you will become a CIS-ONLY developer, and you dont want that,
      do you???!??!
 
This all naturally does not stop one from being pissed that Unisys suddenly
decided to whine about a 7 year old software implementation of their patent,
which they previously did nada about software implementations for!!
-<snip>-

--> From: robertw@neptune (Robert Wallace)

[PKzip and LHarc]
I believe both of these products use Lempel-Zif-Huffman compression which is
not covered by the patent.

--> From: conz@cyber.com.au (Con Zymaris)

This stuff really irks me off. Someone should beat software patents over the 
head with a cold tuna!

I'm not very legally minded, but I do believe these software patents are only 
applicable to the US, and no where else, like the UK or Australia, which do 
not allow the patenting of software.  Anyone care to comment?

I can just imagine where we'd all be if Isaac Newton had patented the 
Calculus!!

--> From: jakobf@colossus.ping.dk (Jakob Faarvang)

Just caugt the following on another network:

===========================================================================
- CompuServe is asserting no proprietary rights in the GIF spec, and couldn't
even if we wanted to, since it has long been openly published.

- The LZW algorithm was incorporated from an open publication, and without
knowledge that Unisys was pursuing a patent.  The patent was brought to our
attention, much to our displeasure, after the GIF spec had been published and
passed into wide use.

- We found it necessary to take a license to the patent from Unisys, and
since a number of our developers had used GIF in good faith, we also
negotiated a pass-through license for their benefit.  Clawson has
distributed parts of this license, with a poor interpretation, outside of
its original context, which was to developers of CompuServe-related products
alone.  GIF is included in this license because we are unable to pass
through a general license to practice LZW.

- It is not the intent of CompuServe to attempt to enforce proprietary
rights in GIF against users or developers, including those of Web
technology.  We cannot and do not speak for Unisys' intent in this matter.

- Having and transmitting GIF images is not an infringement of the patent,
since 'practicing' LZW means running code which accomplishes the compression
of the graphics.

Tim Oren,  Vice President, Future Technology  CompuServe, Inc.

--> From: thornton@icchip.wes.army.mil (Shannon Thornton)

I have a hard time believing they can enforce this one.  I've purchased several graphics books that 
explicitly shows the LZW algorithm.  When I say explicitly, I mean they didn't pseudo-code it, they 
provided a C (Pascal in another book) implementation and included a companion disk with the 
algorithm.  Furthermore, most popular magazines (PC World, Dr. Dobb's Journal, etc.) have published 
the LZW at one time or another.  I think the patent just happened a bit to late.

I do think this might be another good reason to move the "standard" image format to JPG.  Most WWW 
viewers can do them anyway.

Just my $.02.

--> From: robertw@neptune (Robert Wallace)

Just because the algorithm (and code to implement the algorithm) has been
published doesn't mean the patent is unenforceable.  In fact, the details
of the algorithm *must* be made public in order to receive a patent.
This isn't a trade secret or anything.

However, it could be problematic that they've waited this long to
begin enforcing it.  That's up to the courts, should anyone take
the matter that far.  I'd bet on the patent holder, though.

Court is an expensive process for both sides.  Much easier to go along
for the moment, pay the license and royalty fees and switch to something
in the public domain as quickly as possible.  LZ77, as used in gnuzip
and others, seems a likely choice.

Robert Wallace
robertw@not.a.lawyer.grainosalt

--> From: robertw@neptune (Robert Wallace)

Another important point concerns shareware.  There's no need for the author
to track shareware copies *until a user registers it with the shareware
author.*  Only then is the author expected to pay Unisys for the use of
the patented algorithm.  

One question I didn't see answered is the royalty fees (if any) for
subsequent upgrades to already licensed software.

         VISUAL BASIC PROGRAMMERS JOURNAL - INTERNET LOVERS/HATERS?

--> From: CZ_HOWER@Delphi.com (Chad Z. Hower)

    First of all, I *really* enjoy VBPJ, but when they say things like
this, this is ridiculous. They have many good traits which I have
pointed out in the past, but they also need to know when they have
gone astray.

    Taken from page 5 of January 1995 issue of VBPJ: (By Jim Fawcette)

    "Lately I've been told I'm getting too conservative ....<snip> So
forthwith a journey into the realm of three-dot journalism for a
bouilibaisse of random punditry seasoned with vitrol."

    OK, I know he is going to be prodding some fun and jokes, which is
fine.  But this next part goes beyond this.  I have the whole section
IN order, but I have split it apart (in order) to respond to each
piece. If you wish to read for yourself, I have cited the exact page
number and issue.

    "We've been generating a lot of Internet traffic since we launched
our Compuserve forum."

    Excuse me, but how does your activity on a CIS forum translate to
internet traffic? It is bad enough that half the public thinks that
AOL *IS* the internet.  Yes, you have been RECIEVING a lot of internet
mail because of it, but how are YOU GENERATING internet traffic? Has
the internet been shut down and replaced by CIS?

    "First a bunch of Internet whiners said that we were immoral
because we didn't do something for them for free."

    WRONG. The argument WAS, has been, and STILL is:

    Many of use will not succomb to the tactics, policies, ignorance,
and other things of CIS.  For one reason or another MANY of use WILL
NOT use CIS.  This goes BEYOND price of CIS.

    It is because of issues like:
    - Service: Try to call their customer service line.  Take a week
vacation first.

    - Access: They are 2400 here, when everyone else is 14.4.  There
are similar situations in a good deal of areas.  They told me I cannot
even use the faster Sprintnet/Tymnet lines, even if I wanted to (and
pay the surcharge)

    - Service: Try to get an answer from their customer service. If
you get one, your lucky if it is what you need.

    - Various other reasons besides my experiences.

        "Then a larger contingent came on to say that most Internet
users aren't flamers and whiners, despite what WIRED magazine would have us
believe."

    HMM.  Which are we flamers or whiners? First we are whiners, now
you are not sure which we are.  When you can't find a valid point,
resort to name calling.  I have never read WIRED, but that looks like
name calling to.  (Putting down another publication)

     "...BTW: Internet isn't free; nothing's free. And, as usual,
the people stuck with the bill are taxpayers."

    OOHH, This one is going to be fun. No, the internet is not free.
But it is BLATENTLY obvious you have little knowledge of how it works.

    "No, I', not referring to the NSF funding, which is about to dry
up (finally),"

    Well you have ONE point I will agree with you on.

    " but the fact that all the nodes are at research labs, paid for
by taxpayers,"

    HMM. *ALL* the nodes? What about Microsoft? Novell? Delphi? AOL? CIS? IBM?
    Do these fit under that?  CIS is now a research lab? And CIS is
payed for by tax dollars?

     "and at aerospace companies,"

    How many aerospace companies allow incoming access to their sites?
Huh? Don't you think they have that site for THEIR research???? THEIR
communication?

    Even if some do allow incoming access, that is not WHY they have a
site. That incoming anonymous access is limited and does not
significantly affect or hamper the system. It is provided as "Well as
long as it does not slow us down or cost us, why not?"

    Name **ONE** aerospace company who allows anonymous incoming
access, and that established their node PRIMARILY for this reason.

    And yes, Universities have nodes.  But what do you think their
reason for this is? HMM, could it be research and communications for
their students and faculty?  They have phones too, are taxpayers now
subsidizing Ma Bell on the basis that all universities have phones too?

    "where the bill shows up in the markups for products sold to the
government."

    See above.

     "There's something bizarre about 99 percent of the population
subsidizing welfare for people who have modems"

    Oh yeah, right.  I don't see the government paying my hourly
access charges. (And don't expect them too)

    PS, YES I support freenets.  But freenets usually are RUN with
BUSINESS donations. (Although usually started with SOME gov money)  I
do NOT support however government money going to pay for the backbone
of the internet.  I *KNOW* what I am talking about here, as I am a
charter member of a newly formed effort to establish such.

    Are public libraries to be abolished too?  There is a BIG
difference between providing INFORMATION EXCHANGE and giving welfare handouts.

    PROVIDERS pay for access, and THIS pays for the backbones.
PROVIDERS then pass this bill on to THEIR users.

    ", and who whine if the great unwashed dare to enter their
privileged kingdom."

    What is this with whining? Any sort of disagreement with your
opinion is "whining" ?  It sounds like you are the one whining IMO.
The internet is not a priviliged kingdom, it is open to all.

    I did not know Dvorak was writing in VBPJ now too....... Hmm has
any one ever seen Dvorak and Fawcette at the same place at the same
time.........????

    Gee I hope the CLIPPER chips don't catch what I wrote....

--> From: ers3@Lehigh.EDU

    I've read that article too. It seems that the editor is just trying to
cover up his ignorance or limited KB. I have access to the Internet via
University. I thought my access was paid with my tution, which cannot be
considered as wellfare even though Gov. started it. I liked the VBPJ at first,
now I cancelled the subscription. Besides, I've learned nothing from VBPJ
considering what I learned from comp.lang.basic.visual.* . I have no objection
to CIS or AOL etc. since they provide an access to willing people, objection
is to the idea of they are being the Internet. No way.

From: ajm@geac.co.nz (Andrew Mayo)

With regard to accuracy of the trade press recently, I have to say I am 
increasingly disappointed with the information in most leading journals. 
For example:-

Computerworld US is determined that Microsoft's products should be 
criticised at all costs; for example, ODBC is slow and flakey. They 
insist on this point of view despite contrary views and their editorial 
staff seem increasingly out-of-touch with the new, non-mainframe world.

Byte's wide-eyed gormless admiration for almost anything technologically 
new leads them to praise almost any new product even when (e.g the 
HP/Intel VLIW project) the idea is clearly brain-damaged. (VLIW is 
brain-damaged because in the new, runtime, OOP world, how *can* the 
compiler schedule instructions if everything happens at runtime)?

I could go on. But picking on VBPJ is like shooting fish in a barrel. I 
am afraid the truth is that with few exceptions (Dr. Dobbs deserves an 
honourable mention for its accurate reporting) the trade journals are 
becoming increasingly unreliable as information sources. It is a matter 
of reading everything and then averaging out the noise. 

If anyone wants to submit a 'top ten' list of favoured journals, I'd be 
interested to see what was chosen. I have come increasingly to buy Dr. 
Dobbs and Byte each month; despite its flaws, Byte has moved back to its 
technical centre after those awful 'action point' press releases of a few 
years back. Other than that, I buy only if there's an article that looks 
interesting.

--> From: CZ_HOWER@Delphi.com (Chad Z. Hower)

Ers3@lehigh.edu,

>    I've read that article too. It seems that the editor is just trying to
>cover up his ignorance or limited KB. I have access to the Internet via
>University. I thought my access was paid with my tution, which cannot be

    And don't you use your internet account for research? I am sure
you use it for other things too.  I would guess you are some sort of
computer major, and your access to this group therefore would be academic.

>considered as wellfare even though Gov. started it. I liked the VBPJ

    Yes, going to college is now welfare, according to this.

    I did like January issue very well.  BUT I just got the February
issue, and I really like this one. (The content itself)

    I am an advanced programmer, so many of the articles are not of
much use to me, but no one knows everything, including myself.  It
gives me a chance to see how others are doing things.  And I often
find tidbits of valuable information.  The one thing that I really
like about VBPJ is the fact that it covers everything, it has articles
for newbies, hobbyists, and pros alike.

    I also get it to see VB ads.

    I seriously dislike their attitude towards the internet.  It seems
to me that they do not know that much about the internet (ie Ignorant)
and are "afraid" of change.

    Robert Scoble is making good efforts though with his presence
here, but with his editors attitude......

--> From: mhoffman@unicomp.net (Mark Hoffman)

I too enjoy reading VBPJ, but when I read Fawcett's editorial about Net I was
a bit ticked off. First he makes inferences to people who use the Internet
as freeloaders, and that Compu$erve seems to contain a more professional crowd,
because they "pay" for their service. The whole feeling I received was that of
being snubbed by some pompous,arrogant,bowtie wearing ass. 
Oh well, fortunately I don't have to agree with Fawcett to enjoy his magazine.

--> From: efried@netcom.com (Eric Friedman)

Don't know how to break this to you guys, but CIS now gets USENET news
groups, and has had email access to the net for awhile. So CIS (and I
suppose AOL) are on the internet. So the editor isn't wrong.

--> From: ianhar@u09002.skm09.svskt.se (Ian Harcourt)

         I believe the people at VBJ are doing a great job. However, there
always is one. One person who spoils it for the rest. One who can't match
the high stands set by the others. What is so sad about this case is that it's
Jim Fawcette, the Publisher and Editor himself. 

         Internet is one of the most important communication forms in the world
today. It's rapid growth is testament of the need for such a system and 
reflects it's importance to many people around the world. In the last few
weeks I 've given advice to Doctor in Singapore and myself received 
important information from a colleague in Vietnam.  This possibility, which 
is so stimulating, is  only availible to me through internet. I remember back 
to my university days where we had JANET, the forerunner to Internet within
the academic community in Britain. Jim implies that Internet is made up of 
whiners. So little he knows. The limitations are set by our imagination and
expertise. This global hacking should be encourage, Jim talks about the 99%
who subsidize the minority. Our aim should be that this technology spreads
so that the present majority who sit without access to 'a net', whether it be
Internet, CompuServe etc., soon become a minority. Then Jim's argument
has no meaning, the minority becomes the majority.

Thought for the Day :

      It is easy to criticise but encouragement brings out the best in people.

                                         Dedicated to Jim

     So come on Jim think positively, help us, encourage inovation. It's
much more rewarding and you make a lot more friends. Credit where
credit is due you started The Basic Magazine after all. Try and rediscover 
that pioneering spirit you once had.

--> From: ajm@geac.co.nz (Andrew Mayo)

Not from New York, are they?. Actually that is not an uncommon opinion; 
few developer 'heavies' hang out on clbv, as far as I can see. Its a 
shame but look at the noise level. Frankly, if you're paying for 
CompuServe then you're probably a serious developer or corporate user; 
not a University student. So the noise level is definitely less. But I, 
for one, intend to remain here; like the local cafe, its noisy, but 
cheerful. Sooner or later you get your Qs answered.

--> From: CZ_HOWER@Delphi.com (Chad Z. Hower)

Efried@netcom.com,

__>Don't know how to break this to you guys, but CIS now gets USENET news
__>groups, and has had email access to the net for awhile. So CIS (and I
__>suppose AOL) are on the internet. So the editor isn't wrong.
__>>is to the idea of they are being the Internet. No way.

    Did you read the post?  The VBPJ forums are LOCAL to CIS. The fact
that CIS can ACCESS newsgroups, has nothing to do with this.  You
cannot get to VBPJ from the internet....

    But that was not the original point, the original point is the
consistent "hate" that VBPJ has of the internet, and the fact that
when confronted with facts, certain parties at VBPJ (Not Robert
Scoble) resort to name calling.

    Time to take out the trash.....

--> From: bmurray@pluto.njcc.com (Brad Murray)

Chad Z. Hower wrote:

:     I seriously dislike their attitude towards the internet.  It seems
: to me that they do not know that much about the internet (ie Ignorant)
: and are "afraid" of change.

That will be their demise.

:     Robert Scoble is making good efforts though with his presence
: here, but with his editors attitude......

My ass.  I was one of many people who said that they had subscribed (I 
have already paid for it) and never got an issue.  Robert said he would 
take care of it about a month ago.  No letter, no phone call, and most of 
all, no magazine.

Brad


------------------------------------------------------------------------------
                     Using Metafiles with Visual Basic
                              by Joe Oliphant
------------------------------------------------------------------------------
[The code and examples in this article can be found in the GRAPHMF.ZIP file
accompanying the newsletter.]

[Joe Oliphant is the Hydraulics Laboratory Manager at the Center for
Irrigation Technology at California State University, Fresno.  He holds a 
B.S. and M.S. in Agricultural Business from California State University, 
Fresno. He has 18 years of application and data acquisition programming
experience on platforms ranging from main frames to dataloggers. He can be
reached by E-Mail at joe_oliphant@csufresno.edu or on CompuServe at
71742, 1451.]

[Editor's note: Joe has written the excellent Graphics Viewer VB utility.
It is a freeware DLL that allows VB programmers to view numerous graphics
formats in the programs. To find it, do an archie for GV2]

A Metafile is a set of drawing commands that can be stored either in a 
disk file or a memory location and can be played on either the screen in 
a picture control or form or on the printer.

Visual Basic itself has no statements for creating or using metafiles so 
it is necessary to use calls to the Windows API.  If you've never used 
API functions, you need to get Daniel Appleman's "Visual Basic 
Programmer's Guide to the Windows API."  This book covers all of the 
functions you will see in the GRAPHMF project.

Metafiles have several uses.  You can create metafiles of charts or 
drawings and then save them to disk for later playback.  You can also 
create a "Print Preview," copy them to the clipboard where they can be 
pasted into other Windows applications and be displayed or printed just 
as they were originally displayed in your VB app, and you can print or 
display them in any size without the distortion you get as you do with 
stretching bitmaps.


                 API Functions Vs. Visual Basic Statements

One of the great things about VB is the amount of time it saves you to 
perform what would normally take several lines of complex code.  For 
example, to print a 100 by 100 blue filled box at position 10, 10 in a 
picture control with VB requires only one statement:

Picture1.Line(10, 10) - (110, 110), QBColor(1), BF

To perform the same thing using Windows API Calls requires the following 
code:

Const BS_SOLID = 0

Declare Function CreatePen% Lib "GDI" (ByVal nPenStyle%, ByVal nWidth%, 
ByVal crColor&)
Declare Function CreateSolidBrush% Lib "GDI" (ByVal crColor&)
Declare Function DeleteObject% Lib "GDI" (ByVal hObject%)
Declare Function Rectangle% Lib "GDI" (ByVal hDC%, ByVal X1%, ByVal Y1%, 
ByVal X2%, ByVal Y2%)
Declare Function SelectObject% Lib "GDI" (ByVal hDC%, ByVal hObject%)


Dim OldPen%, NewPen%, OldBrush%, NewBrush%, Clr&, PicDC%, Ret%

Clr& = QBColor(1)
NewPen% = CreatePen(BS_SOLID, 1, Clr)
NewBrush% = CreateSolidBrush(Clr)
OldPen% = SelectObject(PicDC%, NewPen%)
OldBrush% = SelectObject(PicDC%, NewBrush%)
Ret% = Rectangle(PicDC%, 10, 10, 110, 110)
Ret% = SelectObject(PicDC%, OldPen%)
Ret% = SelectObject(PicDC%, OldBrush%)
Ret% = DeleteObject(NewPen%)
Ret% = DeleteObject(NewBrush%)

As you can see, using API calls is much more complicated, but you can 
create your own functions to simplify things.  You could put the above 
code in a function as follows:

Sub DrawRect(PicDC%, Clr&, Tp%, Lft%, Wdth%, Hght%)
   Dim OldPen%, NewPen%, OldBrush%, NewBrush%, Ret%

   NewPen% = CreatePen(BS_SOLID, 1, Clr)
   NewBrush% = CreateSolidBrush(Clr)
   OldPen% = SelectObject(PicDC%, NewPen%)
   OldBrush% = SelectObject(PicDC%, NewBrush%)
   Ret% = Rectangle(PicDC%, Lft%, Tp%, Lft% + Wdth%, Tp% + Hght%)
   Ret% = SelectObject(PicDC%, OldPen%)
   Ret% = SelectObject(PicDC%, OldBrush%)
   Ret% = DeleteObject(NewPen%)
   Ret% = DeleteObject(NewBrush%)
End Sub

Then just call it with:
PicDC% = Picture1.hDC
Clr& = QBColor(1)
DrawRect PicDC%, Clr&, 10, 10, 100, 100

The SelectObject function may not be familiar to you.  Before you can 
use any pen or brush on a device, you need to select it into the device 
with SelectObject.  The return value of SelectObject is the handle to 
the old pen (or any object) that was associated with the device.  
Whenever you create a new pen, you need to delete it with DeleteObject 
when you are done with it; otherwise, you will run out of system 
resources.  Make sure you restore the original object or some other new 
object before you destroy any that you created.  Setting the ForeColor 
or FillColor properties on a control with VB does all of this for you 
automatically.

The GRAPHMF Project shows how to perform several different kinds of 
drawing functions with Windows API functions.


                               Mapping Modes

The mapping mode tells Windows what scale you are going to use.  VB has 
the ScaleMode property to set the scale of a picture control or printer 
to several different modes (Picture1.ScaleMode = 3, for instance, sets 
the mapping mode to pixel); however, VB doesn't actually change the 
mapping mode of the control, it just maintains an internal conversion 
process.  As far as Windows knows, all VB controls are set to pixel 
(ScaleMode = 3 or MM_TEXT); therefore, no matter what ScaleMode you use 
with VB, All Windows API calls use pixels.  Rectangle(PicDC%, 10, 10, 
110, 110), for instance, draws a box 100 pixels by 100 pixels 10 pixels 
from the left and 10 pixels down from the upper left corner of PicDC% no 
matter what scalemode you have PicDC% set to; therefore, you need to 
convert whatever scalemode you are using to pixels before calling the 
API functions.  The easiest way to do this is to have conversion 
variables for the X and Y values as follows:

Suppose you are creating a bar graph with values that have a maximum X 
value of 10 and a maximum Y value of 100.  You could use 
Picture1.Scale(0, 0) - (10, 100).  Then if you have a X value of 3 and a 
Y value of 20 and you want to show a bar you could just use 
Picture1.Line(X - .5, 100) - (X + .5, 100 - Y), , B.  A one unit wide 
bar will be drawn centered 3 units over and drawn 20 units up on the 
picture control.  The following code shows how to convert between the 
scale you are using to pixels:

Dim X!, Y!, CX!, CY!, MaxX!, MaxY!, PicDC%, Ret%
Dim W%, H%

Picture1.ScaleMode = 3
W% = Picture1.ScaleWidth
H% = Picture1.ScaleHeight
MaxX! = 10
MaxY! = 100
X! = 3
Y! = 20
CX! = W% / MaxX!
CY! = H% / MaxY!
PicDC% = Picture1.hDC
Ret% = Rectangle(PicDC%, (X! - .5) * CX!, H%, (X! + .5) * CX!, H% - Y! * CY!)
Picture1.Refresh

In these examples, the scale is set so that 0, 0 is in the upper left 
corner and the X values increase to the right and the Y values increase 
down.  This is the standard way Windows (and VB) sets the mapping mode.  
You can reset them using SetWindowOrg and SetWindowExt but it is much 
more complicated.  You can also change the mapping mode of a VB control 
with SetMapMode to MM_ANISOTROPIC and then use any scale you want; 
however, if you are going to use a combination of VB statements and API 
calls on the same control, this can get very complicated.  Whenever you 
change the mapping mode (or the viewport or window extents) of a VB 
control, you need to save the original device context settings and 
restore them with SaveDC and RestoreDC.  Using any VB statements while 
the mapping mode or window extents are changed will guarantee you 
problems.

Once you have down the basics of using API calls, it is a relatively 
simple matter to use them to create metafiles.  Instead of using the API 
calls to draw on a picture control, you just pass the handle of a 
metafile to the calls instead.


                            Creating a Metafile

Creating a metafile is very simple, just use the following code:

Add the following statements to the declarations section:
  Const MM_ANISOTROPIC = 8
  Declare Function CreateMetaFile% Lib "GDI" (ByVal lpString&)
  Declare Function CloseMetafile% Lib "GDI" (ByVal hMF%)
  Declare Function SetWindowOrg& Lib "GDI" (ByVal hDC%, ByVal X%, ByVal Y%)
  Declare Function SetWindowExt& Lib "GDI" (ByVal hDC%, ByVal X%, ByVal Y%)

Dim CMF%, Metafile%, RetL&

CMF% = CreateMetafile(0) ' Memory metafile
RetL& = SetMapMode(CMF%, MM_ANISOTROPIC)
RetL& = SetWindowOrg(CMF%, 0, 0)
RetL& = SetWindowExt(CMF%, Picture1.ScaleWidth, ScaleHeight)

Then just pass CMF% to any of the API drawing functions (or your own 
functions) instead of a picture control or form hDC.  When you are done 
drawing on the metafile, use Metafile% = CloseMetafile(CMF%) to obtain 
the handle (Metafile%) for the metafile.  It is important to set the 
mapping mode, origin, and extents of the metafile before you perform any 
drawing functions on the metafile handle.  These values represent the 
default size and position for the metafile and for reasons that will 
become clear are set to the width and height in pixels of a picture 
control.  If you use MM_ANISOTROPIC for a mapping mode, the metafile can 
be resized to any size.

Drawing lines on metafiles is fairly simple, but text is a little more 
difficult.  You need to create a font and then select it into the 
metafile device context before you can use TextOut to draw the text.  
The main problem you have is figuring out what size to make the font and 
how to position it.  VB has the FontSize, TextWidth, and TextHeight 
properties that allow you to set the size and determine how wide or tall 
a particular font will be.  There is an equivalent API call, 
GetTextExtent, to tell you the width and height of a text string on a 
given device; however, it doesn't work on a metafile device context 
(most of the other API text functions don't work either).

One way to get around this is to use a known size, set the metafile 
extents to that size, and then use the text point size, heights and 
widths that fit that size.  For instance, to determine a point size to 
set the font to for a metafile, you can use the following:

Windows uses twips as a standard measurement.  There are 1440 twips per 
inch.  If the screen resolution is set to 640 x 480, there will be 15 
twips per pixel.  There are 72 points in an inch and at 15 twips per 
pixel, there will be 0.75 points per pixel.  if you want a font size of 
12 points on the screen, setting the lfHeight element of TEXTMETRIC to -
Fix(12 / 0.75) will give you the same size font as the VB statement 
FontSize = 12 on a screen with a resolution of 640 x 480.

The text height can be determined by checking the TextHeight property 
for a VB control when the ScaleMode is set to Pixel (3).  For instance, 
the text height of MS Sans Serif at point size 12 is 19 pixels.  If you 
want to center text vertically, subtract 10 from the Y coordinate where 
you want the text placed.

Determining the text width is more of a problem.  One thing you can do 
is set the extents of the metafile to the same size as the picture 
control or printer on which you want the metafile displayed and then set 
the font properties of that control to the ones you want the metafile to 
be and then check the TextWidth of that control for the string you want 
to use.  Make sure the control is set to ScaleMode = 3.  If you use a 
fixed pitch font, you can just use the text width of a single character 
times the length of the text to determine the width.

The important thing to remember is to set the window extents of the 
metafile to the same size as the size of the control you are using.  In 
the GRAPHMF project, the Pic_Graph control is 280 by 280 pixels and the 
window extents of the metafile is also set to 280 by 280.  When the 
metafile is resized, the font size will automatically change relative to 
the size of the metafile.  The larger you make the extents of the metafile,
the more resolution you will have.


                             Printing Metafiles

The one thing that makes the trouble of using API calls to create 
drawings instead of VB statements is that when you want to print 
drawings, you don't have to have a whole other set of statements to 
print them.

Once you have the metafile, it is a fairly simple matter to print it to 
the printer (or to another form or picture control for that matter).  To 
print a metafile, simply determine the area you want the image to be 
placed and play the metafile as follows:

Suppose you are using a scale of 80 columns by 66 rows on the printer 
object (Printer.Scale(0, 0) - (80, 66)) and you want the drawing to 
start at row 30 and column 20 and you want it to extend to row 60 and 
column 80.

Add the following statements to the declarations section:
  Const MM_ANISOTROPIC = 8
  Declare Function RestoreDC% Lib "GDI" (ByVal hDC%, ByVal nSavedDC%)
  Declare Function SaveDC% Lib "GDI" (ByVal hDC%)
  Declare Function SetViewportExt& Lib "GDI" (ByVal hDC%, ByVal X%, ByVal Y%)
  Declare Function SetViewportOrg& Lib "GDI" (ByVal hDC%, ByVal X%, ByVal Y%)

Dim Tp%, Lft%, Wdth%, Hght%, SavDC%, Ret%, RetL&

Printer.Scale(0, 0) - (80, 66)
Printer.CurrentX = 20
Printer.CurrentY = 30
Printer.Scalemode = 3
Tp% = Printer.CurrentY
Lft% = Printer.CurrentX
Printer.Scale(0, 0) - (80, 66)
Printer.CurrentX = 80
Printer.CurrentY = 60
Printer.Scalemode = 3
Hght% = Printer.CurrentY - Tp%
Wdth% = Printer.CurrentX - Lft%
SavDC% = SaveDC(Printer.hDC)
Ret% = SetMapMode(Printer.hDC, MM_ANISOTROPIC)
RetL& = SetViewportOrg(Printer.hDC, Lft%, Tp%)
RetL& = SetViewportExt(Printer.hDC, Wdth%, Hght%)
Ret% = PlayMetaFile(Printer.hDC, Metafile%)
Ret% = RestoreDC(Printer.hDC, SavDC%)
Printer.Scale(0, 0) - (80, 66)


The Viewport is the area on the printer that is going to contain the 
drawing.  SetViewportOrg sets the top and left positions and 
SetViewportExt sets the width and height from that position.  
SetViewportOrg and SetViewportExt need integer values in pixels and the 
easiest way to get them is to set the current print position with 
CurrentX and CurrentY and then use ScaleMode to change the scaling of 
the control to pixel and then read back the current print position with 
CurrentX and CurrentY (the current print position doesn't change when 
you change scale modes).

The important thing to remember is to use SaveDC to save the current 
device context information and RestoreDC to restore it.  Don't use any 
VB statements on the printer object between the time you use SaveDC and 
until after you have used RestoreDC.

                     Copying Metafiles To The Clipboard

Now that you have your metafile handle Metafile%, you might think that 
copying it to the Clipboard would be as simple as executing 
Clipboard.SetData Metafile%, CF_METAFILE, but you would be very wrong.

You can't use the Clipboard object to copy metafiles.  Instead you need 
to use the Clipboard APIs OpenClipboard, SetClipboardData, and 
CloseClipboard.  You also need to know that anything you copy to the 
clipboard no longer belongs to your application; therefore, you need to 
make copies of things you want on the clipboard.

Copying metafiles to the clipboard is different than copying bitmaps in 
that a bitmap handle can be passed directly to the clipboard with:

Ret% = SetClipboardData(BMPHandle%, CF_BITMAP)

To copy metafiles, you need to pass a handle to a METAFILEPICT structure 
which describes the metafile.  The METAFILEPICT structure has the 
following elements:

Type METAFILEPICT
   mm As Integer    ' This is the mapping mode 
   xExt As Integer  ' This is the default width of the metafile
   yExt As Integer  ' This is the default height of the metafile
   hMF As Integer   ' This is the handle to the copy of the metafile
End Type

You can also use xExt and yExt to specify an aspect ratio by giving them 
negative values.  

Using MM_ANISOTROPIC for a mapping mode allows the metafile to be 
stretched to any size.  The xExt and yExt can be set to any value.  Use 
CopyMetafile to make a copy of the metafile for hMf.  The following code 
puts this all together:

Add the following statements to the declarations section:
  Const MM_ANISOTROPIC = 8
  Declare Function CopyMetafileByNum% Lib "GDI" Alias "CopyMetaFile" 
(ByVal 
  hMF%, ByVal lpFilename&)

Dim MP As METAFILEPICT
Dim CpyMetafile%

MP.mm = MM_ANISOTROPIC
MP.xExt = 1200 ' Arbitrary setting
MP.yExt = 1200 ' Arbitrary setting
CpyMetafile% = CopyMetafileByNum(Metafile%, 0)
MP.hMF = CpyMetafile%

Now you have your METAFILEPICT all ready to go and all you have to do is 
copy it to the clipboard, right?  Except that the clipboard wants a 
handle to the memory block that contains the METAFILEPICT structure.  VB 
doesn't let you know what the address or handle to variables are so you 
have to make your own.  To do this you need to use the GlobalAlloc and 
GlobalLock functions as follows:

Add the following statements to the declarations section:
  Const GMEM_MOVEABLE = &H2
  Declare Function GlobalAlloc% Lib "Kernel" (ByVal wFlags%, ByVal dwBytes&)
  Declare Function GlobalLock& Lib "Kernel" (ByVal hMem%)
  Declare Function GlobalUnlock% Lib "Kernel" (ByVal hMem%)

Dim Memhndl%, MemAddr&

Memhndl% = GlobalAlloc(GMEM_MOVEABLE, Len(MP))
MemAddr& = GlobalLock(Memhndl%)

Now you have a handle and an address to a memory block, but your 
metafile information is stored in MP.  You need to copy the information 
in MP to the new memory location so you can pass it to the clipboard.  
To do this you use hmemcpy as follows:

Add the following statement to the declarations section:
  Declare Sub hmemcpy Lib "Kernel" (ByVal hpvDest&, hpvsource As METAFILEPICT,
  ByVal cbCopy&)

hmemcpy MemAddr&, MP
Ret% = GlobalUnlock(Memhndl%)

Finally you have everything you need to copy the metafile to the 
clipboard with the following statements:

Add the following statments to the declarations section:
  Const CF_METAFILEPICT = 3
  Declare Function CloseClipBoard% Lib "User" ()
  Declare Function OpenClipboard% Lib "User" (ByVal hWnd%)
  Declare Function SetClipBoardData% Lib "User" (ByVal wFormat%, ByVal hMem%)

Ret% = OpenClipBoard(Form1.hWnd)
Ret% = SetClipBoardData(CF_METAFILEPICT, MemHndl%)
Ret% = CloseClipboard()

Whatever you do, don't use GlobalFree to release Memhndl%, it no longer 
belongs to you and if you try to free it you will get a GPF.  You also 
don't need to use DeleteMetafile to release CpyMetafile%.

You probably want to add some error checking to make sure the clipboard 
is available (OpenClipboard returns zero if it is not available) and to 
make sure the metafile was copied (SetClipboardData will return the same 
value as Memhndl% on success).

To see the difference in appearance of a metafile vs. a bitmap, create a 
graph with GRAPHMF, open up another Windows application that accepts 
bitmaps and metafiles (like Word for Windows) and copy the bitmap with 
the Copy BMP button and insert it into the other application, then copy 
the metafile with the Copy MF button and insert it into the other 
application.  Stretch both so they are the same size and then print them 
out.


              PRINTING AND VIEWING METAFILES FROM A DISK FILE

Windows metafiles are stored with the extension .WMF; however, there are 
two types of metafiles that Windows uses.  There are standard metafiles 
(which are the kind CopyMetafile creates when creating a disk file), and 
placeable metafiles.  Placeable metafiles have an additional METAFILEHEADER
structure at the beginning of the file that gives information about the 
size and location of the metafile.  The METAFILEHEADER structure as the
following elements:

Type METAFILEHEADER
   Key As Long          ' Should be the value &H9AC6CDD7&
   HMF As Integer       ' Not used, should be 0
   bbox As RECT         ' A RECT Structure, size and position of metafile
   inch As Integer      ' Number of metafile units per inch
   reserved As Long     ' Not used, should be 0
   checksum As Integer  ' Checksum
End Type

The bbox RECT structure contains the position on the page (or screen) where
the metafile is to be placed and the width and height of the metafile.  The
inch element contains the number of metafile units per inch.  To convert 
the metafile units to a size that will come out in the proper dimensions on
the screen or printer, use the following:

Suppose the RECT structure contains the following:

bbox.left =250
bbox.top = 500
bbox.right = 1250
bbox.bottom = 2000

and the inch value is 500, meaning that there are 500 metafile units per 
inch, it would mean that the metafile should be placed 1/2" from the left 
of the page (250 metafile units / 500 metafile units per inch = 0.5 inches)
and 1" from the top of the page.  The metafile is 2 inches wide (1250 - 250
/ 500), and the height is 1 1/2 inches.  If you are using a ScaleMode of 3 
(Pixel), you need to convert these inch values to pixel values.  
TwipsPerPixelX and TwipsPerPixelY allow you to convert pixel values to twips
and there are 1440 twips in an inch; therefore, to convert metafile units 
to pixel values use the following:

Pixels = Metafile Units / Metafile Units per Inch * 1440 / Screen(or 
Printer).TwipsPerPixelX

If the metafile is 1000 metafile units wide and there are 500 metafile units
per inch and there are 15 twips per pixel on the device you are using, then 
by replacing the variables in the above formula gives you 1000 / 500 * 1440
/ 15 = 192.  The metafile needs to be 192 pixels wide on the device to 
appear 2 inches wide.

Now that you know the size and position of the metafile, you can proceed 
with playing it.  Normally, you would use GetMetafile to obtain a handle to
a metafile, but if you try to use GetMetafile to read a placeable metafile,
you will discover that nothing happens.  This is because GetMetafile doesnt
work on placeable metafiles because the METAFILEHEADER structure is in the 
way.  So you need to read in the METAFILEHEADER structure from the file and 
then copy the rest of the metafile to a global memory block.  The following 
code shows you how to use GlobbalAlloc and lread to accomplish this as well 
as how to use GetMetafile for a standard metafile (the metafile DOS file 
name is assigned to File$):

Add the following statements to the declarations section (Type statements 
must be in a module):
  Const GMEM_MOVEABLE = &H2
  Const MM_ANISOTROPIC = 8 

  Type RECT
      left As Integer
      top As Integer
      right As Integer
      bottom As Integer
  End Type

  Type METAFILEHEADER
      Key As Long
      HMF As Integer
      bbox As RECT
      inch As Integer
      reserved As Long
      checksum As Integer
  End Type

  Declare Function GlobalAlloc% Lib "Kernel" (ByVal wFlags%, ByVal dwBytes&)
  Declare Function GlobalFree% Lib "Kernel" (ByVal hMem%)
  Declare Function GlobalLock& Lib "Kernel" (ByVal hMem%)
  Declare Function GlobalUnlock% Lib "Kernel" (ByVal hMem%)
  Declare Function lread% Lib "Kernel" Alias "_lread" (ByVal hFile%, ByVal *
  lpBuffer&, ByVal wBytes%)


Dim Ret%, RetL&, Fi%, File$, Tp%, Lft%, Wdth%, Hght%, hMF%, Gptr&, SavedDC%
Dim Bytelen&
Dim WMFH As METAFILEHEADER

Printer.ScaleMode = 3
SavedDC% = SaveDC(Printer.hDC)
File$ = "filename.wmf"
Fi% = FreeFile
Open File$ For Binary As #Fi%
Get #Fi%, , WMFH
If WMFH.key = &H9AC6CDD7 Then  ' If it is not this value, metafile is not 
                               '  placeable
   Wdth% = ((WMFH.bbox.right - WMFH.bbox.left) / WMFH.inch) * (1440 / *
   Printer.TwipsPerPixelX)
   Hght% = ((WMFH.bbox.bottom - WMFH.bbox.Top) / WMFH.inch) * (1440 / *
   Printer.TwipsPerPixelY)
   Tp% = ((WMFH.bbox.Top) / WMFH.inch) * (1440 / Printer.TwipsPerPixelX)
   Lft% = ((WMFH.bbox.left) / WMFH.inch) * (1440 / Printer.TwipsPerPixelY)
   Bytlen& = LOF(Fi%) - 22     ' METAFILEHEADER is 22 bytes long
   hMF% = GlobalAlloc(GMEM_MOVEABLE, Bytlen&)
   If hMF% <> 0 Then
      Gptr& = GlobalLock(hMF%)
      Ret% = lread(FileAttr(Fi%, 2), Gptr&, Bytlen&)
      Close Fi%
   End If
Else
   Close Fi%
   Wdth% = 1200                  ' Arbitrary width
   Hght% = 1200                  ' Arbitrary height
   Lft% = (Printer.ScaleWidth - Wdth%) / 2 ' Center horizontally on the page
   Tp% = (Printer.ScaleHeight - Hght%) / 2 ' Center vertically on the page
   hMF% = GetMetaFile(File$)
End If
Ret% = SetMapMode(Printer.hDC, MM_ANISOTROPIC)
RetL& = SetViewportOrg(Printer.hDC, Lft%, Tp%)
RetL& = SetViewportExt(Printer.hDC, Wdth%, Hght%)
Ret% = PlayMetaFile(Printer.hDC, hMF%)
If WMFH.key = &H9AC6CDD7 Then
   Ret% = GlobalUnlock(hMF%)
   Ret% = GlobalFree(hMF%)
Else
   Ret% = DeleteMetaFile(hMF%)
End If
Ret% = RestoreDC(Printer.hDC, SavedDC%)


You can also use the above code to play a metafile into a VB control; 
however, if you are not concerned about the size of the metafile, you can 
just use Picture1.Picture = LoadPicture("filename.wmf") which will work with
either kind of metafile.


                    Using a metafile as "Print Preview"

The great advantage of a metafile is that it will appear the same no 
matter what device you play it on.  If you use a metafile to create a 
printed report instead of VB statements, you can display the metafile on 
a form by playing it with the following statements:

Dim Ret, RetL&, ShDC%, SavedDC%

Form1.Autoredraw = True
Form1.Cls
Form1.ScaleMode = 3
ShDC% = Form1.hDC
SavedDC% = SaveDC(ShDC%)
Ret% = SetMapMode(ShDC%, MM_ANISOTROPIC)
RetL& = SetWindowOrg(ShDC%, 0, 0)
RetL& = SetWindowExt(ShDC%, Form1.ScaleWidth, Form1.ScaleHeight)
RetL& = SetViewportOrg(ShDC%, 0, 0)
RetL& = SetViewportExt(ShDC%, Form1.ScaleWidth, Form1.ScaleHeight)
Ret% = PlayMetaFile(ShDC%, Metafile%)
Ret% = RestoreDC(ShDC%, SavedDC%)
Form1.Refresh
Form1.Show


Questions or comments about the GRAPHMF project can be sent to Joe 
Oliphant.  E-Mail joe_oliphant@csufresno.edu or CompuServe 71742, 1451.




------------------------------------------------------------------------------
                             My favorite tools
                               by Ian Lynagh
------------------------------------------------------------------------------

[Ian Lynagh has been programming in VB2Pro for just under half a year for a
hobby. He has also had experience in AMSDOS basic, GWBasic and QBasic. He has
never programmed on anything other than a PC(and it's predecessor the CPC).
He can be reached at ian@lynagh.demon.co.uk.]

Below is a list of, in my opinion, what every VB4Win
programmer should know and have.


1) Visual basic for windows

Vital.

2) Message blaster

This handy .VBX allows you to intercept windows messages.
With this you can do things that you can't do in plain VB. An
example of a use for this is when you want to add an item to
the system menu. For it to do anything you must have to
have this or an equivalent.

You can get this from one of the popular sites (item 5) as
MSGBLAST.ZIP usually.

3) VB Helpwriter (VBHW)

There are others, but this is the only one I've investigated,
and it seems to work fine. VBAHA is another that comes to
mind. These are utilities to help you write helpfiles. The
latest version of VB Helpwriter is, I think, 1.8.9. It is written
by Fred Bunn, who is allegedly very helpful.

VBHW is, I think, VBHW189.ZIP usually
VBAHA is, I think, VBAHA94 usually
                                    ~~ not sure about numbers

4) VBKB, VBT&T and all the VB FAQ's
   ~~~~full text searching version

Never be without them. You can get them from the major sites
(section 5). The latest version of Tips and tricks is 94. It
will be called VBTT94.ZIP I should think. The KB is called
VBKB_FT.EXE. Beware. It's massive. I'm not sure about the
FAQ, but try archie-ing FBFAQ.ZIP, VB_FAQ.ZIP, VBFAQ.EXE
etc.

5) Useful FTP sites

ftp.microsoft.com - BEWARE. It's massive. See the FAQ
for a "map".

ftp.cica.indiana.edu/pub/pc/win3/programr/vbasic - VERY BUSY
                     ~~~not sure if there is a pub directory

ftp.cdrom.com/.5/cica/programr/vbasic - mirror of CICA. There
are many others
                      ~~~~~~~~not sure if there is a programr
directory

There are probably other sites and other points that I've
forgotten, but never mind.


------------------------------------------------------------------------------
          Dynamic Table Attaching with Microsoft Visual Basic(TM)
                             by J. Les Gainous
------------------------------------------------------------------------------

[Les Gainous is a Senior Programmer/Analyst with The Walt Disney Company's
Buena Vista Home Video in southern California. Mr. Gainous, a graduate of
Florida State University, has 11 years experience with database programming
and design on platforms ranging from desktop systems to minicomputer
environments. He may be reached at lgainous@studio.disney.com (business hours,
US Pacific time), or les_gainous@ix.netcom.com (evening/weekends).]

                   Also printed in OCVBUG, February 1995

Have you ever deployed an application that utilizes attached tables in Visual
Basic, only to find that the path to these attached tables isn't the same in
the user's environment as in yours?

Well, if you answered "yes", you're not alone. This discussion will address
the issue of environmental differences among the users' workstations as it
pertains to attaching external tables to Visual Basic. With various client
network configurations, it's necessary to apply the dynamic table attaching
technique. This technique involves, through code, finding a table's path and
attaching it to your database. This discussion also explains how to refresh an
existing table link.

Why attach tables?

Before jumping into the details of attaching tables, the question frequently
arises, "Why attach tables when I can access them directly?"  The main reason
for attaching tables is that your application can perform "cross-join"
operations across unlike database formats.

Another question also arises, "Why don't I just import the external table?"
Importing a table severs the logical link from the original external database.
If the external table is being maintained or updated by other applications,
importing is not an option. If you leave the data in attached tables, the
source database continues to administer and control that data.

How It Is Done

Functionally, the dynamic table attaching technique is achieved by specifying
the table name and its path in an initialization (or INI) file. Your
application calls a routine that reads this INI file and extracts the path to
this target table. Once this path is obtained, the table is attach with Visual
Basic code to a database object.

For this discussion, we will dynamically attach a dBase table; specifically,
dBase III format.

How to Get It Done

The technical details on dynamic table attaching is as follows. Listing 1
shows the required entries in your application's initialization (INI) file.

The RefreshNonODBCLinks entry in the [Environment] section specifies if the
dynamic table attaching routine should refresh the links to the already
attached tables.

A setting of "N" helps to save time when loading the application since the
routine to attach the tables does not have to spend time reattaching the table
if it already exists. Setting this to "Y" will cause the routine to detach the
table (if it is attached) and reattach it using the path in the [FileLocations]
section. This is helpful when a user's drive mappings are changed and table
reattachment using a new path is necessary.

The [FileLocations] section is where the path entries for each table to be
attached reside.

Specify the operating system's filename for the table (with filename extension)
and set that equal to the path of the filename. Use of a trailing backslash is
optional.

In order for the dynamic attaching routine to work properly, we will:

1. Setup a constant
2. Declare two Windows API functions (GetPrivateProfileString() and
   WritePrivateProfileString())
3. Insert a "wrapper" used in calling the GetPrivateProfileString() function
4. Insert the actual table attaching routine, AttachExtTables()
5. Call this attaching routine from your application, before the table(s) is
   actually needed (preferably in an initialization routine for the application,
   or the main form's Load event)

A "wrapper" is basically a custom routine to call, which subsequently calls the
actual API function. The wrapper is set up in such a way that makes it easier
to call it than to call the API function directly.

In one of your application's basic code modules (BAS files or modules) make
sure the lines in Listing 2 are placed in the declarations section of the BAS
module. This declares a constant and the two functions required by the attaching
routine.

Finally, place the wrapper routine and the attaching routine (in Listing 3,
and Listing 4, respectively) in a BAS module. This BAS module does not have to
be the same module where the code from Listing 2 was placed, and the two
routines are not required to reside in the same BAS module.

Dynamic Attaching Routine

Listing 4 is relevantly documented with comments. Basically, the flow of
AttachExtTables() is:

1. Set a variable containing the INI file name.
2. Retrieve the RefreshNonODBCLinks setting in the INI file and set a flag,
   bRefresh.
3. Main logic for attaching/reattaching a table:
- Set up variables for the specific table to attach.
- Using the global database object, retrieve the table definitions into the
  table definition object, tdefMainDB. The database object does not have to be
  a global object. If it is not, you must modify the AttachExtTables() routine
  to allow the database object to be passed as an argument.
- Loop through all the table definitions until either, a) the table to attach
  is found, or b) there are no more table definitions at which to look. We
  will skip any system tables by using a bitwise comparison of the table
  attributes property and the global DB_SYSTEMOBJECT constant. If this
  comparison is false, the table is not a system table and we will continue to
  interrogate the table.
- If the table is found, we set a flag indicating so, bTableAlreadyAttached.
- If the table is already attached and we are going to force a reattachment,
  then delete the table link.
- If the table is not already attached or we have deleted the link, retrieve
  the table's path from the INI file, adding a trailing slash if necessary.
- Build the table definition object's connect string property, using the
  retrieved path.
- Set the table definition object's SourceTableName and Name properties.
- Finally, attach this table (using the append method) to the database object.
4. Repeat Step #3 above for any other tables to be attached. Remember that
   each additional table must be specified in the INI file, in the
   [FileLocations] section. The code presented here is fairly "open" to create
   a looping structure, allowing multiple table attachments while minimizing
   code.
5. If we forced a reattachment, set the RefreshNonODBCLinks line in the
   [Environment] section to "N", so as not to slow down the loading of
   subsequent sessions of the application.

Summary

As you have probably noticed, this dynamic table attaching technique can save
valuable time when distributing your application to "unknown" network mapping
environments. Many types of external tables may be attached, such as Paradox,
FoxPro, and dBase IV.

Code Listings

Note: In the following listings, the character, "_" specifies that the
statement is too long to be displayed on one line in the listing. Therefore,
within Visual Basic, the line following this character should be typed in as
part of the previous line. If you are using Visual Basic version 4, you may
use the line-continuation character.

Listing 1: YourApp.INI file

[Environment]
; force reattachment of non-ODBC external tables?
RefreshNonODBCLinks=N

[FileLocations]
; path for this xbase file to be attached (trailing backslash optional)
YOURTBL.DBF=d:\user\data\tables\blah\blah\



Listing 2: YourApp.BAS file (declarations section)

'// database constants
Global Const DB_SYSTEMOBJECT = &H80000002

Declare Function WritePrivateProfileString Lib "Kernel" (ByVal lpAppName$,_
ByVal lpKeyName$, ByVal lpString$, ByVal lpFileName$) As Integer

Declare Function GetPrivateProfileString Lib "kernel" (ByVal lpAppName$,_
ByVal lpKeyName$, ByVal lpDefault$, ByVal lpReturnString$, ByVal nSize%,_
ByVal lpFileName$) As Integer



Listing 3: Utility "wrapper" for the GetPrivateProfileString() API function

Function sGetPrivProfileString (ByVal sAppName As String, ByVal sKeyName As_
String, ByVal sININame As String) As String
'// =========================================================================
'// Calls the windows API to get a value from a profile string entry
'//
'// Les Gainous 05/05/93
'//
'// This utility code module and all it's routines (subroutines,
'// functions, declarations) is property of and owned by Les Gainous.
'// Any use of this source code and/or it's resulting object code
'// requires express written consent of the owner.
'//
'// Modifications:
'// Date     Who Description
'// -------- --- ------------------------------------------------------------
'//
'//
'// =========================================================================
  Dim sAnswer As String
    Dim iSize As Integer
      Dim iValid As Integer
        Dim lpReturnString As String

  lpReturnString = Space$(128)
    iSize = Len(lpReturnString)

  iValid = GetPrivateProfileString(sAppName, sKeyName, "", lpReturnString_
  iSize, sININame)

  '// Discard the trailing spaces and null character.
    sAnswer = Left$(lpReturnString, iValid)

  sGetPrivProfileString = sAnswer

End Function



Listing 4: Main routine to attach external tables

Sub AttachExtTables ()
'// =======================================================================
'// Attaches all non-ODBC tables. If the refresh option is turned on in the
'// .INI file, then force a refresh (reconnect).
'//
'// Les Gainous 12/27/94
'//
'//
'// Modifications:
'// Date     Who Description
'// -------- --- ----------------------------------------------------------
'//
'//
'// =======================================================================
  Dim tdefMainDB As TableDefs             '// table defs of main db
  Dim tdefExt As New TableDef             '// table def for new table
  Dim iTableCount As Integer              '// num of tables in db
  Dim iI As Integer                       '// counter variable
  Dim iTmp As Integer                     '// dummy variable
  Dim sTableName As String                '// table name to attach
  Dim sOSTableName As String              '// filename to attach
  Dim bTableAlreadyAttached As Integer    '// boolean
  Dim sTablePath As String                '// path of table to attach
  Dim sRefreshLinks As String             '// Y-force attachment, N-don't
  Dim bRefresh As Integer                 '// boolean
  Dim sINIFileName As String              '// full pathname of .ini file

  '// on error goto ? (put a call to your error routine here, if needed)

  '// initialize variables; change the path to your INI file, if needed
    sINIFileName = app.path & "\YourApp.ini"

  '// check if we should refresh all external database connections (non-ODBC)
  sRefreshLinks = sGetPrivProfileString("Environment",_
  "RefreshNonODBCLinks", sINIFileName)
  If UCase(sRefreshLinks) = "Y" Then bRefresh = True Else bRefresh = False

  '//******************************************************************
  '// This section pertains to one table attachment only! Repeat this
  '// section for each table to be attached.

  '// *** Add YOURTBL.DBF, if needed ***
  '// first, let's see if it already attached
  sTableName = "YOURTBL"
  sOSTableName = "YOURTBL.DBF"

  '// get database table definitions
  Set tdefMainDB = gdbDatabase.TableDefs

  '// initialize variables for this section
  iTableCount = tdefMainDB.Count - 1
  bTableAlreadyAttached = False
  iI = 0    '// counter for the table def index

  '// loop thru all tables in this database and look for the
  '// target table (sTableName). If we find the table, there is
  '// no need to keep looping, so set a flag which causes a loop exit

  While (iI <= iTableCount) And Not bTableAlreadyAttached
  '// skip system tables; a system table is defined as: the bitwise
  '// comparison of the table's attributes property and the
        '// DB_SYSTEMOBJECT is true
        If (tdefMainDB(iI).Attributes And DB_SYSTEMOBJECT) = 0 Then
                '// if table is already attached, set flag (which will cause
                '// an exit from this loop)
                If tdefMainDB(iI).Name = sTableName Then bTableAlreadyAttached = True
        End If  '// regular table (not a system table)
        iI = iI + 1 '// increment counter
  Wend

  '// if table is not attached or if we're going to force a
  '// reconnect (refresh), then continue; otherwise fall thru to the
  '// end of this "if" structure
If Not bTableAlreadyAttached Or bRefresh Then

  '// if table is already attached, disconnect it before trying to reconnect
  If bTableAlreadyAttached And bRefresh Then
        gdbDatabase.TableDefs.Delete sTableName
  End If  '// bRefresh

    '// retrieve the table's path (using sOSTableName)
    sTablePath = sGetPrivProfileString("FileLocations", sOSTableName, _
    sINIFileName)

    '// append a trailing backslash, if needed
    If Right$(sTablePath, 1) <> "\" Then sTablePath = sTablePath & "\"

    '// construct the connect string (change according to your needs)
    tdefExt.Connect = "dBASE III;DATABASE=" & sTablePath & ";"

    '// set the source table name as it exists in the original data source
    tdefExt.SourceTableName = sTableName

    '// set the name as it is to be known by this database (i.e. an alias)
    tdefExt.Name = sTableName

    '// finally, attach the table to the Access database
    gdbDatabase.TableDefs.Append tdefExt

    '// cleanup after ourselves for this table
    Set tdefExt = Nothing
End If      '// not bTableAlreadyAttached
            '// *** end of the section for attaching YOURTBL.DBF ***

  '//******************************************************************
  '// Repeat the above section for more external table attachments
  '//******************************************************************

  '// set refresh flag to "N" in the ini file, only if we refreshed
    If bRefresh Then
        iTmp = WritePrivateProfileString("Environment", "RefreshNonODBCLinks",_
        "N", sINIFileName)
    End If  '// bRefresh

  '// cleanup after ourselves for this subroutine
  Set tdefMainDB = Nothing

End Sub


------------------------------------------------------------------------------
                            When I'm Calling You
            (A few hints regarding VB calls to DLLs and the API)
                               by Luke Webber
------------------------------------------------------------------------------

[Luke Webber is an independent contract programmer and software developer based
in Melbourne, Australia. Being too pig-ignorant to complete tertiary
qualifications, he had to sneak into programming from operations via operating
system support. Of course this all began nearly 19 years ago, so the people
responsible for his advancement have long since been disciplined. Luke programs
in C (*not* C++) for DOS, Windows and Unix and, of course, in VB/Win. He is
the father of two spoiled girls aged 6 and 3 who occupy most of his time away
from the computer. He can be contacted at webber@werple.mira.net.au]
 
                                  Summary
 
There are a number of hazards that we all face when we call the Windows API
from any language and VB is certainly no exception. This article attempts to
point out some of the more troubling problems that the VB programmer is likely
to encounter. Much of the content of this article can also be gleaned from
chapter 24 of the VB3 Programmer's Guide, but it seems that many programmers
are still confused on the subject, so a slightly more detailed look might be
useful.
 
                          Let the ByVal'er Beware

The manual is fairly clear on the subject of the use of ByVal, but it is a
modifier which will continues to trip up the unwary. For the record, the main
rules are as follows...

1.      Applied to non-string variables, ByVal will cause the value of the
variable to be passed, rather than a long pointer to the memory area
containing the value.

2.      Applied to string variables, ByVal will cause the VB runtime to pass
the address of a null-terminated (C-style) string. Without the ByVal, VB will
pass the handle of the VB string.

3.      A DLL written specifically to support calls from VB might possibly
support passing of VB strings without the ByVal keyword, but IN ALL OTHER
CASES, VB strings should be passed ByVal.

4.      User-defined types cannot be passed ByVal, they must always be passed
by reference (that is, by address). In fact, user-defined types are the only
data types which are routinely passed to the API without the ByVal keyword.

5.      In almost all cases, numeric values such as integers and longs will
be passed ByVal and, of course, user-defined types will be passed by reference,
there being no choice in the matter.
 

By now, many will be wondering what concern it is of the programmer whether an
API argument is passed ByVal or not. After all, the Win 3.1 API help file and
WIN30API.TXT and WIN31EXT.TXT all provide declarations for most of the Windows
API functions, so surely the VB programmer need not bother with such low-level
concerns. This is true enough in most cases, but by no means all. To find out
why, read on.


                              "Any" Just Cause

While most of the standard, off-the-shelf API declarations provided in the box
with VB will do the job just fine without much real attention from the VB
programmer, there exist a significant number of exceptions. These exceptions
are those which declare one or more arguments As Any.

The Any data type was created in order to allow calls to API functions which
can accept references to a number of different types of data in one catch-all
argument reference. The upshot of this is that the VB programmer must always
be alert to the possibility that a declaration might use the Any keyword, so
as to provide the correct call syntax.

To take the simplest (and by far the most common) case of this, a number of
functions have been designed to accept a long pointer to a string (LPSTR, or
ByVal String), except where the argument is not required, in which case a null
pointer should be passed. For example, the GetProfileString function can accept
an optional lpKeyName argument. If lpKeyName is provided, only the value for
the specified entry is returned. If it is a null pointer, a list of all keys
is returned, delimited by the null character (Chr$(0)).

To allow for this functionality in a single declaration, the VB architects
fastened upon the Any data type, declaring GetProfileString as follows...
 
Declare Function GetProfileString Lib "Kernel" (ByVal lpAppName As String, 
        lpKeyName As Any, ByVal lpDefault As String, ByVal lpReturnedString
        As String, ByVal nSize As Integer) As Integer
 
You will note that lpKeyName is declared As Any. 


What this means to the programmer is that in calling GetProfileString (and
many other functions), the ByVal has to be explicitly provided in the function
call. For example...

RetVal% = GetProfileString("windows", "spooler", "Bog off", Result$,
                           Len(Result$)) ' Wrong!

... will return the rather snotty default string "Bog off", rather than "yes"
or "no". This is because the API function never sees the string "spooler",
instead it sees the handle to the VB string, which it treats as a string
pointer to alphabet soup.
 
Instead of the above, we require the function call in this form... 
 
RetVal% = GetProfileString("windows", ByVal "spooler", "Bog off", Result$, 
                           Len(Result$)) ' Right!

In this case, the ByVal refers to a string value, which converts our string to
a long pointer to a C-style null-terminated string. This frequently causes
programmers to conclude that the API declaration is wrong and to "fix" it by
replacing the As Any with a type declaration compatible with their immediate
requirements.

Alternatively, in the case where we wish to return all keys in the [windows]
section, we would call thus...

RetVal% = GetProfileString("windows", ByVal 0&, "Bog off", Result$,
                           Len(Result$)) ' Right!

Note that we have been forced to specify the ByVal in the function call and,
further, to declare the type of the numeric value we are passing. If we leave
off the ampersand type character we will get a "Bad DLL calling convention"
error, number 49. This is because Any doesn't really mean any old Any, but
specifically a 4-byte Any. Of course, if we were passing a numeric variable
instead of a constant, the ampersand would not be necessary, so long as the
variable was declared Long.

An alternative to use of the Any keyword might be to provide multiple
declarations for functions which support multiple data types. For example, we
could declare GetProfileString twice, as follows...
 
Declare Function GetProfileSection Lib "Kernel" Alias "GetProfileString" (ByVal 
        lpAppName As String, ByVal lpKeyName As Long, ByVal lpDefault As String,
        ByVal lpReturnedString As String, ByVal nSize As Integer) As Integer
 
Declare Function GetProfileString Lib "Kernel" (ByVal lpAppName As String, ByVal
                 lpKeyName As String, ByVal lpDefault As String, ByVal
                 lpReturnedString As String, ByVal nSize As Integer) As
                 Integer

The above declarations would give us all of the flexibility of the Any
declaration with none of the headaches. To retrieve all key names in a section,
we'd call GetProfileSection, passing zero for the lpKeyName argument, and to
retrieve the value of a specific section, we'd call GetProfileString with the
section name. In neither case would we need to specify the ByVal statement.

Unfortunately, this workaround is also far from perfect. For one thing, the VB
programmer needs to be aware that there are now two separate declarations for
this function, among many others, invalidating much of the standard SDK
documentation. For another thing, there are some noteable cases where the data
being passed can be quite literally any data type. Take for example those
ubiquitous catch-all functions SendMessage and PostMessage.
 
                           What does it all mean?

Put simply, VB programmers calling API functions must be sure to read any
declarations before coding the calling statements, to check if the occasional
ByVal may be required. It is not enough simply to cut and paste function
declarations and then call by rote, because Any demands that you think before
you call. If an API call fails to perform as expected, you should immediately
suspect that you have left out a ByVal in the function call.

As a further caution, it is always a good idea to ensure that you save your
project before running it if you have made any changes to API function
declarations or added or modified any calling statements. If you disregard
this advice, you stand to lose all program modifications since your last
project save when the VB design environment aborts with a GPF. This is a very
good time to consider the "Save project before run" environment option.
 
                           Reservations required

One final warning. Many API functions require the caller to provide the
address of a memory buffer to be used to return data. For the VB programmer,
this usually means passing a string ByVal (although the ByVal may be already
provided in the function declaration).

The thing to watch in these cases is that the string is pre-initialised to a
sufficient size to accept the maximum amount of data that may be returned, or
General Protection Faults will result.

To allocate space to a string, you may either dimension it as a fixed length
string or use a simple assignment statement, such as...
 
	MyBuf$ = Space$(MaxSize%) 

Contributed by Luke Webber. Internet webber@werple.mira.net.au
Copyright 1995 Luke Webber Consulting Services; ALL RIGHTS RESERVED


------------------------------------------------------------------------------
                       Windows API for Screensavers
                              by Elijah Meeker
------------------------------------------------------------------------------

[Elijah Meeker is a contract programmer in Austin, Texas USA. He has only
been programming for a short time but is willing to say anything likely
sounding given half a chance. He is not satisfied with the number of GPFs he
gets with VB, Toolbook and Director so he is learning C++. He will answer to
any variation on his name exept "Ezekiel". He kan be contacted at
elijah@bga.com.]

There have been a lot of Screensaver questions in c.l.b.v.* and after the
inevitable answer "Article Q106239 in the VBKB" and a little tweaking you
can get any program to be triggered by windows. However when I was
commissioned by Bat Conservation Intl. to do a screen saver of bats for
their current catalog I found there was a lot to consider in a commercial
screen saver. In the end out of hundreds sold I've only had one support call
where the program wouldn't run and a reinstall fixed the problem
(apparently-I never heard from him again, maybe he tossed it!). Your program
upon waking nees to figure out 1)Where it is 2)Who it is and 3)How to act
like people expect a screen saver to act. In this series I won't be focusing
on explaining every nuance of the API calls for two reasons: 1) this is a
practical do-it-this-way-and-it-will-work article, 2) I'm no Daniel
Appleman. In this first installment I'll talk about scoping out the local
landscape.

Part 1: This Sure the Heck Isn't Kansas

With screen savers you can make huge hardware and environment demands of the
user and they will happily labor to accommodate you. No wait that's Wing
Commander III. With screen savers you're the poor stepchild whose one false
step leads to being purged and branded as a evil miscreant. The first
misstep is assuming all your customers have the latest bleeding edge
hardware and for screen savers this usually means graphics cards. My full
screen,  24 bit, .AVI  and stereo 16 bit 44k .WAV playing screensaver- more
appropriately called a hard drive stresser- has a small practical install
base- me and few other power junkies. If we can't control the environment
our software will be run in we should prepare for any eventuality. So lets
take a look around and see where our new "William Wegman Swimsuit
Screensaver" (obscure art reference) has just been installed.

First our variable declarations. I use Option Explicit to avoid general as
well as GPF snakebites.

Option Explicit 
Dim  DisplayBits%, DisplayPlanes%, DisplayWidth%, DisplayHeight%
Dim hDesktopWnd%, hDCcaps%,RetValue%

Now our API Declares:
  Declare Function GetDesktopWindow% Lib "USER" ()
  Declare Function GetDC% Lib "USER" (ByVal hWnd%) 
  Declare Function GetDeviceCaps% Lib "GDI" (ByVal hDC%, ByVal Item%) 
  Declare Function ReleaseDC% Lib "USER" (ByVal hWnd%, ByVal hDC%) 

Let's peek under the hood at our display and get the info we need:  
  hDesktopWnd = GetDesktopWindow()          'get handle to desktop
  hDCcaps = GetDC(hDesktopWnd)     'get display context for desktop
  DisplayBits = GetDeviceCaps(hDCcaps, 12)   'number of bits per pixel
  DisplayPlanes = GetDeviceCaps(hDCcaps, 14)  'number of bitplanes
  DisplayWidth = GetDeviceCaps(hDCcaps, 8)  'horizontal resolution
  DisplayHeight = GetDeviceCaps(hDCcaps,10)  'vertical resolution
  RetValue = ReleaseDC(hDesktopWnd, hDCcaps) 'release display context

Now we'll use part of this info to test for color resolution
  If DisplayBits = 1 Then                    
       	If DisplayPlanes = 1 Then                    
		'See 1) below	
       
	ElseIf DisplayPlanes = 4 Then                    
		'See 2) below
        	End If

  ElseIf DisplayBits = 8 Then                  
    	'See 3) below
	
  ElseIf DisplayBits = 16 Then                
     	'See 4) below
	
  Else
   	'See 5) below
	
  End Sub

1)  Running in 1-bit, 2-color mode,  if your program uses images you might
End if this is true in case someone is trying to load at our beautiful JPEG
image screen saver on a monochrome display. Just because YOU wouldn't think
of it... 

2)  Running in 4-bit, 16-color mode. I've seen a image-based screen saver
that allowed this. The user kept telling me how awful it was when all that
was wrong was that 256 colors looked psychedelic quantized to 16.
Screensaver users may not be motivated to track down what's going on so CYA!

3) Running in 8-bit, 256-color mode. Entry level for nice images. Important
test for anyone using JPEG compression. JPEGs are ALWAYS 24 bit.  I use
Image Knife from Media Architects (plug #1) for  JPEGs and if the display is
8 bit (256 colors) the program  uses the imkReduceColors call to keep the
image from looking hammered. 

4) Running in 16-bit, 65000-color mode. Nice going.

5)  Running in a custom color mode. Here in the rarefied air of  16 million
colors one might expect (besides nosebleeds) that you can act like you own
the place. Big bitmaps, fast display times, cool transitions,etc. But
beware, there are many $100 graphics cards will display 16M colors only very
slowly. Being able to end your screen saver IMMEDIATELY when the user hits a
key or moves the mouse is critical and if they have to wait even 5 seconds
to get their workspace back your program might be history. FXTools from
ImageFX (plug #2) allows you to  interrupt an image loading but you're stuck
if you're using a control (like the Picture Box) that doesn't.

Now what follows is a little silly because you could just say:
   Form1.height = DisplayHeight
   Form1.width =  DisplayWidth
and this is what I did, but let's assume you need some specifics:

If DisplayWidth = 640 Then
    	If DisplayHeight = 480 Then                          
     		 'Running in 640 x 480 mode (VGA).
    	ElseIf DisplayHeight = 350 Then                          
     		'Running in 640 x 350 mode (EGA).
   	ElseIf DisplayHeight = 200 Then                          
    		 'Running in 640 x 200 mode (CGA).
    	End If
  ElseIf DisplayWidth = 720 Then                         
   	 'Running in 720 x 348 mode (Hercules).

  ElseIf DisplayWidth = 800 Then            
   	 'Running in 800 x 600 mode(SuperVGA, 8514/A, XGA).

  ElseIf DisplayWidth = 1024 Then                    
 	 'Running in 1024 x 768 mode (8514/A, XGA ).
  Else
  	'Running in some weird custom mode.
  End If

So who cares? Well our fine Wegman swimsuit pictures look fine stretched
full screen in a proportional 640 x 480 or 800 x 600 mode but look smashed
at 720 x 348 so having a  "Stretch to Screen" option would be a good idea.
Another reason for this feature is that  there are some cards that cannot
stretch an image full screen and fail to show the image at all.

There are several other things you might test for, Windows version (even if
it's an emulated Windows!), determine 286, 386 or 486, 386-enhanced etc. and
for these I will refer you to Chapter 5 in Appleman.
Next issue I will talk about saving and loading settings from .ini files,
how to avoid waking up in the wrong place and basic encryption. 

Have fun, Elijah.

                                 References

Image Knife by Media Architects 71662.371@compuserve.com
FXTools Pro by ImageFX 74431.3331@compuserve.com
Appleman, Daniel - Visual Basic Programmers Guide to the Windows API, Ziff
Davis Press
Wegman, William - artist best known for dressing his dogs up in human clothes
and taking their picures (I'm not a fan but it is more artistic than it
sounds).

------------------------------------------------------------------------------
                           Dearming the Time Bomb
                              By Chad Z. Hower
------------------------------------------------------------------------------

[Chad Z. Hower is an Independent Programmer/Consultant who owns Phoenix
Business Enterprises. Mr. Hower, is a self taught programmer who started
programming on a Timex Sinclair with 4K of RAM in 1980.  He has worked on many
8 bit platforms, Unix, VMS, CP/M, TOS (Atari Developer #10126), Windows, DOS
and many other platforms.  The majority of his current programming is devoted
to telecomunications and the internet, followed by datbase applications.  He
may be reached at CZ_HOWER@Delphi.Com.]

        As many of you will notice, I am a programmer. I am going to do my
best to communicate much of my knowledge to other programmers in the VB
community.  Hopefully my programming knowledge will make up for my writing
skills <g>.  I welcome any comments and/or criticisms.
        VB is a very powerful language, and promotes easy and quick
development of programs in previously unprecedented time frames. Unfortunately
like any programming language, a program can be brought to a screeching halt by
a single typo or error.
        Many programmers intimidated, or unfamiliar with error handling merely
do not use it.  Even advanced programmers migrating from other languages are
often intimidated by the way that basic handles errors. Unforunately, this
leaves your apps being distributed with a time bomb in them, just waiting to
go off.
        I don't know anyone who would purposely distibute any app with a time
bomb in it, so over the next couple issues I will discuss how to "bomb proof"
your applications. After all, how many times have you had "Illegal Function
Call #5" bring your program to a halt, or at least occur?  I am willing to
bet, even the best of programmers have encountered this one.
        I will start out at the very bottom for novice programmers, but by
the end of this series there will be advice for programmers of all levels.  I
will begin by explaining different methods, and how to prevent and handle
errors.  Towards the end, I will cover how to add error tracking information
to your programs, so that you can find and fix most errors immediately on
receipt of an error report.

        Do not be discouraged if some of the first articles are too basic for
you.  We were all beginners once.

        A couple things to consider about errors:

        - Assume that ANY statement in your program can produce ANY error.
        No matter how simple the statement is, or unlikely that it may cause
        one.
        - For maximum protection, give EVERY procedure an error handler.  This
        may sound tedious, but I will discuss methods to make this routine,
        and very easy to implement.
        - For minimum protection give every top level procedure an error handler.
        In an event driven language such as VB, these can be quite numerous. A
        top level routine is any routine that is not called from another
        routine in your program. Basically, I am talking about events.

        I will classify several ways to handle errors in your applications.
 These are my definitions, but undoubtedly others are using some of these
 techniques.  Any application will use a mixture of these methods, and will
 not likely use just a single one, so pay attention to them all.

	My classifications are:

	Basic (Easy to implement, but not very powerful)
		1) Ignorance
		2) Prevention
		3) Forced Conformity

	Advanced
		4) Trapping and aborting
		5) Trapping and resuming
		6) Constant Checking


Please note: All of my examples assume the following is in the declarations
section:
	
	OPTION EXPLICIT
	DEFINT A-Z

Also note for the sake of formatting, any line ending with _ is continued on
the next line.


                               1 - IGNORANCE
                               -------------

        This is the easiest of the methods to use.  However, do not use this
as a cure all. This method is very limited, and should not be used heavily.

	Ex:
	Sub foo(ia)
	On Error Resume Next

		MsgBox "The number is: " & ia

	End Sub

        Now the likeliness that the MsgBox statement will cause an error is
pretty rare, but remember "things to consider".     After all, you never know
what Windows might return.

        IGNORANCE is easy to understand.  It means to just ignore the error.
Suppose MsgBox did return an error? What should/could we do?  What error would
it likely return? In an instance like this, you are basically saying "Who
Cares?".

	Where IGNORANCE could be used:
        - Very small Routines/Functions
        - Small routines with very small loops, and ONLY if they have
          definite terminations.
        - Events that merely have one or two statements to call other
          routines
	

                               2 - PREVENTION
                               --------------

        Prevention is one of the safest form of bomb proofing, but cannot be
used in all situations. It would be both inefficient, and impractical to do so.
Prevention is often, but not necessarily used with user input.

	Ex:
	Sub foo()
	Dim ib

                Do
                        ib = Val(InputBox$("Enter item to select in the_
                             listbox:"))

			If ib<0 or ib>=lstMain.listcount then
                                MsgBox "That is an invalid item number._
                                Please select another."
			Else
				lstMain.listindex = ib
				Exit Do
			Endif
		Loop
	End Sub

        Again, this is a very simple and straight forward method. Unfortunately
many programmers do not use this technique where possible.  You should use it
whenever possible and practical.  Any time you can prevent errors elsewhere in
your program, and alert the user (When used against user input) how to remedy
the situation, should not be passed up.

        This particular example should also be used with another procedure wide
error handler, but I have not covered the type that would be used in this
example yet. (Trap and Abort)

		
                           3 - FORCED CONFORMITY
                           ---------------------

        This method is used when an invalid value would cause an error, but a
default value can easily be predicted.  It is not always the best thing to
"guess", but it is a much better alternative to allowing an error in many
situations.  In a way, this is a form of ignorance also.  But this method
prevents the error before VB catches it.  A common place for this method is in
resizing routines.

	Ex:
	Form_Resize
	On Error Resume Next 'Use Ignorance for a procedure wide method

		'No use in setting the properties if minimized, the
		'User cannot see the textbox anyways
		If WindowState = MINIMIZED then exit sub

		'Position the textbox txtInput on the form with a border
		'around it
		txtInput.Left = 120
		txtInput.Top = 120
		txtInput.Width = mmax(Scalewidth - 240, 1)
		txtInput.Height = mmax(ScaleHeight - 240, 1)

	End Sub

	Function mmax(la as long, lb as long) as long
	On Error Resume Next 'Ignorance Method

		If la>lb then 
			mmax = la
		Else
			mmax = lb
		Endif	

	End Function	

        If you set the width or height properies to anything less than 1, they
will cause an error.  With this method you simply "force them into conformity".
The resize event could easily produce a number less than 1 from the statement
"Scaleheight - 240" if the user sizes the parent form to a small size.

        The three basic methods I covered this time are very basic, but will
prevent both you and your users many headaches if used properly. These methods
will not completely bomb proof your program, but when combined with the
advanced techniques (To be covered in subsequent issues) you can literally
make your program bomb proof.  (Except maybe from the GPF)

				Stop,
					Chad Z. Hower
					Phoenix Business Enterprises

	End
End Sub

------------------------------------------------------------------------------
                                 Cursor DLL
                               by David Bold
------------------------------------------------------------------------------

[David Bold can be reached at david@terminus.ericsson.se]

[Editor's note: Included with this newsletter is a cursor DLL (containing
8 cursors) written by David Bold. I receive this via mail from David. The
instruction text is not written as a newsletter article, just as a guideline.
You can use the cursor DLL freely in your programs. Try the demo program
for more information. The demo program and the DLL is in the CURSOR.ZIP file.]

The cursor.dll contains 8 sample mousepointers named:

	cursor1
	cursor2
	...
	cursor8

These names are used in the LoadCursor API call to pull out the
corresponding resource.  I can supply the cursor.exe file without
the resources so that you can add your own resources using the
resource compiler which comes with the Windows SDK.  You simply
create the cursors using (say) Image Editor, add the file names and
tags to a resource file, and resource compile them with the
cursor.exe file to create a new exefile which is renamed cursor.dll
and copied to the windows directory.  Obviously, if you're happy
with the examples then you don't need to do any of this!

I have also included a demo program which shows the mousepointers
in action (one at a time).  You will need to edit the API call to
use the other bitmaps.  The principle is described in KB entry
Q76666.  You should be aware that the objects must have the property
mousepointer = 0 and that the cursor applies to all instances of that
object.  This is a *class* level change not an object level change.
If you have other instances of the object then all instances must
have mousepointer = 0 otherwise it won't work.  This is a limitation
but there are mechanisms available for dynamically swapping the
bitmap over when the cursor is over a specific object but I'll leave
that as an exercise.

I accept no responsibility for any damage etc that the use of this
causes but I provide it in good faith.  Hope you enjoy it.  Let me
know if you have problems or whatever.

David.

------------------------------------------------------------------------------
               Information on VB Talk via Internet Relay Chat
                               by Ryan Heldt
------------------------------------------------------------------------------

[Ryan Heldt can be contacted at rheldt@ins.infonet.net]

     The Internet Relay Chat (IRC) allows it's users to talk to each
     one another, on the screen, in real time.  The subject matter
     ranges from the Arts to Zoology.
     
     The Internet, unlike Compuserve, does not have forums to ask
     questions or talk about issues.  Sure, there are the newsgroups,
     but it may be a few days before you get an answer, if it's
     answered.  So, my quest is to do this, with the Internet Relay
     Chat system...
     
     VB talk IRC is a somewhat moderated, open discussion forum on
     the IRC (Internet Relay Chat) that is entirely devoted to Visual
     Basic for Windows and DOS.  
     
     Please note, however, VB talk IRC will not be open for discussion
     all the time, but an article will be posted in the c.l.b.v.*
     newsgroups at what day and what time a forum is to be held.

     The forums will be held on channel #VBTALK, but this may change
     due to conflicts with IRC hosts, so watch the postings for channel
     changes.

     Although I am the moderator, this isn't a strictly controlled
     forum.  It's just a place for VB users to ask questions, and
     to answer some, as well.  Please don't send answers via the
     /MSG command.  First of all, the answer cannot be captured
     for the transcript, and secondly, I would like all tips and
     the such to be shared with everyone.  And lastly, DO NOT
     FLAME OTHER PEOPLE!!!  This will not be tolerated.  Please
     respect your fellow programmers, and if you get the urge,
     pass.
   
     I will try to make a transcript a few days after VB talk IRC
     is held.  Please see the FAQ for more information concerning
     transcripts.  If you wish to receive a FAQ, send a message via
     e-mail to rheldt@ins.infonet.net and in the SUBJECT LINE, put
     "Request VB talk IRC FAQ".  If demand is high enough,
     transcripts may be posted to a couple of c.l.b.v newsgroups
     They may also be made available by FTP. Future plans may be to
     create a Windows Help file, such as the VB KnowledgeBase, or
     VB Tips and Tricks!

     I have some VB Celebrities interested in VB talk IRC.  Gary
     Wisniewski, President of Apex Software, said he would try to
     join us.  Ken Lassesen from the Microsoft Developer's Network
     also seemed interested.  I have also contacted several people,
     including Bill Gates and Alan Cooper (know to some as the
     father of Visual Basic).  I have not heard from these people,
     yet, but hopefully I will.

     The next scheduled forum will be FRIDAY, FEBRUARY 10, 1995
     at   5:00 pm Pacific
          6:00 pm Mountain
          7:00 pm Central
          8:00 pm Eastern
          9:00 pm Atlantic
     For more information on IRC, try these FTP locations:
           SITE                    LOCATION
        *  nic.funet.fi            /pub/unix/irc/docs
        *  cs.bu.edu               /irc/support
        *  coombs.anu.edu.au       /pub/irc/docs

     Also, try some books on the Internet.  I have one called "The
     Internet Complete Reference".  It has a good chapter on IRC.

     See you there for VB talk IRC!
    

------------------------------------------------------------------------------
                           Contacting the editor
------------------------------------------------------------------------------

To contact the editor, write to jakobf@colossus.ping.dk (Jakob Faarvang). If
you want to write to him via snail mail, his address is:

                               Jakob Faarvang
                                Kirkebjerg 2
                               5690 Tommerup
                                  DENMARK

Remember: The newsletter is based on people submitting articles, reviews, etc.
Please consider submitting anything you would feel would be of interest to
other VB programmers.

<End of newsletter>
