Screen Capture the Visual Basic Way

Thursday Aug 12th 1999 by Peter G. Aitken

One of the most useful Windows utilities is a screen capture program. People who write about computers use screen capture regularly, usually to illustrate a book or magazine article or to create figures as part of an in-house training manual.

One of the most useful Windows utilities is a screen capture program. People who write about computers use screen capture regularly, usually to illustrate a book or magazine article or to create figures as part of an in-house training manual. Thanks to the Windows API, capturing the screen image is not a difficult task, and in this column, I'll show you how to do it in Visual Basic. We'll also take a look at precise form sizing, Visual Basic speed comparisons, and finally, newsgroups that are of interest to the Visual Basic programmer.

Capturing the Screen

Microsoft recognized the importance of being able to capture the screen by building a basic capture facility into Windows itself-simply press PrintScreen or Alt+PrintScreen to copy an image of the entire screen or of the active window, respectively, to the clipboard. There are also plenty of specialized screen capture programs out there, and some of them are quite sophisticated. Even so, you may want to do it yourself and decide either to include capture capability as part of a larger Visual Basic application or create a capture utility that works exactly the way you want it to.

The utility that I develop here is fairly simple. When the Capture button is clicked, the program minimizes itself, captures the entire screen area, and then reappears with the captured image displayed. Clicking Save lets you store the image on disk as a BMP file. Because the program uses the Windows clipboard for temporary storage of the image, you can capture an image and then paste it into any graphics program (such as Photoshop) for additional manipulation or for saving in a different format.

When I first started to plan this program, I considered the possibility of using Windows to get the screen image on the clipboard and then using Visual Basic to retrieve the image. Theoretically, this should work fine, using the SendKeys() procedure to "press" the PrintScreen or Alt+PrintScreen key. I did not, in fact, try this approach, so I cannot be sure that it actually works. Rather, I decided to do everything within Visual Basic. This provides more flexibility and provides the opportunity to learn something about the inner workings of Windows. As you may have already guessed, we'll need to call on some Windows API functions to get the job done.

Everything in Context

At the heart of it all is something that Windows calls a device context. You can think of a device context as an in-memory representation of a physical device, typically the screen or a printer. Your program interacts in a device-independent manner with the device context, and Windows (by means of drivers for the specific device) links your program to the actual device. If we create a device context for the screen, we can use it to write text or graphics to the screen. More important for the current project is that we can also use a screen device context to access the image that is presently displayed on the screen.

How is a device context related to a display context? A display context is a special type of device context that is connected to a specific window rather than to the entire screen. The Windows operating system treats each window as a separate display surface, sort of a virtual screen. With a display context, a program is limited to writing text or graphics to the related window, and it cannot access other parts of the screen. A screen device context, in contrast, permits access to the entire screen, and for that reason can be a dangerous tool. For this project, we will only be reading data from the screen, so there should be no problems.

Once we have created a screen device context, what's next? We need to convert the screen image data into a bitmap. You can't just throw any kind of data on the clipboard; the data has to be in one of several recognized formats (including the Windows bitmap format). This is not as simple as it may seem, requiring as it does two distinct steps.

First, we need to create a compatible device context. A compatible device context has the same characteristics as a "real" device context, meaning that it is compatible with the associated device. However, it is not connected with any physical device and exists only in memory. Thus, if you create a device context that is compatible with the screen device, the compatible device context will have the same characteristics, such as pixel dimensions and color depth, as the actual screen. You can use graphics statements to "draw" to a compatible device context, but you won't see the result until you transfer the final image from the compatible device context to the "real" device context. This technique is commonly used by Windows programs because transferring an image from one device context to another is a lot faster than the process of constructing the original image. For the screen capture utility, we will do things backwards, transferring the existing screen image from the screen device context to a compatible device context.

Before we transfer the screen image to the compatible device context, however, we must put an empty bitmap in it. What exactly does this mean? In one sense, every image in Windows is a bitmap because the individual picture elements (or pixels) are represented by data (bits) in memory. In this case, I'm referring to a special kind of Windows bitmap format that contains not only the pixel data but also a couple of header structures that further contain additional information about the bitmap, such as its dimensions and color depth. A device context can do lots of things with a "plain" bitmap that is not in this special format, but for certain actions, including transferring the image to the clipboard, it is necessary that the image data be in this format. Having the image in Windows bitmap format will also simplify other program tasks, such as displaying it in an Image control and saving it to disk.

At this point, most of the work is done. Once we have a copy of the screen image in the compatible device context, in Windows bitmap format, it is an easy matter to use API calls to copy the image to the clipboard and then from the clipboard into an Image control for display. From there, we can save the image to disk with Visual Basic's own SavePicture statement.

The project consists of one form module and one Basic module (code for these modules is given in Listings 1 and 2). The form contains an Image control with all properties at their default values, a Common Dialog control named CD1, and a control array of three command buttons with the captions &Capture, &Save, and E&xit. There are plenty of places for enhancements in this program, such as capturing part of the screen or staying hidden and capturing to an automatically generated file name with a hotkey. Once you know the secrets of screen capture, the bells and whistles are easy.

Curiously, while I can get this program to work under Windows 95, I cannot get it to work under Windows NT 4.0. The problem is that the CreateDC() API function returns Null and not a valid device context. The reference material I have consulted says that CreateDC() should work identically under both NT and 95, but it doesn't, at least not for me. I will continue to investigate this and report my findings in a future column.

Sizing a Form to a Specified Size

While working on a recent graphics programming project, I wanted to display a form at an exact pixel dimension. Why? The form contained a Picture Box control in which I was going to display a bitmap. It was easy enough to automatically size the Picture Box to fill the form's client area by putting this code in the form's Resize event procedure:

Private Sub Form_Resize()
    On Error Resume Next
    Pic1.Move 0, 0, Me.ScaleWidth, _
End Sub

It was not as easy to figure out how to make the form the exact same size as the bitmap (whose pixel dimensions I knew ahead of time). The problem was that the inner display area of the form-the part inside the borders and title bar-needed to be set to a specified size. However, the only way to size a form is by setting its Height and Width properties (either directly or by using the Move method), and these properties specify the form's outer dimensions in twips, not its inner dimensions in pixels.

Rather than wracking my brain, I posted my question to one of the several Visual Basic newsgroups. (More on these later.) An elegant solution was quickly forthcoming from someone on the group. Although I do not remember the individual's name, he or she has my thanks. Here's how it works:

Once the form is loaded, it has a "size" even though it has not been displayed yet. By setting its ScaleMode to Pixels and reading the ScaleWidth and ScaleHeight properties, we can determine the current pixel dimensions of the form's inside area. Comparing these values to the desired pixel dimensions of the form's inside area. Comparing these values to the desired pixel dimensions gives us the change in size (in pixels) that is required.

Next, we read the Screen object's TwipsPerPixelX and TwipsPerPixelY properties to find out how many twips are in each horizontal and vertical pixel. (Twips, you'll recall, are Visual Basic's default unit of screen measurement.) Multiplying these values by the desired dimension change in pixels tells us by how many twips the form's size must be changed. Note that because the size of the form's borders and title bar remain constant, changing the form's overall size (the Height and Width properties) by a specified amount changes the size of the internal area by the same amount.

Once these calculations are complete, it is a simple matter to use the Move method to resize the form. Here's the code for a general purpose procedure that can set any form to have a specified internal size expressed in pixels:

Public Sub SizeFormByPixels(frm As Form, _
     nWidth As Integer, nHeight As Integer)
  Dim OffsetX As Integer
  Dim OffsetY As Integer
  Dim NewSizeX As Integer
  Dim NewSizeY As Integer
  Dim OldScaleMode As Integer

  With frm
    OldScaleMode = .ScaleMode
    .ScaleMode = vbPixels
    OffsetX = .ScaleWidth - nWidth
    OffsetY = .ScaleHeight - nHeight
    NewSizeX = .Width - (Screen.TwipsPerPixelX _
    NewSizeY = .Height - (Screen.TwipsPerPixelY _
    .Move .Left, .Top, NewSizeX, NewSizeY
    .ScaleMode = OldScaleMode
  End With
End Sub

Then, in the appropriate location in the form's code, call the procedure as follows:

Call SizeFormByPixels(Me, PixWidth, PixHeight)

As written, the procedure does not change the form's position. It would be a simple matter to modify the code to center the form on screen, within its parent MDI form, or wherever you like.

How Fast?

If someone were to poll all Visual Basic programmers and ask what the most important new feature in Visual Basic 5 is, I bet that the most common answer would be native code compilation. Most important, of course, is that it deprives Delphi proponents of their most important argument in their attempts to show that Delphi is superior to Visual Basic. But seriously, folks, native code does offer the real advantage of faster program execution. Furthermore, because Visual Basic 5 uses the same compiler technology as Visual C++, which has a deserved reputation for being fast, we might expect some real improvements over Visual Basic 4. But just how much of an improvement can we expect, and how does Visual Basic 5 compare with other development tools?

The process of performing benchmarks is fraught with peril. The goal is to obtain some numerical indication of the perceived speed of applications created with a particular development tool. It is this somewhat nebulous "perceived speed" that users care about, and at best, a benchmark can provide some numerical indication that programs written with compiler X will be perceived to run a lot faster, a little faster, or about the same as programs written with compiler Y. Then there are always arguments about what code should go into the benchmark programs. Fast math operations may not influence the execution of a word processing program, for example. There are no perfect benchmarks, and someone will always complain, particularly if the results show that their favorite development tool is not as fast as they "know" it is!

All this being said, let's take a look at what I did. I compared Visual Basic 4.0 (32 bit), Visual Basic 5 native code, Visual C++ 5.0, Delphi 2.0, and Visual J++ 1.1. All tests were run on the same system, a 200MHz Pentium/MMX with 64MB of RAM and no network connections. I rebooted before each run to ensure that the system was in the same state each time. Timing was done with each language's own time-related statements. To the extent permitted by each development environment, compiler options were set to favor fast code over small code. I devised 3 test programs that each executed a block of code 1 million times in a loop:

  • The math suite performed a variety of integer and floating point operations: addition, subtraction, division, exponentiation, multiplication, and modulus.
  • The string suite performed search, concatenation, and extraction operations.
  • The graphics suite performed a sequence of drawing and pixel manipulation operations using the language's native statements rather than API calls.

In the results we see that version 5 of Visual Basic is faster than version 4 in all categories, but not strikingly so except for graphics. I'm not sure whether this means that the old P-code was better than we thought or that the native code compiler needs some more tweaking to provide optimized output. We also see that Visual C++ is quite a bit faster in all categories. Delphi is the real speed demon for math and string operations, but it falls slightly behind for graphics. The results for Visual J++ are difficult to understand. Compared with Visual Basic 5, Visual J++ is slightly faster for math, significantly slower for graphics and abysmally slow for string operations.

What can we make of these results? To be quite honest, I am a bit disappointed with the string and math results for Visual Basic 5. I had hoped that the move to native code compilation would provide a greater speed boost over the P-code of version 4, and that it would bring Visual Basic's performance more in line with Visual C++ and Delphi. On the bright side, the graphics performance has improved a great deal, and I think that fast display of graphics is a more important factor in a program's perceived speed than are rapid math and string manipulations. After all, how much time does a program actually spend crunching numbers or stings? Except for very specialized applications, not much. Besides you wouldn't choose a development tool based solely on benchmark results any more than you would choose a car based on top speed comparisons.


Mobile Site | Full Site
Copyright 2017 © QuinStreet Inc. All Rights Reserved