Home
Archives
About us...
Advertising
Contacts
Site Map
 

ruby in steel

 

CLASSIC VB : GRAPHICS #1

In this new series, Dermot Hogan looks at how Windows graphics have evolved from Windows 1.0 right through to Avalon.…
Requirements:
VB6

 

Download The Source Code:
vbgfx1src.zip

 

See also: part two of this series

You can do far more with ‘Classic VB’ than just display forms with buttons on them. In this series I’ll look at using the Graphics Device Interface or GDI to do some simple drawing operations. This uses the 'application programming interfaces' or APIs to do a lot of the work.

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

Yes, Sir, I Can Doodle…

The standard graphics available in Visual Basic 6 are very simple and limited in scope. You can draw lines, circles, boxes and fill them with colours and patterns. But you might consider it to be somewhat ambitious to build a CAD system using the available Visual Basic primitives. What Microsoft provides in this respect is really not up to the job. However, you can go behind the scenes and use more advanced Windows GDI techniques to get better performance and more sophisticated graphics.

The Graphics Device Interface (GDI) is the part of Windows that is responsible for the drawing of pictures and text on your screen. It extends a bit further than that in practice – it’s also used for dealing with printing, faxes and so on.

The problem with the GDI is that it’s old. Parts of it go back to Windows 1.0 and it’s been modified, tweaked and re-written countless times since. Frankly, it shows. The general design of the GDI API is, well, eccentric. For the next couple of months, I’m going to explore the highways and byways of the GDI and then move on to look at how it’s done in .NET. Interestingly, you’ll find that the .NET GDI isn’t that different from the original GDI. Finally, I’ll look at how you do the same things in the new Windows Vista system, WinFX or Avalon (the Windows Presentation Foundation).

Don’t be put off by the fact that this is using Visual Basic 6, by the way. If you’ve never used the Windows API or figured out why Windows works the way it does, then you’ll still find the explanations and the underlying principles also apply to VB .NET and beyond. Additionally, Visual Basic 6 is still a lot easier to follow, especially if you’re not used to object orientation. You may find this series to be a good introduction to object oriented programming as well, since you’ll be able to compare the old style non-object oriented way and the newer .NET style.

The GDI

At its core, the GDI divides into two worlds: the ‘logical’ and the ‘physical’. A program writes to the logical world where the monitor dimensions are very large, colours are perfect and almost anything can be done. The GDI then translates this perfection into grubby reality of 800x600 (say) pixels on an LCD. The logical GDI has a set of objects which don’t exist on physical devices – for example, a ‘brush’, which is used to fill a background with a colour or pattern. The GDI then generates the necessary instructions for filling a rectangle with a colour for the device driver – the interface to the graphics card - to make it happen.

When you talk to the GDI, you must specify a ‘device context’. This is a sort of scratchpad of interesting things that the GDI needs to know about whenever it does something. For example, when you draw a line, you need to specify not only its starting and ending locations, but the colour, the thickness and possibly the style – dashed or solid. To save having to specify these basic properties every time you want to draw a line, you specify these in the device context (‘select into’ in the GDI jargon) and just pass the device context to the API call.

The GDI drawing functions really form four basic classes: lines, filled areas, bitmaps and text, though the last one probably exceeds all the other three combined in terms of complexity. We’ll start with a humble line.

From A to B  … and back again

You’d think that drawing a line from one point to another is just about the easiest thing in the world … sort of like falling off the GDI log. It’s not quite that easy though. There’s the small matter of Visual Basic that gets in the way. We’ll start off simply with a form and a single ‘Test’ button on it (Download Source Code). In the form’s load method, we’ll create a new ‘pen’ and tell Windows to use it:

Private Sub Form_Load()
redPen = CreatePen(PS_SOLID, 5, vbRed)
DeleteObject SelectObject(hdc, redPen)
End Sub

The CreatePen API makes a new ‘pen’. This is what you probably think it is – something that draws a line in a certain style and colour. Here, it’s solid, 5 pixels wide and a nice red colour. You can also set it to various combinations of dots and dashes and even make it invisible. The new pen is then ‘selected into’ the device context – that is, it replaces the existing pen in the device context. This is done by the SelectObject API which takes as arguments the device context of the form, hdc, and the newly created pen. A handle to the old pen is returned by SelectObject. It’s considered good programming practice to delete the old pen to avoid memory leaks, but for us that’s not very significant. Still, that’s why DeleteObject is called here.

Now we’ve got a red pen, so let’s draw something with it:

Sub Draw()
MoveToEx hdc, 0, 0, 0
LineTo hdc, 200, 200
End Sub

The first line of this function uses the MoveToEx API to move the ‘point’ of the pen to somewhere definite – here the form’s co-ordinates (0,0). Next, the LineTo API draws a line from the current pen point (0,0) to a new pen point (200, 200). All pretty easy so far, so let’s test it:

Private Sub Test_Click()
Draw
End Sub

You should get a thick red diagonal line running part way across the form. But now we hit a problem. If you minimise the form and then expand it to full size again – the line has vanished!


Getting Visual Basic to draw the graphics is not trouble free. There is clearly a bug when the form is repainted which you have to work around.

There are two ways to get 'home grown' graphics to display in Visual Basic. One is to use the Paint method, but it’s simpler to set the AutoRedraw property.

If you click Test again, the line re-appears. It turns out that you get the same behaviour if you overlap the line with another window. Clearly, Visual Basic is not redrawing the form correctly. More precisely, it is redrawing the form because the Test button is redrawn, but it’s not redrawing the line.

The obvious solution (well, sort of … after a good bit of poking around) is to set the Form’s AutoRedraw property to True. This should make the form redisplay any graphics when it is moved or resized. If you set this, and then press ‘Test’ you’ll get … nothing! But now try minimising the form then restoring it. The line will magically appear. It turns out that this is nothing more than a good old-fashioned bug and it is indeed documented as such in the MSDN library. Unfortunately, it’s only documented as being a problem for a bit-mapping operation, BitBlt. This is not a great deal of use when you are searching for problems with LineTo. It took me some considerable time to track it down.

The solution suggested by Microsoft does work – just put in a call to Refresh after the call to Draw. However, by the time I’d discovered that, I was well on the road to plan B. It’s this: if the AutoRedraw property is set to False, you must handle the re-painting required yourself. There are two areas where this is necessary, in the Paint method which handles overlaps and maximize/minimize operations and in the Resize method. So all we need to do is put a call to Draw in these two methods. This also works.

Square Bashing

Now we’ll move onto more interesting structures than the humble line -  squares for instance. You can draw a square in a number of different ways. The first is just to draw four lines that happen to join up. In the example program, there are four buttons A, B, C and D which, if you click them in order, will produce a square. However, while these may look to you like a square (and to a mathematical topologist they are a square) to Windows they are merely four lines. They do not have an ‘interior’. This means that you can’t ‘fill’ the square with a colour, pattern or anything else.


Drawing a rectangle with four lines isn’t the same as creating a 'filled shape'. Four lines (even if they seem to be connected) do not have an interior and so can’t be filled

To do this, you have to draw one of Windows’ ‘filled shapes’ – a rectangle, ellipse or polygon. You fill rectangles and on so on with a ‘brush’. A brush is a GDI object that really does work a bit like a paintbrush. At its simplest, you specify a colour and possibly a pattern and Windows will then ‘paint’ the interior of a filled shape with the pattern and colour.

We’ll just use a simple one – a rectangle. In the Rectangle button’s click code there is just the call:

RectangleX hdc, 50, 50, 250, 250
Refresh


You can paint the interior of a filled shape with either a solid colour or a pattern. Several standard patterns are supplied by default.

I’ve had to call the API RectangleX using the Alias keyword in the API’s Declare statement to avoid a conflict with the Rectangle Button (I could have called the button something else, of course, but that would be doing things the easy way). The RectangleX API call creates a Windows rectangle of size 200 at a starting position of (50,50). Now if you just have that code you won’t get something very different from the four simple lines. This is because Windows is filling the interior of the rectangle with the current ‘brush’ which is exactly the same as the grey of the form.

So we have to create a new brush with a different colour in the Form’s Load method:

blueBrush = CreateSolidBrush(vbBlue)
DeleteObject SelectObject(hdc, blueBrush)

Now if you click Rectangle,  you’ll get a blue colour filling the interior. You can try variations on brushes by calling CreateHatchBrush which creates various simple brush patterns, ‘hatches’. For example, try clicking the HatchBrush button followed by the Rectangle Button.


Another way of doing things - PostScript

There is a world elsewhere...

The Microsoft GDI isn’t the only way to approach the problem of drawing ‘high level’ concepts such as circles onto a ‘low level’ device like a printer. Adobe designed PostScript in 1984 (or thereabouts) as a means of enabling computer printers to produce high quality phototypesetting output. PostScript was adopted by Steve Jobs in the Apple Mac in 1985 and became part of the Mac legend – the ability to produce really good desktop publishing from a (relatively) cheap personal computer.


Quartz sits below the Mac OS X 'Aqua' interface. Quartz uses PDF as its graphics language which is arguably a better way of doing things than the Windows GDI.

PostScript has been through several generations now, but arguably the most significant innovation was the design of Display PostScript used in Steve Jobs’ NeXT computer. The idea here was that instead of using API calls to draw a circle, or whatever, on a screen, the program would generate PostScript commands that would be interpreted directly by a graphics ‘engine’. There were two advantages to this. First, give or take small differences in the PostScript engine, what you saw on the screen really was what you got when it was printed. And secondly, it was an elegant piece of engineering.

Unfortunately, NeXT did not thrive. But Steve Jobs did. Via a long and tortuous route, he took over at Apple and managed to keep it off the rocks. He and his team have managed to put the company on a stable and prosperous footing – a feat which had defeated the previous managements. But times have moved on and PostScript is not the state-of-the-art graphics processing language that it once was. Instead, Adobe’s Portable Document Format (PDF) rules the roost. But PDF is a superset of PostScript, optimised and enhanced. Instead of Display PostScript, OS X now uses ‘Quartz’ – the equivalent to Windows GDI. And guess what? Quartz uses PDF as its graphics language. In many respects, the Mac is superior to Windows XP which still uses the GDI. Even in .NET the GDI is still there – hidden, it’s true, by a good class interface. But elegant, it still isn’t.

Paths To Glory

Paths are another way of defining shapes...

Simple shapes like circles, ellipses and rectangles are fine for objects like windows, textboxes and the like. But for really complicated drawings or shapes, it is better to use a ‘path’.


Here’s the difference between a ‘stroked’ path and two simple lines. You can see that the ends of the two simple lines are not joined smoothly whereas the stroked lines are.

GDI paths are collections of lines, not necessarily connected, which the GDI handles as a single entity. You start a path using BeginPath and then just  add lines as you would normally, ending with EndPath. But there are two differences. First, the sequence:

BeginPath hdc
MoveToEx hdc, 10, 10, 0
LineTo hdc, 10, 210
LineTo hdc, 210, 210
LineTo hdc, 210, 10
LineTo hdc, 10, 10
EndPath hdc

...doesn’t display anything. To display the path you have to issue the StrokePath command. The second difference lies in the fact that the lines here are connected; and that has an effect when you specify the types of line ending and mitre joins that each line has where it connects to the next line in the sequence.

You can specify the type of join and mitre by using the ExtCreatePen API which we’ll look at next month.

Next Month...

Next month, we’ll look at further GDI programming  with bit-blitting and other tricks.

 

February 2006

 


Home | Archives | Contacts

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