Home
Archives
About us...
Advertising
Contacts
Site Map
 

ruby in steel

 

visual basic CLASSIC VB : GRAPHICS #3

You can do far more with Visual Basic 6 (aka ‘Classic VB’) than just display simple forms with buttons on them. …
Requirements:
VB6 ; Windows Millennium/Windows 2000/Windows XP

 

Download The Source Code:
vbgfx3src.zip

In the final part of this series on VB graphics, Dermot Hogan explains how to sublass the WM_PAINT message and 'bitblt'...

See also: part one and part two of this series

Take Full Control Over Windows Graphics

Visual Basic 6 (aka 'Classic VB') provides a good comprehensive framework for writing programs. Using simple Visual Basic you can write quite complicated applications quickly. Sometimes you may require a finer control over what the program is doing and in several places Visual Basic provides you with these facilities. But you can go further and take complete control if you wish.

Classic VB is the name given to the Visual Basic product line culminating in Visual Basic 6 by  the Classic VB campaigning group.

When working with the GDI, the combination of standard Visual Basic and the extensions that are provided is something of an unsatisfactory compromise. The whole point of working with the GDI is to get the last ounce of detailed control that Windows permits. You may wish to handle the background repaint event, say, to either suppress it or do something different from the standard. The tools that Visual Basic provides do not permit you to do this but, as is common in software, there is a get-out clause: you can intercept the fundamental Windows messages that instruct Visual Basic to do things. This technique is called ‘subclassing’.

Beyond The Event Horizon

The basic operation of Windows involves messages which are generated by events. An event might be a mouse click, a key press or mouse move. Roughly speaking, event messages are directed to an application by Windows and, unless you pick them up yourself, are processed by a window’s default ‘window procedure’. You can substitute your own window procedure by using an API call, SetWindowLong (the origin of the API name goes back into Window’s pre-history) like this:

oldproc = SetWindowLong(h, GWL_WNDPROC, AddressOf MyWndProc)

Here, h is the window’s handle and MyWndProc is the code that will be executed by Windows when a message arrives for that window. The old – here, the default – window procedure is returned by SetWindowLong. We need to hang onto this in order to reset the window to normal message handling, which is done quite simply like this:

SetWindowLong h, GWL_WNDPROC, oldproc

The message handling procedure, MyWndProc, always has the same arguments:

Private Function MyWndProc(ByVal hwnd As Long, _
ByVal iMsg As Long, _
ByVal wParam As Long, _
ByVal lParam As Long) As Long

The first argument is the handle of the window that the message is destined for. You can use this to get the device context for painting and other graphics operations. The second parameter is the message number, say, WM_PAINT. The last two parameters, wParam and lParam just give information about the message. What they convey and the format in which they convey it varies a lot from message to message.
If you decide not to handle the message (which will be the case for most messages) you must call the default window procedure and return the value returned by that:

MyWndProc = CallWindowProc(oldproc, hwnd, iMsg, wParam, lParam)

In general, working with a window procedure is fairly easy if you don’t try to be too ambitious. If you try to be clever, you’ll find that it isn’t too easy to find out what is happening in a window procedure using Visual Basic. The standard way of setting a breakpoint and using the debugger doesn’t work because both your program and the Visual Basic debug system will be fighting over who gets to process the messages for the window in question. Incidentally, that’s how the Visual Basic debugger works – it intercepts messages for your program by subclassing and hooking inbound messages. The trick when you do your own subclassing is to use the Debug.Print method and look at the resulting output in the Immediate window.

The Window Procedure

When a message is routed to your subclassed window by the Windows operating system, the window procedure that you have set up is called. The standard way of handling messages is to use If Then Else or a Select Case statement. So the code looks something like this:

Select Case iMsg
Case WM_PAINT
Case WM_ERASEBKGND
End Select

You will need a Case clause for each message you want to handle. The rules for handling messages in a subclassed window are simple, but they must be followed to the letter or you’ll end up with some rather unexpected results. The two basic rules are:

  • if you handle a message, then the procedure must return zero, and…
  • if you don’t handle a message, then you must call the old window procedure:

Case Else
    MyWndProc = CallWindowProc(oldproc, hwnd, iMsg, wParam, lParam)
End Select

We’ll start by looking at the WM_PAINT message. This is sent to the window in question whenever Windows has determined that something needs to be drawn or repainted. For example, if you click the button Start subclassing in the sample program, you won’t see any messages in Visual Basic’s docked Immediate window as you move the main form around a little. But as soon as you resize the window – or minimise and maximise the window, you’ll see a whole raft of them appear in the Immediate window.

Processing the WM_PAINT message has its own set of rules. First, you must call the BeginPaint API. This returns the device context and additionally sets up a PAINTSTRUCT structure containing information about the painting operation, should you need to use it. Here, I’ve just printed out the data in it. Next, you draw the picture – I’ve used DrawBox as the drawing routine. This just draws a rectangle with a thick border. Following that, you call EndPaint (it’s not clear why this last call to EndPaint is necessary; it’s probably historic). Finally, you must set the return value to zero:

hdc = BeginPaint(hwnd, ps)
Drawx hdc
EndPaint hwnd, ps
MyWndProc = 0

vb graphics programming
This is what happens if you don’t handle the WM_ERASEBKGND message.
Note the Immediate window docked on the right for debugging purposes.

If you just write code to handle the WM_PAINT message, you won’t get the result you might have expected (see the screenshot above). As well has handling the WM_PAINT message, you must also redraw any background in the window. Otherwise, you’ll get what was there before – sort of. Fortunately, this is easy – just handle the WM_ERASEBKGND message:

GetClientRect hwnd, rc
brush = CreateSolidBrush(vbGreen)
FillRect wParam, rc, brush
MyWndProc = 0

This paints the background a hideous lime-green colour – but at least you can see that it’s doing something! Essentially, the trick is to get the dimensions of the portion of the window to be repainted using the GetClientRect API and then create a brush of a suitable colour. Normally, this is the boring Windows grey, but you can choose any colour you like. Next, just ‘fill’ the rectangle with the brush and you’ll get rid of the random background.

That’s nearly it, but there’s one final wrinkle – you must paint the background as you start to subclass. Otherwise, only the part of the background that Windows thinks needs repainting will be done. The reason for this is that Windows tries to minimise the amount of work that needs to be done by working out what parts of the window have been overlaid. So the client rectangle area obtained in the WM_ERASEBKGND message  just contains what is ‘invalid’ and if you haven’t painted the background (or invalidated the area which is another way), Windows won’t know this.

visual basic graphic programming screenshot
Even when you do handle the background repaint messages correctly, you will find that Windows only wants you to repaint the part of the window that it thinks needs to be repainted.

Blitzing The Bits

Now to ‘bitblts’. A bitblt (usually pronounced ‘bit-blit’) is short for “bit block transfer” – a technique for copying pixels around on the screen. A bitblt is a very common operation in graphics programming. The reason that it’s given a special name is that the alternative – two nested for loops doing a pixel by pixel copy - is way too slow. A bitblt is normally done in the hardware. Graphics adapters have long had the ability to zap blocks of pixels around and do clever things with them like overlaying them on the existing background so that just the bits you want are visible.

Visual Basic bitblt
This is an example of a basic bitblt. The rectangle and its border is copied to the same window lower down. Note that this affects only the current display - if you resize the window, the copy will be lost.

A bitblt is easy to do too – here’s a simple example (just press the BitBlt button on the example program):

r = BitBlt(hdc, 400, 400, 200, 100, hdc, 0, 0, SRCCOPY)

This simply copies the block of pixels of width 200 and height 100 starting at (0,0) into a location at the same window at the destination location (400, 400). You can also play around with various modifications other than a straight copy.

However, bit-blitting is very handy is you want to do screen capture. This is covered in more detail in the section on ‘Capturing the Screen’ later on.

vb graphics programming
Here’s the complete result. In order to get this you must repaint the entire backround as you want it before subclassing the window.

Basic Bitmaps

Bitmaps are the most fundamental objects that are manipulated by the GDI

At some point most graphics operations come down to generating, tweaking and then outputting a bitmap. The term 'bitmap' is misleading. There’s not much mapping involved and you certainly need more than one bit to represent an element of a bitmap these days. Very early on in the evolution of personal computers (and probably before), the monitor screen was monochrome and one bit could indeed represent one pixel efficiently given the very limited memory then available. These days, 24 or 32 bits per pixel is the norm.

Bitmaps in Windows can also be a good bit (no pun intended) more complicated than a simple representation of the colour to be displayed at a pixel location. There are two types of bitmap – device independent bitmaps (DIBs) and device dependent bitmaps (DDBs). A DIB represents pixels not directly as colours but as indexes into a colour table. A DDB contains colour information but that colour information is not sufficient by itself to uniquely determine the colour specified. Instead, colour values reference the device’s colour ‘palette’.

DIB graphics Visual Basic illustration
A device independent bitmap contains two main structures.
Each element in the pixel array is a value in the colour table.
This then specifies the actual colour used to the monitor.

Capturing The Screen

You can use bit-block transfer to copy the whole screen into another bitmap.

Bit-blitting is used when you want to capture a screen. Essentially, you want to copy the entire screen bitmap, all 1024x768 pixels say, to somewhere else. This turns out to be reasonably easy. The first thing to is to get  the screen’s device context, using the CreateDC API:

shdc = CreateDC("DISPLAY", 0, 0, 0)

Next, you create a ‘clone’ DC, compatible with the screen DC:

chdc = CreateCompatibleDC(shdc)

Now create a bitmap of the right size and select it into the cloned DC:

hbm = CreateCompatibleBitmap(shdc, 1024, 678)
r = SelectObject(chdc, hbm)

We’re nearly ready to go now, but first we need to hide the application’s window – unless you want to copy that as well. This is done by the ShowWindow API:

ShowWindow hwnd, SW_HIDE

Now we can bitblit the screen bitmap from the screen device context to our clone:

r = BitBlt(chdc, 0, 0, 1024, 768, shdc, 0, 0, SRCCOPY)

Having got the screen bit map, we can restore the application’s window:

ShowWindow hwnd, SW_SHOW

Just to show that we’ve done something, we can bitblit from the cloned DC into the application window:

r = BitBlt(hdc, 0, 0, 1024, 768, chdc, 0, 0, SRCCOPY)

That’s more or less all there is to it, though copying the bitmap to disk or tweaking areas of the copied bitmap requires a few more API calls.

Screen Capture with Visual Basic
This might look like the start of a hall-of-mirrors, but it is a screenshot of a screenshot of an application. 
This illustrates the use of bitblitting in screen capture.

 

April 2006

 


Home | Archives | Contacts

Copyright © 2006 Dark Neon Ltd. :: not to be reproduced without permission