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).