PC Magazine Utility: PrintNow

  ----------------  Version 1
                    By Gregory A. Wolking
                    Presented in the 4/24/97 issue of PC Magazine

                    Summary

                    Printing the Screen in Windows 95
                    PrintNow makes the PrtSc key work just as it did
                    under DOS

                    Gregory A. Wolking
                    Remember the good old days, when all you had to
                    do to dump your screen to your printer was press
                    the PrtSc key? I do, and it's one of the
                    Benchmark   features I sorely miss now that I'm using
                    Windows 95. You can still get screen dumps from
                    text-mode DOS applications using Shift-PrtSc,
                    but there's no equivalent function for Windows
                    applications.

                    Windows uses the PrtSc key to capture an image
                    of the entire desktop and Alt-PrtSc to capture
                    an image of the active window, but neither of
                    those functions prints anything; they just place
                    a copy of the image in the Clipboard. The only
                    way to get hard copy is to paste the image into
                    a graphics application and print it from there.
                    I wrote PrintNow to eliminate the extra steps.

                    PrintNow runs in the background, waiting for you
                    to press PrtSc or Alt-PrtSc. When you do, it
                    presents a standard Print dialog, so that you
                    can select the desired printer settings, and
                    then prints the image you just captured. You can
                    also use PrintNow manually to print almost any
                    device-independent bitmap (DIB) image you place
                    in the Clipboard.

                    Though it was written for the Windows 95
                    environment, most of PrintNow's features work
                    under Windows NT 4.0 as well. Under Windows NT,
                    PrintNow will correctly print and scale bitmaps
                    from the Clipboard. Because of differences in
                    the implementation of certain API functions,
                    however, PrintNow cannot detect when a user has
                    pressed PrtSc or Alt-PrtSc. So if you run
                    PrintNow under Windows NT 4.0, you will have to
                    use PrintNow's menu or dialog box to print the
                    Clipboard image after pressing PrtSc or
                    Alt-PrtSc.

                    The source code for PrintNow, which was written
                    in Microsoft Visual C++, is also provided for
                    those interested in seeing how it works.

                    Using PrintNow

                    To install the program, place the program files
                    Printnow.exe, Printnow.hlp, and Printnow.cnt in
                    a directory of your choice, then create a
                    shortcut to Printnow.exe. PrintNow does not
                    write to an .INI file or to the Windows
                    Registry. You may wonder, then, where PrintNow
                    stores your preferences. The answer is that it
                    doesn't. As you'll see, PrintNow's
                    multiple-instance feature makes saving
                    preferences undesirable. But you can use
                    command-line switches (refer to the program's
                    help for details) to launch PrintNow using
                    settings other than the defaults. To remove the
                    program, you need only delete its files from
                    your disk.

                    When you launch PrintNow, the program adds its
                    icon to the system tray. Point at the tray icon
                    and the ToolTip will indicate the program's
                    current status. Right-click on the icon to open
                    the pop-up menu (see Figure 1). Double-click on
                    the icon to activate the main dialog (see Figure
                    2). The dialog contains the same settings and
                    commands as the menu, so you can use whichever
                    interface you prefer. If the dialog is already
                    open, click either mouse button on the tray icon
                    to bring it to the foreground.

                    The dialog works much like a property sheet. If
                    you change a setting, it does not take effect
                    until you click either the Apply or the OK
                    button. The Apply button leaves the dialog open,
                    and the OK button hides it. Clicking the Cancel
                    button hides the dialog and discards your
                    changes, except for printer settings; the Print
                    dialogs have their own OK and Cancel buttons. To
                    get pop-up help for any of the dialog's
                    controls, point at the control, click the right
                    mouse button, and select What's This? from the
                    context menu. To get full help, click the Help
                    button or press F1.

                    Conversely, when you use the tray icon's pop-up
                    menu to change a setting, the new setting takes
                    effect immediately and the menu reopens
                    automatically, so that you can see the change.
                    Technically, this is nonstandard behavior, but I
                    find it annoying for pop-up menus with multiple
                    check-box or radio-button items to close
                    immediately after you change a setting.
                    Otherwise, the menu's behavior is standard. To
                    close it, select Close Menu, click anywhere
                    outside the menu, or press the Esc key.

                    PrintNow Settings

                    You control PrintNow's behavior using check
                    boxes and radio buttons or their pop-up-menu
                    equivalents. The Screen Capture Enabled check
                    box determines whether PrintNow activates
                    automatically when you press PrtSc. By
                    unchecking this box, you can disable PrintNow
                    without removing it from memory.

                    The Always Show Print Dialog check box
                    determines whether PrintNow presents a standard
                    Print dialog box before printing each image, so
                    you can change your printer settings or cancel
                    the operation. When the box is cleared, PrintNow
                    prints each image using the current printer
                    settings.

                    The Center Image check box determines whether
                    PrintNow will center the image on the page. When
                    the box is cleared, the program prints the image
                    flush at the top left corner of the page.

                    The Image Scaling radio buttons let you control
                    image scaling. The options Full, 3/4 Page, and
                    1/2 Page scale the image to fill the specified
                    portion of the total page area while maintaining
                    the image's aspect ratio. The No Scaling option
                    does not scale the image unless it is larger
                    than the page. In that case, PrintNow shrinks
                    the image as necessary, without attempting to
                    maintain its aspect ratio. If the image is too
                    wide for the page, the program shrinks the width
                    to fit. If the image is too tall for the page,
                    the program shrinks the height to fit. With most
                    printers, this option produces a very tiny
                    printout, because each pixel in the image maps
                    directly to one pixel (or less) on the page. For
                    example, on a printer that prints 300 dots per
                    inch (dpi), an 800-by-600 screen with no scaling
                    will be rendered 2 inches high and under 3
                    inches wide.

                    PrintNow Commands

                    To print a copy of the image currently stored in
                    the Clipboard, select Print Clipboard Image. Any
                    image in DIB format, regardless of its source,
                    will be printed; for example, you might select a
                    rectangular area in Paint and copy it to the
                    Clipboard. The Print Clipboard Image item is
                    disabled if the Clipboard does not contain a DIB
                    image when you open the menu or launch the
                    dialog. The Clipboard's contents may change
                    while the dialog or menu is open. If the Screen
                    Capture option is enabled, the dialog's command
                    button will track the Clipboard's contents
                    dynamically, but the pop-up menu cannot do this.
                    If the Clipboard's contents are no longer in the
                    proper format by the time you issue the command,
                    PrintNow presents an appropriate message and
                    cancels the operation.

                    Select Print Setup to launch a standard Print
                    Setup dialog box and change your printer
                    settings. This is particularly useful when you
                    have disabled the Always Show Print Dialog
                    setting.

                    The Unload PrintNow (n) command terminates the
                    current instance (n represents the instance
                    counter) of the program and removes its icon
                    from the system tray; Unload All Instances does
                    the same for all instances, including the
                    current one. If any instances are printing when
                    you issue this command, they will shut down
                    immediately after they have finished printing.

                    Using Multiple Instances

                    You may run more than one instance of PrintNow
                    at a time. Each instance will maintain its own
                    settings and generate its own printout. Thus,
                    you can send a screen dump to multiple printers
                    or make multiple copies with different printer
                    settings, all with a single keystroke. Each
                    instance's tray icon will look the same, but you
                    can identify which is which by the instance
                    counter. You can see this number, displayed
                    inside parentheses, in the tray icon's ToolTip,
                    the pop-up menu's Open and Unload commands, and
                    the main dialog's caption. The instance counter
                    starts at 1, increases by 1 for each instance
                    you launch, and does not reset until all
                    instances are unloaded.

                    Multiple instances work most efficiently if you
                    use the Print Setup command to select the
                    desired printer and printing options, then
                    disable the Always Show Print Dialog option. If
                    you use multiple instances to print to the same
                    printer, that printer's driver must have print
                    spooling enabled. Each instance prints
                    asynchronously, so the order in which the images
                    print will usually have no relation to the
                    instance counter.

                    While printing is in progress, each instance
                    requires enough memory to hold its own separate
                    copy of the image. If all you want to do is
                    print multiple copies of an image on the same
                    printer with the same settings, it's much more
                    efficient to use the Copies field in the Print
                    dialog than to launch a separate instance of
                    PrintNow for each copy.

                    Printing to a File

                    PrintNow can print to a file as well as to a
                    physical printer. Just check the Print to File
                    box in the Print dialog, or select a printer
                    driver that's connected to the FILE: device.
                    Windows will present a file selection dialog
                    when printing begins. Using this function
                    bypasses the print spooler, so only one
                    application can use that printer driver at a
                    time. If you want multiple instances of PrintNow
                    to print to files, each instance must have its
                    Always Show Print Dialog setting enabled. When
                    PrintNow is activated, you must pick one Print
                    dialog, acknowledge it, then wait for that
                    instance to finish printing before proceeding to
                    the next instance.

                    The most important consideration is that some
                    printer drivers are horribly slow when printing
                    to a file. Therefore, you should not use this
                    option while any timing-sensitive applications,
                    such as communications software, are running.
                    You must also be patient. While printing,
                    PrintNow changes the mouse pointer to a wait
                    cursor. The system will appear to ignore any
                    mouse and keyboard input, but it doesn't. It
                    merely delays processing your input until
                    printing finishes, then it performs all of your
                    commands in rapid succession. To prevent the
                    possibility of sending commands to the wrong
                    window, you should avoid using the mouse buttons
                    until printing has been completed.

                    Working with DOS Applications

                    DOS applications fall into two general
                    categories: text-mode and graphics-mode. For
                    text-mode applications, Windows provides the
                    Shift-PrtSc command to dump the screen directly
                    to LPT1:, just as PrtSc alone does in DOS. This
                    works whether the application is running
                    full-screen or in a window. If the application
                    is running full-screen, PrintNow will not react
                    when you press PrtSc or Alt-PrtSc, because
                    Windows places the data in the Clipboard as
                    text, not as a DIB image. PrintNow can print
                    only DIB images.

                    When a text-mode application running in a window
                    is active, pressing Alt-PrtSc captures a DIB
                    image of the entire window, and PrintNow will
                    print it just as it would any other window. This
                    provides a couple of advantages over the
                    Shift-PrtSc command, which prints only plain
                    text in your printer's current font on an
                    otherwise blank page.

                       * You get an accurate rendition of the entire
                         window, including its frame and caption, as
                         it appears on the screen--in color, with a
                         color printer, or otherwise in gray-scale.
                         For example, programs that display white
                         text on a black background (the default DOS
                         colors) will print as white text on a black
                         background, not black text on a plain page.

                       * Your printer won't go nuts if the display
                         contains characters that your printer would
                         interpret as control codes instead of as
                         literal text.

                       * You won't have to eject the page manually
                         when the screen dump is finished.

                    The sole drawback is that with a monochrome
                    printer, some color combinations don't reproduce
                    very well.

                    For graphics-mode applications running
                    full-screen, PrtSc and Alt-PrtSc perform the
                    same function. They capture the entire screen,
                    if possible, and place it in the Clipboard as a
                    DIB image. The key phrase is if possible; many
                    such applications use the display adapter in a
                    way that prevents Windows from capturing the
                    screen. If Windows can't capture the image, it
                    will present an error message telling you that
                    the program's display mode doesn't allow
                    capture, and of course there will be nothing for
                    PrintNow to print.

                    Even if Windows can capture the image, PrintNow
                    will not activate automatically when you press
                    PrtSc. Some DOS applications misbehave when
                    Windows suspends them to switch to the desktop,
                    so I left it to you to decide whether to switch
                    away from a DOS application while it's still
                    running. Once the image has been captured, it
                    will remain in the Clipboard until you replace
                    it with something else. You can use PrintNow's
                    Print Clipboard Image command to print that
                    image whether you shut the application down
                    completely or just switch away from it
                    temporarily.

                    Inside PrintNow

                    I wrote PrintNow using Microsoft Visual C++,
                    Version 4.0. The program takes advantage of two
                    Windows features. The first is the fact that
                    PrtSc and Alt-PrtSc place a copy of the desired
                    region into the Clipboard as a DIB. Since DIB
                    format is native to Windows, I can use Windows
                    API functions to manipulate the image. The
                    second is a Windows mechanism called the
                    Clipboard viewer chain, which allows
                    applications to hook themselves into the
                    Clipboard and receive notification messages
                    whenever the Clipboard's contents change. An
                    application that uses this mechanism is called a
                    Clipboard viewer.

                    Designing a Clipboard Viewer

                    Clipboard viewers use three API calls to
                    communicate with the Clipboard viewer chain:

                       * GetClipboardViewer() returns the handle of
                         the first window in the chain.

                       * SetClipboardViewer() adds a window to the
                         beginning of the chain and returns a handle
                         to the next viewer in the chain.

                       * ChangeClipboardChain() removes a window
                         from the chain.

                    Once a window is added to the chain, it receives
                    WM_DRAWCLIPBOARD messages when the Clipboard's
                    contents change and WM_CHANGECBCHAIN messages
                    when a viewer other than itself is removed from
                    the chain. For the viewer chain to work
                    properly, each viewer must provide handlers for
                    these messages. In addition to any processing of
                    their own, they must pass these messages along
                    to the next viewer in the chain, when
                    appropriate. Finally, you must remove your
                    viewer from the chain before you destroy it.

                    You need to take special care when calling
                    SetClipboardViewer(). You must save the handle
                    it returns and use that handle to pass any
                    WM_DRAWCLIPBOARD or WM_CHANGECBCHAIN messages
                    down the chain when appropriate. You cannot rely
                    on a NULL return value from SetClipboardViewer()
                    to indicate an error condition. The function
                    does return NULL if it fails, but it also
                    returns NULL if the chain is empty. Therefore,
                    you must call GetLastError() immediately after
                    calling SetClipboardViewer(). GetLast-Error()
                    returns a DWORD. If bit 31 of that DWORD is set,
                    SetClipboardViewer() failed. In Visual C++, you
                    can use the SUCCEEDED() macro to test this
                    return value.

                    It is critical to note that if
                    SetClipboardViewer() succeeds, the system uses
                    Send-Message() to send a WM_DRAWCLIPBOARD
                    message to your window before
                    SetClipboardViewer() returns. Since
                    SetClipboardViewer() has not yet returned a
                    "next viewer" handle, your window's
                    WM_DRAWCLIPBOARD handler must not try to pass
                    this first message down the chain. You should
                    use a flag of some sort to prevent this, or the
                    results can be unpredictable. This is the only
                    time your viewer does not have to pass a
                    WM_DRAWCLIPBOARD message down the chain.

                    It is possible to add a given window to the
                    chain more than once. If you do this twice in
                    direct succession, your window's "next viewer"
                    handle will point to itself. As a result, when
                    the system sends a WM_DRAWCLIPBOARD message to
                    your window, it gets stuck in a loop sending the
                    message to itself. This causes infinite
                    recursion, which will eventually crash the
                    application, and possibly the entire system.
                    Therefore, it is vital to make sure that your
                    window is not already hooked into the chain
                    before calling SetClipboardViewer().

                    When the Clipboard removes a viewer from the
                    chain, it sends a WM_CHANGECB-CHAIN message down
                    the chain after removing the window. The message
                    parameters provide two handles, of the window
                    that was removed and the window that follows it.
                    Each remaining viewer must check to see if its
                    "next viewer" handle points to the window that
                    was removed. If not, it simply passes the
                    message along to the next viewer. If so, it must
                    replace the old handle with the new one.

                    I should point out that while debugging my
                    WM_CHANGECBCHAIN handler, I discovered a nasty
                    error in the documentation for the Microsoft
                    Foundation Classes (MFC). MFC provides its own
                    ChangeClipboardChain() function as a member of
                    the CWnd class. The help topic for
                    CWnd::ChangeClipboardChain() states that its
                    return value (TRUE or FALSE) indicates success
                    or failure, and I was depending on that for
                    PrintNow's error checking. It took me a long
                    time to figure out why PrintNow kept crashing if
                    there were any other viewers in the chain when
                    it tried to unhook itself. You'll find correct
                    documentation for this function in the Windows
                    API reference. Specifically, if there are other
                    windows in the chain, the return value is
                    typically FALSE, because that's what each
                    window's WM_CHANGECBCHAIN handler should return
                    when it processes the message. If your window is
                    the only one in the chain, the return value is
                    typically TRUE. But the key word is typically,
                    because not all viewers necessarily respond this
                    way--especially if they were written using MFC
                    by someone depending on Microsoft's
                    documentation! The correct way to verify whether
                    ChangeClipboardChain() succeeded is to call
                    GetLastError() immediately after
                    ChangeClipboardChain(), then use the SUCCEEDED()
                    macro to test the result.

                    When writing your window's WM_DRAWCLIPBOARD and
                    WM_CHANGECBCHAIN handlers, it's important to
                    realize that the system uses SendMessage() to
                    send the message to the top of the chain, and
                    each viewer must also use SendMessage() to pass
                    them along. Unlike the related function
                    PostMessage(), the SendMessage() function does
                    not return until the receiving window has
                    processed the message. That means the system
                    waits for each viewer in the chain to process
                    the message before execution continues.
                    Therefore, you should design these handlers as
                    efficiently as possible to avoid tying up the
                    system for too long.

                    I wrote my OnDrawClipboard() handler as a series
                    of simple tests to determine whether it should
                    take action when it receives the message. I
                    begin by checking a Boolean flag set by the
                    Hook_Clipboard() function. If this flag is set,
                    I know that the message resulted from hooking
                    into the chain, so I simply clear the flag and
                    exit the handler. Next, I test the handle to the
                    next viewer in the chain. If it's not NULL, I
                    use SendMessage() to pass the message along and
                    let any other viewers handle it before
                    continuing with my own code.

                    When SendMessage() returns, I check a Boolean
                    flag to see if the main dialog is active. If so,
                    I use PostMessage() to place the
                    WM_MY_UPDATE_BUTTON message into the main
                    dialog's message queue and continue processing
                    without waiting for the dialog to handle the
                    message. This lets the dialog enable or disable
                    the Print Clipboard Image command button
                    appropriately when the Clipboard's contents
                    change.

                    After checking another Boolean flag to make sure
                    the program is not already busy printing an
                    image, I call IsClipboardFormat-Available() to
                    see if the Clipboard contains a DIB image. If it
                    does, I test the handle of the Clipboard owner
                    to see if it is the Desktop window. If it is, I
                    know that the image got there as a result of
                    PrtSc or Alt-PrtSc (as opposed to being cut or
                    copied to the Clipboard from another
                    application), so I know that I should print the
                    image. Once again, I use Post-Message() to place
                    the WM_MY_PRINT_CLIPBOARD message into the
                    dialog's message queue, then exit the handler.
                    Therefore, printing does not begin until after
                    OnDrawClipboard() has returned control to the
                    system.

                    Pop-up-menu Pitfalls

                    I had major problems with the tray icon's pop-up
                    menu, which I launch using the TrackPopupMenu()
                    API call. The menu would operate correctly as
                    long as I selected a command from it, but it
                    went nuts if I clicked outside its borders.
                    Normally, doing so should close the menu, but it
                    didn't. The menu would remain on the screen
                    until I activated another window and then passed
                    the mouse pointer back over the menu. After
                    that, the menu would sometimes fail to launch,
                    flickering briefly on the screen and
                    occasionally locking the program entirely. The
                    first workaround I found was to move the dialog
                    completely off the screen, make it visible,
                    launch the menu, then hide the dialog again
                    after the menu closed. This worked, but it
                    required a lot of extra code to keep track of
                    the dialog's position when I wanted to display
                    or hide it under normal circumstances.

                    Fortunately, I got a tip from fellow Utilities
                    column author John Deurbrouck. He pointed me to
                    an article (Q135788) in the Microsoft Knowledge
                    Base. In short, the article says that in order
                    for TrackPopupMenu() to work properly when the
                    menu's parent window is hidden, you must call
                    SetForegroundWindow() to make the parent window
                    active before calling TrackPopupMenu(). When
                    TrackPopupMenu() returns, you must post a dummy
                    message (such as WM_NULL) to the parent window
                    to force it to switch back out of menu mode.
                    Ironically, the MSKB article states that this
                    problem is by design!

                    Tracking Multiple Instances

                    The ability to run multiple instances of
                    PrintNow can be very useful, but I had to find a
                    way for a user to tell which instance of the
                    program is active at any given time. I decided
                    to use a simple counter and place it in the main
                    dialog's caption, the menu's Open and Unload
                    command captions, and the tray icon's ToolTip
                    text. To provide easy access to this counter, I
                    use a named file-mapping object. Using a named
                    object allows each instance to find the shared
                    data without having to communicate directly with
                    the others.

                    When PrintNow starts, the InitInstance function
                    calls OpenFileMapping() to open a memory-mapped
                    object named PrintNow Data. If the call fails, I
                    know the object doesn't exist yet, so no other
                    instance is running. If so, I use
                    CreateFileMapping() to create a new object. I
                    specify 0xFFFFFFFF as the function's first
                    parameter to tell the system to create the
                    object in the system page file, name the object
                    PrintNow Data, and make it just large enough to
                    hold a UINT (though of course an entire
                    4,096-byte page is allocated).

                    Once I've obtained a handle to the file-mapping
                    object, I call MapViewOfFile() to tell the
                    system how I will access its contents. This
                    function returns a pointer to the first byte of
                    the specified region of the object. Since I'm
                    using a single UINT for my counter, I cast the
                    return value as a UINT * and store it for later
                    use. Once I have the counter's address, I set
                    its value to 1 if it's the first instance or
                    increment it.

                    Using file-mapped data normally requires some
                    type of synchronization mechanism, such as a
                    Mutex, to keep two or more instances of an
                    application from trying to modify the same piece
                    of data at the same time. In PrintNow, I access
                    the mapped data only once, during
                    initialization, so that is not a problem. All I
                    have to worry about is cleaning up when the
                    program terminates, so my ExitInstance()
                    function calls UnmapViewOfFile() to release my
                    view of the object, then closes the object's
                    handle.

                    Once all views of a mapped file object have been
                    released, the system automatically destroys the
                    object. If I were using a temporary file to map
                    the data instead of the system page file, I
                    would also have to provide a way to determine
                    when the last instance terminates, so I would
                    know whether to delete the temporary file. Using
                    the system page file solves this problem neatly
                    and eliminates any artifacts, such as undeleted
                    .TMP files that could be left behind if an
                    instance terminates abnormally.

                    The program's complete source code is available
                    online for your perusal. It's thoroughly
                    commented, so you shouldn't have any problems
                    following it. PrintNow brings a handy feature
                    from the DOS world to Windows, and it also
                    demonstrates some useful programming techniques.

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

                    Gregory A. Wolking is the primary sysop of the
                    ZNT:TIPS Forum on ZDNet/CompuServe and moderator
                    of the PC Magazine Utilities discussion area on
                    the Web (www.pcmag.com/discuss.htm).



 
