restoring raster graphics and rubberbands to .net
DESCRIPTION
Described the ins and outs of GDI32 Raster Graphics under .NET. It also demonstrates how to perform rubberband selection of objects on a form. Additional demonstrations show how to use GDI+ to draw selection rectangles. The first GDI+ example demonstrates the DrawReverseRectangle() method, and explains its shortcomings. It also demonstrates how to cleaning draw a GDI+ selection rectangle with a translucent background, as is standard on Vista and Windows 7.TRANSCRIPT
Restoring Raster Graphics and Rubber Bands to .NET
By David Ross Goben Copyright © 2011 by David Ross Goben All rights reserved.
Last Update: Wednesday, June 08, 2011
This is a sample excerpt from the free PDF e-book, Enhancing Visual Basic .NET Far Beyond the Scope of Visual Basic 6.0, by David Ross Goben. Download this e-book, and its free companion, Navigating Your Way Through Visual Basic 6.0 Upgrades to Visual Basic .NET, also by David Ross Goben, at www.slideshare.net/davidrossgoben. They are also available on Google Docs at https://docs.google.com/leaf?id=0B_Dj_dKazINlN2JlY2EwMmEtNGUyMy00NzQzLTliN2QtMDhlZTc5NDUzY2E5&sort=name&layout=list&num=50. The Google site also contains the source code in a file named Restore Raster Graphics and Rubberbands Source.zip.
Table of Contents
Restoring Raster Graphics and Rubber Bands to .NET................................................................................223 Getting an Image Graphics Object Without Going Through the Paint() Event.........................................................224 Restoring Raster Operations to .NET ......................................................................................................................224 Getting One’s Hands Dirty................................................................................................................................... 225 The GDI32 Class ................................................................................................................................................ 227 Emulating a Selection Rubber Band under GDI.......................................................................................................233 Emulating a Selection Rubber Band under GDI+.....................................................................................................234 Using the GDI+ DrawReversibleFrame() Method.................................................................................................. 234 Using the GDI+ DrawRectangle() Method and a Translucent Brush ...................................................................... 235 About the Author ............................................................................................................................................237
Page –223–
Restoring Raster Graphics and Rubber Bands to .NET You may be quick to notice that VB.NET does not support general drawing commands as a part of its
intrinsic language repertoire, as had been the case under VB6. I say thank goodness! I still cannot believe
that the very primitive DOS-BASIC-style drawing commands had managed to make their way all the way
into VB6, and especially as a part of the fundamental language, itself. I had been very fearful that the broad
and largely non-professional, albeit fiercely devoted user-base would force VB.NET to end up suffering a
similar fate during its development cycle. Just look at the damage hoards of die-hard fans had forced on
bitwise and logical operations, changing them from innovative and more clearly defined to just archaic and
so-so (“Mongo fears change,” to quote Mongo from Mel Brook’s classic comedy, Blazing Saddles).
Under .NET, any specialized features, such as drawing, are grouped under a separate namespace. This is a
very good thing. By placing them as members in a separate static class, we can use the methods in that class
to draw on graphical objects, or, later, when more advanced graphical capabilities are added to .NET, we can
instead use the members of the namespace devoted to that paradigm. In the meantime we are not cluttering
up the language, as was sadly done to VB6. For example, suppose someone had developed a class supporting
OpenGL or Direct3D drawing that often used “standard” drawing commands. If we forget to specify or
import their namespace, we would find ourselves drawing using standard features instead of enhanced
features, and we would then have to track down the bugs by finding where we forgot to reference our
graphical class. By keeping all these things in different namespaces, we have simplified the language and
allowed for easy expansion of its capabilities, and there is also less likelihood for method name collisions.
However, as easy and better as all this is, drawing is accordingly different under VB.NET than under VB6.
Granted, some stuff is easy, like changing the VB6 Cls command to Clear under VB.NET.
Others are easy to adapt to, such as using the System.Drawing.Graphics object to draw shapes, lines, arcs, circles, etc. to a PictureBox. Indeed, these are now much easier to use, and are also a lot faster.
Some others do not exist any more, because it is much easier to provide and maintain these features within
your own code; they no longer eat up valuable resource space if you do not use them, which is the case in
most situations. Cases in point are the CurrentX and CurrentY last drawing location properties. These
features are easy enough to compute and update locally without eating control resources to store them for every object, when only a few of them would actually need to make use of them.
Others are not obvious. For instance, the VB6 Point command, now GetPixel(), to get a point on an
image, must be accessed through a Bitmap object, which, logically, is where such a command should be.
And regarding Bitmap objects – most examples tell you to create a new Bitmap object and fill it will the
image from the PictureBox, but this is a waste of time and resources if we just need to access the image or
quickly draw to it. Indeed, the only time we should really need to do something like that is if we will be
making numerous changes to the image, but we want to update the actual displayed image only once, to
eliminate flicker. Otherwise, instead of chewing up resources and valuable time doing something like this:
Dim Bmp As New Bitmap(Me.PictureBox1.Image) 'instantiate a new copy of the image as a bitmap (useful for numerous changes)
We can instead simply do the following (note that a Bitmap and an Image are identical in structure):
Dim Bmp As Bitmap = DirectCast(Me.PictureBox1.Image, Bitmap) 'define a reference to the image without instantiating a new image
Certainly, the only times we would need to create a separate bitmap is when, as previously stated, we need to
make numerous changes, or if the image object might be set to Nothing, which a blank PictureBox or a form
with no background image may possess. In that case, we can declare them like this:
Dim Bmp As New Bitmap(Me.Width, Me.Height, Me.CreateGraphics) 'create blank Form bitmap 'or... Dim Bmp As New Bitmap(Me.PictureBox1.Width, Me.PictureBox1.Height, Me.PictureBox1.CreateGraphics) 'create blank PictureBox1 bitmap
Regardless of how we define it, we can then get the color at a specific point using a command like this:
Dim Clr As Color = Bmp.GetPixel(intX, intY) 'Get an ARGB color value at a specified X,Y point (duplicates the VB6 Point() method)
Page –224–
To set a single point, we are very often advised to draw a circle with a radius of 1, like this:
e.Graphics.DrawEllipse(Pens.Black, 100, 100, 1, 1) 'using Elimpse to draw a point at 100x, 100y with width and height of 1
NOTE: Rather than using e.Graphics, as shown here, which is provided by a Paint event, we can instead easily create our
own graphical interface to the object, as will be demonstrated shortly, and use it outside of Paint events.
Though slothful, it is not as wasteful as it might appear, because .NET will see the width and height are
set to 1, and so it will just draw a dot. Even so, the SetPixel() P/Invoke under gdiplus.dll is much
faster. And because of this, I find the above ellipse advice odd, because VB.NET does in fact support a
much faster SetPixel() method under the Bitmap object, complementing its GetPixel() method,
which does in fact provide an interface to the fore-mentioned SetPixel() P/Invoke, as shown here:
Bmp.SetPixel(100, 100, Color.Black) 'set a single point on the image (duplicates the VB6 graphical Set command)
Getting an Image Graphics Object Without Going Through the Paint() Event The main problem many new users to VB.NET run into is when they try to draw shapes at will to something
like a PictureBox. It may be easy to draw within its Paint() event (as demonstrated earlier in this document
– see the article, Easy Ways to Draw Lines and Shapes, and to Paint in VB.NET, on page 143, for instance),
where the PaintEventArgs “e” parameter provides us with access to a convenient Graphics object that is pre-
tied to the PictureBox. However, they seem to get a bit stuck when they want to simply draw a unique line,
shape, or text to the PictureBox, such as a drawing program might use. In fact, I have seen code where, out of
frustration, people have placed specialized hooks into a Paint() event that passes back the event argument
object, or more often, saving it off somewhere, implementing it in a customized method so that they could
apply those non-typical drawing features. But this leads only to exception errors.
But accessing this graphical interface is really not complicated. We need to understand is that the
graphical interface provided by the Paint() event is just a simple System.Drawing.Graphics object that
is tied to the PictureBox, and this is something that is quite easy to generate within our code.
For example, suppose we want to draw to PictureBox1. To access a graphical interface to it using an
object named eg (reminding us of e.graphics), we could use code like this:
'generate a graphical interface to a picture box image (same as e.Graphics provided by a System.Windows.Forms.PaintEventArgs object)
Dim eg As System.Drawing.Graphics = Me.PictureBox1.CreateGraphics
You may also have seen such objects defined like the following, which is also valid:
Dim egf As System.Drawing.Graphics = System.Drawing.Graphics.FromHwnd(Me.PictureBox1.Handle) 'a graphical interface to a picture box.
Dim egi As System.Drawing.Graphics = System.Drawing.Graphics.FromImage(Me.PictureBox1.Image) 'useful for drawing text or copying images.
NOTE: If you are drawing shapes, use the egf definition for the control, rather than egi for the image on the control;
otherwise the drawn shapes may not display. The first alters the displayed image, the second alters its stored image.
Suppose we want to draw a simple white cross through an image when the user clicks on it. Try this:
'react to user clicking on picture Private Sub PictureBox1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles PictureBox1.Click
Try
Dim eg As System.Drawing.Graphics = Me.PictureBox1.CreateGraphics 'generate a graphical interface to a picture box image
With Me.PictureBox1
eg.DrawLine(Pens.White, 0, 0, .Width, .Height) 'draw a white line from top-left to bottom-right
eg.DrawLine(Pens.White, 0, .Height, .Width, 0) 'draw a white line from bottom-left to top-right
End With Catch 'ignore errors (usually happens when no image data is loaded in a PictureBox, so the Image object is set to Nothing)
End Try
End Sub
Restoring Raster Operations to .NET Other graphical features seem to be lost in limbo, such as the VB6 DrawMode property, which allowed
you to specify how a pen object was to be drawn upon the background. It could be normal
(R2_CopyPen), R2_XOrPen, R2_NotXOrPen, R2_MergePen, R2_MergePenNot, or whatever raster
drawing operation you would want to perform. The reason for this is that Raster operations are not
supported by GDI+ under .NET (regardless of the fact that these are in fact simple processes).
Page –225–
GDI+, introduced in 2001 with WinXP, is faster and more powerful than standard GDI, and this is
because GDI+ is linked directly to the operations available to graphics card though its firmware
(software burned into a board-based read-only memory chip (ROM)). It therefore naturally lacks support
for Raster Operations (memory-mapped operations) because the card’s firmware lacks it.
What this means to most people is that the raster-generated selection rectangle that is used so often in
Windows is not supported by GDI+ under .NET. Indeed, GDI+ was primarily meant to be used by
C/C++ developers, but it was quite naturally incorporated into .NET.
For example, under GDI+, the System.Drawing.Graphics.CompositingMode property, which is the
closest thing it has to the GDI Raster Operations property, has only 2 enumerated settings:
Member name Description
SourceOver Specifies that when a color is rendered, it is blended with the background color. The blend is determined by the alpha component of the color being rendered. GDI has no similar command.
SourceCopy Specifies that when a color is rendered, it overwrites the background color (Default). This is just like the Raster R2_CopyPen
command.
Conversely, GDI Raster operations implemented 17 settings, as defined below:
Member name Description
R2_Black Specifies black pen color. R2_CopyPen Specifies the pen color. The pen over-writes anything it draws over (Default).
R2_MakePenNot Specifies a combination of the colors are common to both the pen and the inverse of the display. R2_MaskNotPen Specifies a combination of the colors are common to the background color and the inverse of the pen.
R2_MaskPen Specifies a combination of the colors common to both the pen and the display.
R2_MergeNotPen Specifies a combination of the display color and the inverse of the pen color. R2_MergePen Specifies a combination of the pen color and the display color.
R2_MergePenNot Specifies a combination of the pen color and the inverse of the display color.
R2_NoOperation Specifies no operation; the output remains unchanged. R2_Not Specifies the inverse of the display color.
R2_NotCopyPen Specifies the inverse of R2_CopyPen. R2_NotMaskPen Specifies the inverse of R2_MaskPen.
R2_NotMergePen Specifies the inverse of R2_MergePen.
R2_NotXOrPen Specifies an inverse of R2_XOrPen. R2_White Specifies a white pen color.
R2_XOrPen Specifies a combination of the colors in the pen and in the display color, but not in both.
As you can see, even with all this functionality, there is no alpha-channel-blending support provided by
GDI, such as the GDI+ SourceOver setting specified. The other GDI+ setting, SourceCopy, is duplicated
by the GDI R2_CopyPen setting. No other correlations between these two platforms are yet supported.
Getting One’s Hands Dirty So, if you want to implement GDI Raster operations under .NET, you need to get your hands dirty and
perform GDI operations in much the same way as they are done under C/C++. This involves obtaining the
device context (DC) of the object we want to draw to, creating pens and brushes to draw with, select them
into the DC, perform the drawing operation, and finally release these created resources.
Of course, even though this can seem to get a bit involved, it all boils down to a uniform series of simple,
repeated processes that can be performed for you in an automated way, so you can focus entirely on the
actual drawing tasks. What this would actually involve are two support methods that can be invoked “behind
the curtain”, out of sight and mind; the first will automatically set up for a drawing task you want to perform,
and the second will automatically close down from it after your drawing task.
The first method, which we will name InitPenAndBrush(), will get the Graphical interface and the Device
context of the target object, create the foreground drawing pen with a selected color and width, create the
background brush (the fill) with its color and pattern, select each of them into the object we want to draw on,
save the pen and brush that were previously assigned, and select the type of Raster Operation we need.
Next, just like we would do under GDI+, we perform the actual drawing task, such as drawing a line.
After we perform the drawing task, the other method is invoked, which we will name
DisposeResources(), that will select back in the previous pen and brush to the target, and finally
release the resources of our new pen, brush, graphical interface, and device context.
Page –226–
Of course, to make this even easier, we can create a class that performs all this added work for each of
the drawing functions for us, so all we have to do is concern ourselves with invoking its public methods
and properties; we will simply define the pen we want to draw with, as needed, the background brush, if
we need it, the type of Raster Operation, and, naturally, to invoke any required drawing operations.
The GDI32 class, listed on the next page, is derived from various C++/C# sources, and also from a lot of
my own current and previous work (actually, one of the anonymous C++/C# postings in fact credited a
sadly unremembered VB.NET author who had designed an ordered structure to the class and defined its
naming conventions – if anyone recognizes it; please let me know). Note that any additional features you
need can also be added quite easily.
Once you invoke an instance of this class, you can set the drawing pen’s color and width, the brush’s
color and hatch style (HS_SOLID is the default), if you need to change them (they are saved within the
class properties, so you do not need to set them with every drawing command), and invoke any of the
required drawing operations: SetPoint(), GetPoint(), DrawLine(), DrawCircle(), DrawEllipse(),
DrawArc(), DrawRectangle(), DrawRoundRect(), and DrawObround().
To implement this class into your own code is very simple. Somewhere within your code, perhaps as
publicly as possible, you would declare an instance of the GDI32 class:
Friend m_GDI As New GDI32 'instantiate an instace of the GDI32 class
Then, when you are ready to draw, be sure your Pen color (foreground), Brush color (background), and
the Raster operation are as you need them to be. By default, the Pen color is White, the Pen style is
PS_Solid, the Brush color is Transparent, the Brush hatch pattern is HS_Solid (no pattern), and the
Raster operation is R2_CopyPen. This will draw using a solid white pen with an invisible brush (no
background color for the pen will be painted over the target surface; typical for ellipses and rectangles).
NOTE: If the Pen color is Transparent, then the drawing operation will use a stock Null Pen (invisible). If the Brush color
is Transparent, then the drawing operation will use a stock Null Brush.
You set the properties for the drawing operation by change the properties of the class. For example:
m_GDI.PenColor = Color.LightBlue 'set pen color to Light Blue (default is Color.White)
m_GDI.PenStyle = PenStyles.PS_DASH 'alternate dashes with dots (default is PenStyles.PS_SOLID)
m_GDI.PenWidth = 3 'Set the pen width to 3 pixels (Default is 1)
m_GDI.BrushHatch = HatchStyle.HS_CROSS 'set the brush style to Crosses (default is HatchStyle.HS_SOLID)
m_GDI.BrushColor = Color.Blue 'set the brush color to Blue (default is Color.Transparent)
m_GDI.RasterOp = RasterOps.R2_XOrPen 'merge the pen and background colors (default is RasterOps.R2_CopyPen)
NOTE: GDI requires RGB colors, not the ARGB colors that are part of the standard Color Palette and GDI+. As such, we
must convert them from ARGB to RGB. Most gurus will tell you to simply perform a Clr.ToArgb And &HFFFFFF operation,
but this is not enough. ARGB is stored internally as AARRGGBB, for Alpha, Red, Green, and Blue, where each letter
represents a Hexadecimal digit. However, RGB is stored as 00BBGGRR. As such, the Red and Blue color values must also
be swapped. I do this for you automatically within the class using the ARGBtoRGB() function, defined below:
'*************************************************************************************************************
' Function: ARGBtoRGB ' Helper function to covert Alpha Color ARGB value (AARRGGBB) to RGB (00BBGGRR)
'************************************************************************************************************* Private Function ARGBtoRGB(ByVal clr As Color) As Integer Dim vARGB As Integer = clr.ToArgb 'convert color value to AARRGGBB
Return RGB((vARGB >> 16) And &HFF, (vARGB >> 8) And &HFF, vARGB And &HFF) 'return RGB color End Function
You then perform the desired drawing operation. Because several of the methods, such as DrawArc(),
DrawEllipse(), DrawRectangle(), DrawRoundRect(), and DrawObround() have overloaded methods, you can
specify them by providing Point objects to define their top-left and bottom-right bounds, or by providing a
bounding Rectangle object. Also, you must provide a graphical interface to the target object you want to
draw upon. For example:
'draw a rectangle from PictureBox coordinates 25,25 (top-left) to 100,100 (bottom-right) m_GDI.DrawRectangle(Me.PictureBox1.CreateGraphics, New Point(25, 25), New Point(100, 100))
Now draw as you see fit. You need to change colors and Raster operations only as needed, not every time.
Page –227–
The GDI32 Class What follows is the GDI32 class:
Option Strict On
Option Explicit On
'**************************************************************************************
' GDI32 - GDI Support for .NET '**************************************************************************************
#Region "GDI32 Enumerations"
'*****************************************************************************************************************************
' Enumerations '*****************************************************************************************************************************
' Pen Styles (how lines are drawn)
Public Enum PenStyles As Integer
PS_SOLID = &H0 'A pen style that is a solid color. ────── PS_DASH = &H1 'A pen style that is dashed. ------
PS_DOT = &H2 'A pen style that is dotted. ●●●●●● PS_DASHDOT = &H3 'A pen style that consists of alternating dashes and dots. -●-●-●
PS_DASHDOTDOT = &H4 'A pen style that consists of dashes and double dots. -●●-●●
PS_NULL = &H5 'A pen style that is invisible. PS_INSIDEFRAME = &H6 'A pen style that is a solid color. When this style is specified in a drawing record that takes a bounding rectangle,
' 'the dimensions of the figure are shrunk so that it fits entirely in the bounding rectangle, ' 'taking into account the width of the pen.
End Enum
'Type of Raster operation (how drawing interacts with the background)
Public Enum RasterOps As Integer R2_Black = 1 'Specifies black pen color.
R2_NotMergePen = 2 'Specifies the inverse of MergePen.
R2_MaskNotPen = 3 'Specifies a combination of the colors are common to the background color and the inverse of the pen. R2_NotCopyPen = 4 'Specifies the inverse of CopyPen.
R2_MakePenNot = 5 'Specifies a combination of the colors are common to both the pen and the inverse of the display. R2_Not = 6 'Specifies the inverse of the display color.
R2_XOrPen = 7 'Specifies a combination of the colors in the pen and in the display color, but not in both.
R2_NotMaskPen = 8 'Specifies the inverse of MaskPen. R2_MaskPen = 9 'Specifies a combination of the colors common to both the pen and the display.
R2_NotXOrPen = 10 'Specifies an inverse of XOrPen. R2_NoOperation = 11 'Specifies no operation; the output remains unchanged.
R2_MergeNotPen = 12 'Specifies a combination of the display color and the inverse of the pen color.
R2_CopyPen = 13 'Specifies the pen color. R2_MergePenNot = 14 'Specifies a combination of the pen color and the inverse of the display color.
R2_MergePen = 15 'Specifies a combination of the pen color and the display color. R2_White = 16 'Specifies a white pen color.End Enum
End Enum
'Hatch Styles
Public Enum HatchStyle As Integer HS_HORIZONTAL = 0 '----- A horizontal hatch.
HS_VERTICAL = 1 '||||| A vertical hatch.
HS_FDIAGONAL = 2 '\\\\\ A 45-degree downward, left-to-right hatch. HS_BDIAGONAL = 3 '///// A 45-degree upward, left-to-right hatch.
HS_CROSS = 4 '+++++ A horizontal and vertical cross-hatch. HS_DIAGCROSS = 5 'xxxxx A 45-degree crosshatch.
HS_FDIAGONAL1 = 6 '
HS_BDIAGONAL1 = 7 ' HS_SOLID = 8 '
HS_DENSE1 = 9 ' HS_DENSE2 = 10 '
HS_DENSE3 = 11 '
HS_DENSE4 = 12 ' HS_DENSE5 = 13 '
HS_DENSE6 = 14 ' HS_DENSE7 = 15 '
HS_DENSE8 = 16 '
HS_NOSHADE = 17 ' HS_HALFTONE = 18 '
HS_SOLIDCLR = 19 ' HS_DITHEREDCLR = 20 '
HS_SOLIDTEXTCLR = 21 '
HS_DITHEREDTEXTCLR = 22 ' HS_SOLIDBKCLR = 23 '
HS_DITHEREDBKCLR = 24 ' HS_API_MAX = 25 '
End Enum
'Stock Objects
Enum StockObjects As Integer WHITE_BRUSH = 0 'White brush.
LTGRAY_BRUSH = 1 'Light gray brush.
GRAY_BRUSH = 2 'Gray brush. DKGRAY_BRUSH = 3 'Dark gray brush.
BLACK_BRUSH = 4 'Black brush. NULL_BRUSH = 5 'Null Brush (equivalen to HOLLOW_BRUSH).
HOLLOW_BRUSH = NULL_BRUSH 'Hollow brush (equivalent to NULL_BRUSH)
WHITE_PEN = 6 'White pen. BLACK_PEN = 7 'Black pen.
NULL_PEN = 8 'Null Pen. The null pen draws nothing. OEM_FIXED_FONT = 10 'Original Equiptment Manufacturer (OEM) dependent fixed-pitch (monospace) font.
ANSI_FIXED_FONT = 11 'Windows fixed-pitch (monospace) system font.
ANSI_VAR_FONT = 12 'Windows variable-pitch (proportional space) system font. SYSTEM_FONT = 13 'System font. By default, the system uses the system font to draw menus, dialog box controls, and text.
DEVICE_DEFAULT_FONT = 14 'WinNT/Win2K/XP: Device-dependent font. DEFAULT_PALETTE = 15 'Default palette. This palette consists of the static colors in the system palette.
SYSTEM_FIXED_FONT = 16 'Fixed-pitch (monospace) system font. This stock object is provided only for compatibility with 16-bit Windows versions earlier than 3.0.
DEFAULT_GUI_FONT = 17 'Default font for user interface objects such as menus and dialog boxes. This is MS Sans Serif. Compare this with SYSTEM_FONT. DC_BRUSH = 18 'Win2K/XP: Solid color brush. The default color is white. The color can be changed by using the SetDCBrushColor() function.
DC_PEN = 19 'Win2K/XP: Solid pen color. The default color is white. The color can be changed by using the SetDCPenColor() function. End Enum
#End Region
'************************************************************************************** ' GDI Class to support GDI operations not supported by GDI+
' GDI+ (gdiplus.dll), a Graphical Design Interface used to take advantage of Graphic
' card hardware and software. It was introduced with Windows XP. It was designed for ' use by C/C++ users, but naturally was incorporated into the .NET platform.
' GDI+ offers faster operations that those provided by GDI, yet Raster operations, such ' as displaying rubberband lines was lost.
'**************************************************************************************
Friend Class GDI32
Page –228–
#Region "GDI32 Protected Fields"
'***************************************************************************************************************************** ' Protected Fields
'*****************************************************************************************************************************
Protected m_hdc As IntPtr 'handle to drawing context
Protected m_gdiPen As IntPtr 'handle to pen Protected m_oldPen As IntPtr 'hold original pen
Protected m_penColor As Color = Color.White 'Default drawing color
Protected m_penStyle As PenStyles = PenStyles.PS_SOLID 'init pen to solid Protected m_penWidth As Integer = 1 'pixel width of pen (line)
Protected m_rasterOp As RasterOps = RasterOps.R2_CopyPen 'Init raster operation to normal
Protected m_gdiBrush As IntPtr 'handle to new brush Protected m_oldBrush As IntPtr 'hold original brush
Protected m_brushColor As Color = Color.Transparent 'default brush color (fill color). Protected m_brushHatch As HatchStyle = HatchStyle.HS_SOLID 'hatch style. None = use Solid Brush
#End Region
#Region "GDI32 P/Invoke Declarations"
'***************************************************************************************************************************** ' INTEROP P/INVOKE DECLARATIONS
'*****************************************************************************************************************************
'-----------------------------------------------------------------------------------------------------------------------------
' Function: CreateSolidBrush ' The CreateSolidBrush function creates a logical brush that has the specified solid color.
'-----------------------------------------------------------------------------------------------------------------------------
Private Declare Function CreateSolidBrush Lib "gdi32.DLL" Alias "CreateSolidBrush" ( _ ByVal crColor As Integer) As IntPtr
'-----------------------------------------------------------------------------------------------------------------------------
' Function: CreateHatchBrush
' The CreateHatchBrush function creates a logical brush that has the specified hatch pattern and color. '-----------------------------------------------------------------------------------------------------------------------------
Private Declare Function CreateHatchBrush Lib "gdi32.DLL" Alias "CreateHatchBrush" ( _ ByVal Style As HatchStyle, _
ByVal crColor As Integer) As IntPtr
'-----------------------------------------------------------------------------------------------------------------------------
' Function: CreatePatternBrush ' The CreatePatternBrush function creates a logical brush with the specified
' bitmap pattern. The bitmap can be a DIB section bitmap, which is created
' by the CreateDIBSection function, or it can be a device-dependent bitmap. '-----------------------------------------------------------------------------------------------------------------------------
Private Declare Function CreatePatternBrush Lib "gdi32.DLL" Alias "CreatePatternBrush" ( _ ByVal hBitmap As IntPtr) As IntPtr
'----------------------------------------------------------------------------------------------------------------------------- ' Function: GetStockObject
' The GetStockObject function retrieves a handle to one of the stock pens, brushes, fonts, or palettes. '-----------------------------------------------------------------------------------------------------------------------------
Private Declare Function GetStockObject Lib "gdi32.DLL" Alias "GetStockObject" ( _
ByVal nIndex As StockObjects) As IntPtr
'----------------------------------------------------------------------------------------------------------------------------- ' Function: CreatePen
' The CreatePen function creates a logical pen that has the specified style,
' width, and color. The pen can subsequently be selected into a device context ' and used to draw lines and curves.
'----------------------------------------------------------------------------------------------------------------------------- Private Declare Function CreatePen Lib "gdi32.DLL" Alias "CreatePen" ( _
ByVal nPenStyle As PenStyles, _
ByVal nWidth As Integer, _ ByVal crColor As Integer) As IntPtr
'-----------------------------------------------------------------------------------------------------------------------------
' Function: SelectObject
' The SelectObject function selects an object into the specified device context ' (DC). The new object replaces the previous object of the same type.
'----------------------------------------------------------------------------------------------------------------------------- Private Declare Function SelectObject Lib "gdi32.DLL" Alias "SelectObject" ( _
ByVal hdc As IntPtr, _
ByVal hObject As IntPtr) As IntPtr
'----------------------------------------------------------------------------------------------------------------------------- ' Function: DeleteObject
' The DeleteObject function deletes a logical pen, brush, font, bitmap, region,
' or palette, freeing all system resources associated with the object. After ' the object is deleted, the specified handle is no longer valid.
'----------------------------------------------------------------------------------------------------------------------------- Private Declare Function DeleteObject Lib "gdi32.DLL" Alias "DeleteObject" ( _
ByVal hObject As IntPtr) As IntPtr
'-----------------------------------------------------------------------------------------------------------------------------
' Function: SetROP2 ' The SetROP2 function sets the current foreground mix mode. GDI uses the
' foreground mix mode to combine pens and interiors of filled objects with
' the colors already on the screen. The foreground mix mode defines how colors ' from the brush or pen and the colors in the existing image are to be combined.
' If the function succeeds, the return value specifies the previous mix mode. ' If the function fails, the return value is zero.
'-----------------------------------------------------------------------------------------------------------------------------
Private Declare Function SetROP2 Lib "gdi32.DLL" Alias "SetROP2" ( _ ByVal hdc As IntPtr, _
ByVal nDrawMode As RasterOps) As RasterOps
'-----------------------------------------------------------------------------------------------------------------------------
' Function: GetROP2 ' The GetROP2 function retrieves the foreground mix mode of the specified
' device context. The mix mode specifies how the pen or interior color and ' the color already on the screen are combined to yield a new color.
' If the function succeeds, the return value specifies the previous mix mode.
' If the function fails, the return value is zero. '-----------------------------------------------------------------------------------------------------------------------------
Private Declare Function GetROP2 Lib "gdi32.DLL" Alias "GetROP2" ( _ ByVal hdc As IntPtr) As Integer
'structure used by MoveToEx. It will contain the previous current position Public Structure POINTAPI
Dim x As Integer Dim y As Integer
End Structure
Page –229–
'-----------------------------------------------------------------------------------------------------------------------------
' Function: MoveToEx ' The MoveToEx function updates the current position to the specified point
' and optionally returns the previous position.
'----------------------------------------------------------------------------------------------------------------------------- 'hdc: Handle to a device context.
'X: Specifies the x-coordinate, in logical units, of the new position, in logical units. 'Y: Specifies the y-coordinate, in logical units, of the new position, in logical units.
'lpPoint: Pointer to a POINT structure that receives the previous current position. If this parameter is a NULL pointer, the previous position is not returned.
'----------------------------------------------------------------------------------------------------------------------------- Private Declare Function MoveToEx Lib "gdi32.DLL" Alias "MoveToEx" ( _
ByVal hdc As IntPtr, _ ByVal x As Integer, _
ByVal y As Integer, _
ByVal lpPoint As POINTAPI) As Boolean
'----------------------------------------------------------------------------------------------------------------------------- ' Function: MoveToEx
' Description: The MoveToEx function updates the current position to the specified point
' and optionally returns the previous position. '-----------------------------------------------------------------------------------------------------------------------------
'hdc: Handle to a device context. 'X: Specifies the x-coordinate, in logical units, of the new position, in logical units.
'Y: Specifies the y-coordinate, in logical units, of the new position, in logical units.
'lpPoint: Pointer to a POINT structure that receives the previous current position. If this parameter is a NULL pointer, the previous position is not returned. '-----------------------------------------------------------------------------------------------------------------------------
Private Declare Function MoveToEx Lib "gdi32.DLL" Alias "MoveToEx" ( _ ByVal hdc As IntPtr, _
ByVal x As Integer, _
ByVal y As Integer, _ ByVal lpPoint As IntPtr) As Boolean
'-----------------------------------------------------------------------------------------------------------------------------
' Function: LineTo
' The LineTo function draws a line from the current position up to, but not ' including, the specified point.
'----------------------------------------------------------------------------------------------------------------------------- 'hdc: Handle to a device context.
'nXEnd: Specifies the x-coordinate, in logical units, of the line's ending point.
'nYEnd: Specifies the y-coordinate, in logical units, of the line's ending point. '-----------------------------------------------------------------------------------------------------------------------------
Private Declare Function LineTo Lib "gdi32.DLL" Alias "LineTo" ( _ ByVal hdc As IntPtr, _
ByVal nXEnd As Integer, _
ByVal nYEnd As Integer) As Boolean
'----------------------------------------------------------------------------------------------------------------------------- ' Function: Ellipse
' The Ellipse function draws an ellipse. The center of the ellipse is the
' center of the specified bounding rectangle. The ellipse is outlined by using ' the current pen and is filled by using the current brush.
'----------------------------------------------------------------------------------------------------------------------------- 'hdc: A handle to the device context.
'nLeftRect: The x-coordinate, in logical coordinates, of the upper-left corner of the bounding rectangle.
'nTopRect: The y-coordinate, in logical coordinates, of the upper-left corner of the bounding rectangle. 'nRightRect: The x-coordinate, in logical coordinates, of the lower-right corner of the bounding rectangle.
'nBottomRect: The y-coordinate, in logical coordinates, of the lower-right corner of the bounding rectangle. '-----------------------------------------------------------------------------------------------------------------------------
Private Declare Function Ellipse Lib "gdi32.DLL" Alias "Ellipse" ( _
ByVal hdc As IntPtr, _ ByVal nLeftRect As Integer, _
ByVal nTopRect As Integer, _ ByVal nRightRect As Integer, _
ByVal nBottomRect As Integer) As Boolean
'-----------------------------------------------------------------------------------------------------------------------------
' Function: Rectangle ' The Rectangle function draws a rectangle. The rectangle is outlined by using
' the current pen and filled by using the current brush.
'----------------------------------------------------------------------------------------------------------------------------- 'hdc: A handle to the device context.
'nLeftRect: The x-coordinate, in logical coordinates, of the upper-left corner of the rectangle. 'nTopRect: The y-coordinate, in logical coordinates, of the upper-left corner of the rectangle.
'nRightRect: The x-coordinate, in logical coordinates, of the lower-right corner of the rectangle.
'nBottomRect: The y-coordinate, in logical coordinates, of the lower-right corner of the rectangle. '-----------------------------------------------------------------------------------------------------------------------------
Private Declare Function Rectangle Lib "gdi32.DLL" Alias "Rectangle" ( _ ByVal hdc As IntPtr, _
ByVal nLeftRect As Integer, _
ByVal nTopRect As Integer, _ ByVal nRightRect As Integer, _
ByVal nBottomRect As Integer) As Boolean
'-----------------------------------------------------------------------------------------------------------------------------
' Function: RoundRect ' The RoundRect function draws a rectangle with rounded corners. The rectangle
' is outlined by using the current pen and filled by using the current brush. '-----------------------------------------------------------------------------------------------------------------------------
'hdc: A handle to the device context.
'nLeftRect: The x-coordinate, in logical coordinates, of the upper-left corner of the rectangle. 'nTopRect: The y-coordinate, in logical coordinates, of the upper-left corner of the rectangle.
'nRightRect: The x-coordinate, in logical coordinates, of the lower-right corner of the rectangle. 'nBottomRect: The y-coordinate, in logical coordinates, of the lower-right corner of the rectangle.
'nWidth: The width, in logical coordinates, of the ellipse used to draw the rounded corners.
'nHeight: The height, in logical coordinates, of the ellipse used to draw the rounded corners. '-----------------------------------------------------------------------------------------------------------------------------
Private Declare Function RoundRect Lib "gdi32.DLL" Alias "RoundRect" ( _ ByVal hdc As IntPtr, _
ByVal nLeftRect As Integer, _
ByVal nTopRect As Integer, _ ByVal nRightRect As Integer, _
ByVal nBottomRect As Integer, _ ByVal nWidth As Integer, _
ByVal nHeight As Integer) As Boolean
'-----------------------------------------------------------------------------------------------------------------------------
' Function: Arc ' The Arc function draws an elliptical arc.
'-----------------------------------------------------------------------------------------------------------------------------
' hdc: A handle to the device context where drawing takes place. ' nLeftRect: The x-coordinate, in logical units, of the upper-left corner of the bounding rectangle.
' nTopRect: The y-coordinate, in logical units, of the upper-left corner of the bounding rectangle. ' nRightRect: The x-coordinate, in logical units, of the lower-right corner of the bounding rectangle.
' nBottomRect: The y-coordinate, in logical units, of the lower-right corner of the bounding rectangle.
' nXStartArc: The x-coordinate, in logical units, of the ending point of the radial line defining the starting point of the arc. ' nYStartArc: The y-coordinate, in logical units, of the ending point of the radial line defining the starting point of the arc.
' nXEndArc: The x-coordinate, in logical units, of the ending point of the radial line defining the ending point of the arc. ' nYEndArc: The y-coordinate, in logical units, of the ending point of the radial line defining the ending point of the arc.
'-----------------------------------------------------------------------------------------------------------------------------
Page –230–
'The points (nLeftRect, nTopRect) and (nRightRect, nBottomRect) specify the bounding rectangle. An ellipse formed by the specified
' bounding rectangle defines the curve of the arc. The arc extends in the current drawing direction from the point where it ' intersects the radial from the center of the bounding rectangle to the (nXStartArc, nYStartArc) point. The arc ends where it
' intersects the radial from the center of the bounding rectangle to the (nXEndArc, nYEndArc) point. If the starting point and
' ending point are the same, a complete ellipse is drawn. 'The arc is drawn using the current pen; it is not filled.
'----------------------------------------------------------------------------------------------------------------------------- Private Declare Function Arc Lib "gdi32.DLL" Alias "Arc" ( _
ByVal hdc As IntPtr, _
ByVal nLeftRect As Integer, _ ByVal nTopRect As Integer, _
ByVal nRightRect As Integer, _ ByVal nBottomRect As Integer, _
ByVal nXStartArc As Integer, _
ByVal nYStartArc As Integer, _ ByVal nXEndArc As Integer, _
ByVal nYEndArc As Integer) As Boolean #End Region
#Region "GDI32 Properties" '*****************************************************************************************************************************
' Properties ' BrushColor: Get/Set brush color. Default is Black. Also be sure this is black for XOR operations and you want a "Rubberband" effect.
' BrushHatch: Get/Set optional Brush Hatch Style. Default is HS_SOLID, which specifies a solid brush.
' PenColor: Get/Set pen color. Default is black. ' PenStyle: Get/Set pen style. Default is PS_SOLID.
' PenWidth: Get/Set pen pixel width. Default is 1. ' RasterOp: Get/Set type of raster operation to perform. Default is R2_CopyPen (overwrite)
'*****************************************************************************************************************************
'Get/Set brush color
Public Property BrushColor() As Color Get
Return Me.m_brushColor
End Get Set(ByVal value As Color)
Me.m_brushColor = value End Set
End Property
'Get/Set Brush Hatch Style
Public Property BrushHatch() As HatchStyle Get
Return Me.m_brushHatch
End Get Set(ByVal value As HatchStyle)
Me.m_brushHatch = value End Set
End Property
'Get/Set pen color
Public Property PenColor() As Color Get
Return Me.m_penColor
End Get Set(ByVal value As Color)
Me.m_penColor = value End Set
End Property
'Get/Set pen style (pattern)
Public Property PenStyle() As PenStyles Get
Return Me.m_penStyle
End Get Set(ByVal value As PenStyles)
Me.m_penStyle = value End Set
End Property
'Get/Set pen width in pixels
Public Property PenWidth() As Integer Get
Return Me.m_penWidth
End Get Set(ByVal value As Integer)
Me.m_penWidth = value End Set
End Property
'Get/Set Raster Operation
Public Property RasterOp() As RasterOps Get
Return m_rasterOp
End Get Set(ByVal value As RasterOps)
m_rasterOp = value End Set
End Property
#End Region
#Region "GDI32 Protected Support Methods" '*****************************************************************************************************************************
' Protected Support Methods
'*****************************************************************************************************************************
'************************************************************************************************************* ' Function: ARGBtoRGB
' Helper function to covert Alpha Color ARGB value (AARRGGBB) to RGB (00BBGGRR)
'************************************************************************************************************* Private Function ARGBtoRGB(ByVal clr As Color) As Integer
Dim vARGB As Integer = clr.ToArgb 'convert color value to AARRGGBB Return RGB((vARGB >> 16) And &HFF, (vARGB >> 8) And &HFF, vARGB And &HFF) 'return RGB color
End Function
Page –231–
'-----------------------------------------------------------------------------------------------------------------------------
' Subrouine: InitPenAndBrush ' Initialize pen annd brush
'-----------------------------------------------------------------------------------------------------------------------------
Protected Sub InitPenAndBrush(ByVal g As Graphics) Me.m_hdc = g.GetHdc 'save handle to device context
'process brush options If Me.m_brushColor = Color.Transparent Then
Me.m_gdiBrush = GetStockObject(StockObjects.NULL_BRUSH) 'hide brush if brush color is transparent
ElseIf Me.m_brushHatch = HatchStyle.HS_SOLID Then Me.m_gdiBrush = GDI32.CreateSolidBrush(Me.ARGBtoRGB(Me.m_brushColor)) 'set solid brush style and fill color)
Else Me.m_gdiBrush = GDI32.CreateHatchBrush(Me.m_brushHatch, Me.ARGBtoRGB(Me.m_brushColor)) 'create hatch brush style
End If
'process pen options If Me.m_penColor = Color.Transparent Then
Me.m_gdiPen = GetStockObject(StockObjects.NULL_PEN) 'hide pen if it is transparent Else
Me.m_gdiPen = GDI32.CreatePen(Me.PenStyle, Me.m_penWidth, Me.ARGBtoRGB(Me.PenColor)) 'set pen pattern, style, and width
End If
GDI32.SetROP2(Me.m_hdc, m_rasterOp) 'set raster operation Me.m_oldPen = GDI32.SelectObject(Me.m_hdc, Me.m_gdiPen) 'set new pen, save old
Me.m_oldBrush = GDI32.SelectObject(Me.m_hdc, Me.m_gdiBrush) 'set new background brush, save old
End Sub
'----------------------------------------------------------------------------------------------------------------------------- ' Subrouine: DisposeResources
' Dispose of created data and reset old data
'----------------------------------------------------------------------------------------------------------------------------- Protected Sub DisposeResources(ByVal g As Graphics)
GDI32.DeleteObject(GDI32.SelectObject(Me.m_hdc, Me.m_oldBrush)) 'reset old brush, delete replaced one GDI32.DeleteObject(GDI32.SelectObject(Me.m_hdc, Me.m_oldPen)) 'reset old pen, delete replaced one
g.ReleaseHdc(Me.m_hdc) 'release device context
g.Dispose() End Sub
#End Region
#Region "GDI32 Public Methods"
'***************************************************************************************************************************** ' Public Methods
'*****************************************************************************************************************************
'-----------------------------------------------------------------------------------------------------------------------------
' Function: DrawCircle ' draw a circle with a uniform radius from a center point
'----------------------------------------------------------------------------------------------------------------------------- Public Sub DrawCircle(ByVal g As Graphics, ByVal p1 As Point, ByVal Radius As Integer) 'p1 is center of circle
Me.InitPenAndBrush(g) 'init pen and brush
GDI32.Ellipse(Me.m_hdc, p1.X - Radius, p1.Y - Radius, p1.X + Radius, p1.Y + Radius) 'draw circle Me.DisposeResources(g) 'disposes of resources
End Sub
'-----------------------------------------------------------------------------------------------------------------------------
' Function: DrawEllipse ' Draw circles and ellipses from a bounds defined by a top-left point and a bottom-right point
'----------------------------------------------------------------------------------------------------------------------------- Public Sub DrawEllipse(ByVal g As Graphics, ByVal p1 As Point, ByVal p2 As Point) 'p1 is top-left of bounds, p2 is bottom-right
Me.InitPenAndBrush(g) 'init pen and brush
GDI32.Ellipse(Me.m_hdc, p1.X, p1.Y, p2.X, p2.Y) 'draw ellipse Me.DisposeResources(g) 'disposes of resources
End Sub
'-----------------------------------------------------------------------------------------------------------------------------
' Function: DrawEllipse ' Draw ellipses defined within a bounding rectangle
'----------------------------------------------------------------------------------------------------------------------------- Public Sub DrawEllipse(ByVal g As Graphics, ByVal Bnds As Rectangle) 'p1 is top-left of bounds, p2 is bottom-right
Me.InitPenAndBrush(g) 'init pen and brush
With Bnds GDI32.Ellipse(Me.m_hdc, .Left, .Top, .Right, .Bottom) 'draw ellipse within the rectangle
End With Me.DisposeResources(g) 'disposes of resources
End Sub
'-----------------------------------------------------------------------------------------------------------------------------
' Function: DrawArc ' Draw an arc within a bounding rectangle. Point ArcStart defines a point from center along start line, ArcEnd is a point along end line
'-----------------------------------------------------------------------------------------------------------------------------
Public Sub DrawArc(ByVal g As Graphics, ByVal Bnds As Rectangle, ByVal ArcStart As Point, ByVal ArcEnd As Point) Me.InitPenAndBrush(g) 'init pen and brush
With Bnds GDI32.Arc(Me.m_hdc, .Left, .Top, .Right, .Bottom, ArcStart.X, ArcStart.Y, ArcEnd.X, ArcEnd.Y) 'draw arc
End With
Me.DisposeResources(g) 'disposes of resources End Sub
'-----------------------------------------------------------------------------------------------------------------------------
' Function: DrawArc
' Draw an arc within a rectangle specifyed by p1 (top-left) and p2(bottom-right). ' Point ArcStart defines a point from center along start line, ArcEnd is a point along end line
'----------------------------------------------------------------------------------------------------------------------------- Public Sub DrawArc(ByVal g As Graphics, ByVal p1 As Point, ByVal p2 As Point, ByVal ArcStart As Point, ByVal ArcEnd As Point)
Me.InitPenAndBrush(g) 'init pen and brush
GDI32.Arc(Me.m_hdc, p1.X, p1.Y, p2.X, p2.Y, ArcStart.X, ArcStart.Y, ArcEnd.X, ArcEnd.Y) 'draw arc Me.DisposeResources(g) 'disposes of resources
End Sub
'-----------------------------------------------------------------------------------------------------------------------------
' Function: DrawLine ' Draw a line from point p1 to point p2
'----------------------------------------------------------------------------------------------------------------------------- Public Sub DrawLine(ByVal g As Graphics, ByVal p1 As Point, ByVal p2 As Point)
Me.InitPenAndBrush(g) 'init pen and brush
GDI32.MoveToEx(Me.m_hdc, p1.X, p1.Y, IntPtr.Zero) 'move to starting point GDI32.LineTo(Me.m_hdc, p2.X, p2.Y) 'draw line from start point to end point
Me.DisposeResources(g) 'disposes of resources End Sub
Page –232–
'-----------------------------------------------------------------------------------------------------------------------------
' Function: DrawPolygon ' Draw a polygon from an array of points
'-----------------------------------------------------------------------------------------------------------------------------
Public Sub DrawPolygon(ByVal g As Graphics, ByRef PointsArray() As Point) If PointsArray Is Nothing Then
Return 'if nothing to process End If
Dim NumPoints As Integer = UBound(PointsArray) 'get upper bounds of array
Me.InitPenAndBrush(g) 'init pen and brush GDI32.MoveToEx(Me.m_hdc, PointsArray(0).X, PointsArray(0).Y, IntPtr.Zero) 'move to starting point
If CBool(NumPoints) Then 'if more than 1 point For Idx As Integer = 1 To NumPoints 'process each point as a sequence in a chain
GDI32.LineTo(Me.m_hdc, PointsArray(Idx).X, PointsArray(Idx).Y) 'draw a line from previous point to current
Next If Not PointsArray(0).Equals(PointsArray(NumPoints)) Then 'close polygon if start and last not the same
GDI32.LineTo(Me.m_hdc, PointsArray(NumPoints).X, PointsArray(NumPoints).Y) 'draw a line from previous point to current End If
End If
Me.DisposeResources(g) 'disposes of resources End Sub
'-----------------------------------------------------------------------------------------------------------------------------
' Function: DrawRectangle
' Draw a rectangle or square from a top-left point to a bottom-right point ' P1 is the top-left corner, P2 is the bottom Right corner
'----------------------------------------------------------------------------------------------------------------------------- Public Sub DrawRectangle(ByVal g As Graphics, ByVal p1 As Point, ByVal p2 As Point)
Me.InitPenAndBrush(g) 'init pen and brush
GDI32.Rectangle(Me.m_hdc, p1.X, p1.Y, p2.X, p2.Y) 'draw rectangle from point1 to point2 Me.DisposeResources(g) 'disposes of resources
End Sub
'-----------------------------------------------------------------------------------------------------------------------------
' Function: DrawRectangle ' Draw a rectangle or square from a bounding rectangle
'----------------------------------------------------------------------------------------------------------------------------- Public Sub DrawRectangle(ByVal g As Graphics, ByVal Bnds As Rectangle)
Me.InitPenAndBrush(g) 'init pen and brush
With Bnds GDI32.Rectangle(Me.m_hdc, .Left, .Top, .Right, .Bottom) 'draw rectangle within bounds
End With Me.DisposeResources(g) 'disposes of resources
End Sub
'-----------------------------------------------------------------------------------------------------------------------------
' Function: DrawRoundRect ' Draw a rounded rectangle (obround). Make CornerWidth and CornerHeight equal for symetrical corners
' P1 is the top-left corner, P2 is the bottom Right corner
'----------------------------------------------------------------------------------------------------------------------------- Public Sub DrawRoundRect(ByVal g As Graphics, ByVal p1 As Point, ByVal p2 As Point, ByVal CornerWidth As Integer, ByVal CornerHeight As Integer)
'if corner radius out of range If CornerWidth <= 0 OrElse CornerHeight <= 0 OrElse CornerWidth >= Math.Abs(p1.X - p2.X) OrElse CornerHeight >= Math.Abs(p1.Y - p2.Y) Then
DrawRectangle(g, p1, p2) 'just draw a rectangle
Else Me.InitPenAndBrush(g) 'init pen and brush
GDI32.RoundRect(Me.m_hdc, p1.X, p1.Y, p2.X, p2.Y, CornerWidth, CornerHeight) 'draw rounded rectangle from point1 to point2 Me.DisposeResources(g) 'disposes of resources
End If
End Sub
'----------------------------------------------------------------------------------------------------------------------------- ' Function: DrawRoundRect
' Draw a rounded rectangle (obround) from a bounding rectangle. Make CornerWidth and CornerHeight equal for symetrical corners
'----------------------------------------------------------------------------------------------------------------------------- Public Sub DrawRoundRect(ByVal g As Graphics, ByVal Bnds As Rectangle, ByVal CornerWidth As Integer, ByVal CornerHeight As Integer)
'if corner radius out of range With Bnds
If CornerWidth <= 0 OrElse CornerHeight <= 0 OrElse CornerWidth >= Math.Abs(.Left - .Right) OrElse CornerHeight >= Math.Abs(.Top - .Bottom) Then
DrawRectangle(g, Bnds) 'just draw a rectangle Else
Me.InitPenAndBrush(g) 'init pen and brush GDI32.RoundRect(Me.m_hdc, .Left, .Top, .Right, .Bottom, CornerWidth, CornerHeight) 'draw rounded rectangle from point1 to point2
Me.DisposeResources(g) 'disposes of resources
End If End With
End Sub
'-----------------------------------------------------------------------------------------------------------------------------
' Function: DrawObRound ' Draw an ObRound, allowing one to specify a uniform corner radius by specifying just a single corner radius property
' P1 is the top-left corner, P2 is the bottom Right corner '-----------------------------------------------------------------------------------------------------------------------------
Public Sub DrawObRound(ByVal g As Graphics, ByVal p1 As Point, ByVal p2 As Point, ByVal CornerRadius As Integer)
'if corner radius out of range If CornerRadius <= 0 OrElse CornerRadius >= Math.Abs(p1.X - p2.X) OrElse CornerRadius >= Math.Abs(p1.Y - p2.Y) Then
DrawRectangle(g, p1, p2) 'just draw a rectangle Else
Me.InitPenAndBrush(g) 'init pen and brush
GDI32.RoundRect(Me.m_hdc, p1.X, p1.Y, p2.X, p2.Y, CornerRadius, CornerRadius) 'draw rounded rectangle from point1 to point2 Me.DisposeResources(g) 'disposes of resources
End If End Sub
'----------------------------------------------------------------------------------------------------------------------------- ' Function: DrawObRound
' Draw an ObRound from a bounding rectangle, allowing one to specify a uniform corner radius by specifying just a single corner radius property '-----------------------------------------------------------------------------------------------------------------------------
Public Sub DrawObRound(ByVal g As Graphics, ByVal Bnds As Rectangle, ByVal CornerRadius As Integer)
'if corner radius out of range With Bnds
If CornerRadius <= 0 OrElse CornerRadius >= Math.Abs(.Left - .Right) OrElse CornerRadius >= Math.Abs(.Top - .Bottom) Then DrawRectangle(g, Bnds) 'just draw a rectangle
Else
Me.InitPenAndBrush(g) 'init pen and brush GDI32.RoundRect(Me.m_hdc, .Left, .Top, .Right, .Bottom, CornerRadius, CornerRadius) 'draw rounded rectangle from point1 to point2
Me.DisposeResources(g) 'disposes of resources End If
End With
End Sub #End Region
End Class
Page –233–
Emulating a Selection Rubber Band under GDI Suppose you wanted to define a Selection Rubber Band on an image, such as a background image on a
form. You can do this quite easily with the GDI32 class. The code required to support this is very simple
(I keep forcing myself to avoid the pun of saying it is very basic).
Create a new VB Windows project. Add a background image to it, if you wish. Also, if you choose to,
add some controls to dress it up (unlike all other rubber band examples you may have seen, this one
actually works exactly like you would expect of normal rubber band operations, where the rubber band
will also cover any controls it passes over on the form). Then, in the Form1 code, add the following:
Public Class Form1
Friend m_GDI As New GDI32 'instantiate an instace of the GDI class Friend m_StartPoint As Point 'keep track of starting point
Friend m_LastPoint As Point 'keep track of last mouse location
'**************************************************************************************
' Subroutine: Form1_MouseDown ' When the mouse select button is down, init the rectangle start and end points
'**************************************************************************************
Private Sub Form1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseDown m_GDI.PenColor = Color.White 'set color to white (black will be invisible in an XOR operation)
m_GDI.BrushColor = Color.Transparent 'hide the brush m_GDI.RasterOp = RasterOps.R2_XOrPen 'use XOR to emulate rubberbanding
m_StartPoint = e.Location 'set the start and end locations to the current mouse position
m_LastPoint = e.Location End Sub
'**************************************************************************************
' Subroutine: Form1_MouseMove
' If the mouse button is down, erase the old rubberband, update the location, ' then draw the new rubberband.
'************************************************************************************** Private Sub Form1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseMove
If e.Button = Windows.Forms.MouseButtons.Left Then 'if the mouse select button is down
DrawRubberBand() 'erase the old rectangle at the old location m_LastPoint = e.Location 'update the location
DrawRubberBand() 'draw the new rectange to the new location End If
End Sub
'**************************************************************************************
' Subroutine: Form1_MouseUp ' Erase the selection rubber band, report selection data
'**************************************************************************************
Private Sub Form1_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseUp DrawRubberBand() 'erase the old rectangle
Dim Ctls As New Collections.Generic.List(Of Control) 'list to collect controls selected
Dim Msg As String = "The selection rectangle was from (" & m_StartPoint.X.ToString & "," & m_StartPoint.Y.ToString & ") to (" & _
m_LastPoint.X.ToString & "," & m_LastPoint.Y.ToString & ")" & vbCrLf & vbCrLf
'define a new rectangle to compare against any controls on the form Dim Rect As Rectangle = NormalizeSelectRectangle()
'find which controls were selected
For Each Ctl As Control In Controls If Ctl.Visible AndAlso Ctl.Bounds.IntersectsWith(Rect) Then 'a control intersection was found
Ctls.Add(Ctl) End If
Next
If Ctls.Count = 0 Then
Msg &= "There were no controls selected" 'if no controls were selected Else
Msg &= "The follows controls were also selected:" & vbCrLf 'else list the controls selected
For Each Ctl As Control In Ctls Msg &= " " & Ctl.Name & vbCrLf
Next End If
'report results
MsgBox(Msg, MsgBoxStyle.OkOnly Or MsgBoxStyle.Information, "Selection Result") End Sub
'**************************************************************************************
' Function: NormalizeSelectRectangle
' Defind the selection rectangle and normalize its definition, where the start point is ' always upper-left, and the ending point is always lower-right
'************************************************************************************** Private Function NormalizeSelectRectangle() As Rectangle
'define the final selection rectangle
Dim X As Integer = m_StartPoint.X 'get start point Dim Y As Integer = m_StartPoint.Y
If X > m_LastPoint.X Then X = m_LastPoint.X 'NORMALIZE COORDINATES if inverted in any way If Y > m_LastPoint.Y Then Y = m_LastPoint.Y
'define a new rectangle to draw our current rectangle, and compare against any controls on the form
Return New Rectangle(X, Y, Math.Abs(m_LastPoint.X - m_StartPoint.X), Math.Abs(m_LastPoint.Y - m_StartPoint.Y)) End Function
'**************************************************************************************
' Subroutine: DrawRubberBand
' Draw or erase the rubberband, taking advantage of XOR Raster method. Also draw/erase ' over controls that may be placed on the form.
'************************************************************************************** Private Sub DrawRubberBand()
'define a new rectangle to draw our current rectangle, and compare against any controls on the form
Dim Rect As Rectangle = NormalizeSelectRectangle() m_GDI.DrawRectangle(Me.CreateGraphics, Rect) 'erase the old rectangle or draw the new one
For Each Ctl As Control In Controls 'also process any controls it intersects with If Ctl.Visible AndAlso Ctl.Bounds.IntersectsWith(Rect) Then 'a control intersection was found
'erase the old rectangle or draw the new one over control, offsetting the rubberband as needed
m_GDI.DrawRectangle(Ctl.CreateGraphics, New Rectangle(Rect.Left - Ctl.Left, Rect.Top - Ctl.Top, Rect.Width, Rect.Height)) End If
Next End Sub
End Class
Page –234–
NOTE: Unlike any other GDI example on the web, this is the first one to demonstrate a feature that many gurus have
typically thought was too difficult – the above DrawRubberBand() method draws the selection rubber band over any controls
it crosses. Most gurus considered this control-inclusive technique to be too complicated and deemed it an advanced topic
that cannot be covered in a single article. But, as you can see, my solution was unbelievably simple (actually, the solution
became simple due to techniques I had already developed to easily emulate VB6 image controls with transparent
backgrounds on a form – see the article, Emulating VB6 Image Control Features in VB.NET on page 58).
Now run the project (once you have added the GDI32 class to it). It
emulates the rubber band function very smoothly. Because it is
drawing to the form’s surface, any controls you place on the form
will be in front of the rubber band, unless we reflect it to the surface
of each control as well. The NormalizeSelectRectangle()
method normalizes coordinates, so that no matter how you make
your selection, the coordinate addressing will be standardized. The
dark-shaded code in the DrawRubberBand() method shows you
how simple it is to actually reflect the rubber band to the surface of
each control that it intersects with.
Emulating a Selection Rubber Band under GDI+ Suppose you wanted to simply emulate a Selection Rubber Band under the graphics support that is
already built into .NET, using GDI+. We can do that as well, though if you are not very careful in the
way you write it, I think it can also look much less professional than the GDI method (and I have seen
some very bad examples on the web at some blog and support sites – but these folks are just trying to get
people pointed in the right direction to solving their own problems). For instance, with some examples, I
often see the selection rectangle disappear after about one second, if you hold it still. But if it is rendered
and handled properly, as I will show you, below, it will work great and it will look clean.
Using the GDI+ DrawReversibleFrame() Method The typical technique developers have been using to perform this task
under GDI+ (and the technique I like the least) is by implementing the
ControlPaint.DrawReversibleFrame() method. Unlike the GDI method,
this method draws to the screen, over the top of everything, which is
useful for also conveniently covering form controls without the need for
additional code. But, because it draws to the whole screen, we must
translate our rectangle’s start coordinates to coordinates relative to the
screen, which we can do using the PointToScreen() method, as you will
see in the new DrawRubberBand() method, below. Unlike the previous
example, this example does not require the GDI32 class.
NOTE: This method can look goofy and lose its luster if you move the selection outside the target form, where it will leave
graphical artifacts on the screen, though no real damage is caused and it cleans easily. As such, I will check for this within
the form code and automatically correct for that. But regardless, it can also at times leave tiny graphical artifacts on certain
controls on the form as well. I do not like this method for this reason, and consider it to be the least professional method to
use. But we will cover it here because so many online examples feature it, and people are constantly asking me about it.
Like the GDI Rubber Band example, create a new VB Windows project. Add a background image to it
if you wish. Also add some controls to dress it up if you choose. And, to avoid window flashing, be sure
to also set the form’s DoubleBluffered parameter to True. Then, in the Form1 code, add the following:
Public Class Form1
Friend m_StartPoint As Point 'keep track of starting point Friend m_LastPoint As Point 'keep track of last mouse location
'************************************************************************************** ' Subroutine: Form1_MouseDown
' When the mouse select button is down, init the rectangle start and end points '**************************************************************************************
Private Sub Form1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseDown
m_StartPoint = e.Location 'set the start and end locations to the current mouse position m_LastPoint = e.Location
End Sub
Page –235–
'************************************************************************************** ' Subroutine: Form1_MouseMove
' If the mouse button is down, erase the old rubberband, update the location,
' then draw the new rubberband. '**************************************************************************************
Private Sub Form1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseMove If e.Button = Windows.Forms.MouseButtons.Left Then 'if the mouse select button is down
Dim Rect As Rectangle = NormalizeSelectRectangle() 'get current rectangle
Rect.Width += 1 'bump 1 for width/height because drawing edge is one higher than rectangle Rect.Height += 1
Me.Invalidate(Rect) 'invalidate that region Me.Update() 'update display of that region
m_LastPoint = e.Location 'update the location
DrawRubberBand() 'draw the new rectange to the new location End If
End Sub
'**************************************************************************************
' Subroutine: Form1_MouseUp ' Erase the selection rubber band, report selection data
'************************************************************************************** Private Sub Form1_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseUp
Me.Refresh() 'refresh form and controls, erasing rectangles
Dim Ctls As New Collections.Generic.List(Of Control) 'list to collect controls selected
Dim Msg As String = "The selection rectangle was from (" & m_StartPoint.X.ToString & "," & m_StartPoint.Y.ToString & ") to (" & _ m_LastPoint.X.ToString & "," & m_LastPoint.Y.ToString & ")" & vbCrLf & vbCrLf
'define a new rectangle to compare against any controls on the form Dim Rect As Rectangle = NormalizeSelectRectangle()
'find which controls were selected For Each Ctl As Control In Controls
If Ctl.Visible AndAlso Ctl.Bounds.IntersectsWith(Rect) Then 'a control intersection was found
Ctls.Add(Ctl) End If
Next
If Ctls.Count = 0 Then
Msg &= "There were no controls selected" 'if no controls were selected Else
Msg &= "The follows controls were also selected:" & vbCrLf 'else list the controls selected For Each Ctl As Control In Ctls
Msg &= " " & Ctl.Name & vbCrLf
Next End If
'report results MsgBox(Msg, MsgBoxStyle.OkOnly Or MsgBoxStyle.Information, "Selection Result")
End Sub
'**************************************************************************************
' Function: NormalizeSelectRectangle ' Defind the selection rectangle and normalize its definition, where the start point is
' always upper-left, and the ending point is always lower-right
'************************************************************************************** Private Function NormalizeSelectRectangle() As Rectangle
'define the final selection rectangle Dim X As Integer = m_StartPoint.X 'get start point
Dim Y As Integer = m_StartPoint.Y
If m_LastPoint.X < 0 Then m_LastPoint.X = -1 'prevent selection from goung outside top-left of frame If m_LastPoint.Y < 0 Then m_LastPoint.Y = -1
If X > m_LastPoint.X Then X = m_LastPoint.X 'NORMALIZE COORDINATES if inverted in any way If Y > m_LastPoint.Y Then Y = m_LastPoint.Y
'define a new rectangle to draw our current rectangle, and compare against any controls on the form
Return New Rectangle(X, Y, Math.Abs(m_LastPoint.X - m_StartPoint.X), Math.Abs(m_LastPoint.Y - m_StartPoint.Y)) End Function
'**************************************************************************************
' Subroutine: DrawRubberBand
' Draw or erase the rubberband, taking advantage of XOR Raster method. Also draw/erase ' over controls that may be placed on the form.
'************************************************************************************** Private Sub DrawRubberBand()
'define a new rectangle to draw our current rectangle
Dim Rect As Rectangle = NormalizeSelectRectangle() With Me.ClientRectangle
If Rect.Left + Rect.Width > .Width Then 'make sure selection, which is actually drown to the screen, not Rect.Width = .Width - Rect.Left + 1 'to the form, will not exceed the bounds of the form's client
End If 'area, otherwise the selection rectangle will be reflected to the
If Rect.Top + Rect.Height > .Height Then 'screen area not covered by the form, and will look very Rect.Height = .Height - Rect.Top + 1 'unprofessional. These two checks for eliminate that problem.
End If End With
'draw the selection rectangle to the actual screen, not to the form and its controls.
ControlPaint.DrawReversibleFrame(New Rectangle(PointToScreen(Rect.Location), Rect.Size), Color.White, FrameStyle.Dashed) End Sub
End Class
Using the GDI+ DrawRectangle() Method and a Translucent Brush If you want to emulate the selection rectangle that Vista and Windows
7 uses, this is also very easy to do. In fact, I was not able to invent this
method until I figured out how to make the rectangle for the
DrawReversibleFrame() method more stable. Only then did I begin
to wonder about how Vista and Windows 7 made their translucent
selection rectangle. Granted, I could have simply used the GDI+
DrawRectangle() method to emulate the previous examples, once I
discovered that the Invalidate(), Refresh(), and Update()
methods could be used to make the GDI+ DrawReversibleFrame()
method appear more professional-looking. However, because it was now so stable, especially with the
form’s DoubleBluffered property set to True, I felt that I had to figure it out.
Page –236–
NOTE: Setting the DoubleBuffered property for a form to True is important to cut down on screen flicker. With this
property set, the display will wait until it is between display refresh cycles to update the screen, so display updates while
drawing is going on will be minimized.
The only real problem I was running into was finding out how to create a translucent brush, and no one
seemed to have any answers on the internet – only questions on how to create one.
That is, until I happened upon an obscure example for using .NET’s SolidBrush() method while
studying brush creation in MSDN.
Creating a translucent brush simply involves assigning a color value to a brush. The color value is one of
the common ARGB colors that are typical to the WinXP+ operating systems. Unfortunately, most
people seem to be at a loss to as to how to do that with an alpha blended color value. Worse, most
examples of the SolidBrush() method specify just a single color parameter, and leave it at that.
The trick, though, is to realize that a color can be multiply defined. Consider the following examples:
Dim Clr As Color = Color.Navy 'assign Clr variable the color Navy Clr = Color.FromName("Navy") 'assign Clr variable the color Navy
Clr = RGB(0, 0, 80) 'assign Clr variable the color Navy (Red, Green, Blue)
Clr = Color.FromArgb(Color.Navy.ToArgb) 'assign Clr variable the integer value of Navy
Clr = Color.FromArgb(128, 0, 0, 80) 'Assign Clr variable an alpha blend value of 128 (1/2 opaque) for the color Green (A,R,G,B)
Clr = Color.FromArgb(0, 0, 80) 'Assign Clr variable the color Navy ([Alpha=255; fully opaque], Red=0, Green=0, Blue=80)
Clr = Color.FromArgb(64, Color.Navy) 'Assign Clr variable an alpha blend value of 64 (1/4 opaque, 3/4 transparent) for the color Navy
With this, we can create a brush with a ½ opaque (½ transparent) Light Blue color using the following:
Dim Brsh As New SolidBrush(Color.FromArgb(128, Color.LightBlue)) 'define translucent brush with 50% translucency
Then to use it, we need only fill a drawn rectangle with the brush:
Me.CreateGraphics.FillRectangle(Brsh, Rect) 'do fill with translucient brush (we can also define the new brush here)
Hence, replace the DrawRubberBand() method in the previous example with the following two methods:
'************************************************************************************** ' Subroutine: DrawRubberBand
' Draw the rubberband. Also draw over controls that may be placed on the form.
'**************************************************************************************
Private Sub DrawRubberBand()
'define a new rectangle to draw our current rectangle, and compare against any controls on the form
Dim Rect As Rectangle = NormalizeSelectRectangle()
DrawBand(Me.CreateGraphics(), Rect) 'draw selection rectangle for form
For Each Ctl As Control In Controls 'also process any controls it intersects with
If Ctl.Visible Then 'if control can be seen
Ctl.Refresh() 'always refresh control surface, even if not affected (otherwise artifacts may be left behind) If Ctl.Bounds.IntersectsWith(Rect) Then 'a control intersection was found
'erase the old rectangle or draw the new one over control, offsetting the rubberband as needed
DrawBand(Ctl.CreateGraphics, New Rectangle(Rect.Left - Ctl.Left, Rect.Top - Ctl.Top, Rect.Width, Rect.Height))
End If
End If
Next
End Sub
'**************************************************************************************
' Subroutine: DrawBand
' Draw using a Vista/Win7-style selection rubberband
'**************************************************************************************
Private Sub DrawBand(ByVal eg As System.Drawing.Graphics, ByVal Rect As Rectangle)
Dim VistaClr As Color = Color.FromArgb(255, 51, 153, 255) 'use Vista/Win7 Selector color (Alpha=255: fully opaque)
eg.DrawRectangle(New Pen(VistaClr), Rect) 'draw out edge of rectangle
eg.FillRectangle(New SolidBrush(Color.FromArgb(64, VistaClr)), Rect) 'do fill with translucient brush (Alpha=64: 1/4 opaque, 3/4 transparent)
End Sub
You now have Vista-style or Windows 7-style rubber band selection.
NOTE: Some people have reported that they have this type of selection rectangle under Windows XP. My XP platform uses
just a rectangle outline. Perhaps they have an enhancement patch installed, or have an option set that I have disabled,
because I thought that this translucent selection rectangle was introduced with Windows 6.0 (Vista. By the way, Windows 7
is actually Windows 6.1 – there goes the Microsoft marketing department, again).
David Ross Goben
Last update: Wednesday, June 08, 2011, 3:33:16 PM Contact: [email protected]
Page –237–
About the Author
David Ross Goben has been a professional software engineer, a writer, and an obsessive
researcher. Of Jewish descent, he has extensively explored Biblical history, ancient
cultural thinking, and ancient slang for over three decades, which has resulted in his seminal work: A Gnostic Cycle: Exploring the Origin of Christianity. He has written
numerous books, manuals, and magazine articles, many uncredited, or authored under
pen names. He has been involved in computing since long before personal computers
were available even in kit form, he was a contributing Editor to 80 Micro Magazine, and there, co-wrote the Feedback Loop column with Beve Woodbury, under the pen name, Mercedes Silver. His
interests include Cosmology, Quantum Physics, human-machine interaction, the Global Warming Myth, The
Electric Universe Theory, Perpetual Energy Technology, Quartz Technology, Dream Walking, and the study of the bio-mechanical origins of life.
He is currently exploring the possibility that Albert Einstein got his Theory of Special Relativity wrong. Just about
everyone is familiar with his equation, E=MC2, Energy (E) equals the Mass (M) times the speed of light (C)
squared. But this Mass to Energy conversion is only half of the Special Relativity expression.
The other half of Special Relativity involves Velocity (V), and is the calculation of Time Displacement (T):
Einstein calculated E = MC2 / T, which would mean that at the speed of light, an object would attain infinite
volume and infinite mass. Correct me if I am wrong, but even an ordinary mortal like me sees this as ridiculous,
because this would mean that we would attain several quintillion times the volume and mass that exists in the
entire universe (and even that may an infinite understatement). All you have to do is simply ponder it for a moment.
However, reverse the order, where E = T / MC2, and all calculations yield the same results, save one, and that is
that instead of attaining infinite volume and infinite mass at the speed of light, one instead moves into an alternate
universe. What most people do not realize is that the very atoms of our body, at this very moment, are already vibrating at just under the speed of light. Just a tiny push and we would become multidimensional beings. I think
this is why Quantum Physics, which is still nowhere near being a perfect model for planck-scale physics, will,
even so, discover the Far World, or what the ancients called the Underworld or the Hidden World, or most everyone today refers to as the Afterlife or Heaven. Quantum Physics has already demonstrated that nothing in the
universe has ever existed without Consciousness. So, where was Consciousness when the Universe was created
out of literally nothing during a causal Singularity Event? Evidence shows that it existed. And that has been the crux of great debates throughout history.
There is an easy experiment that can prove that Einstein’s Theory of Special Relativity is inverted. Weigh a very
heavy object, and then drop it onto a solid base, such as a concrete floor. Now weight the object again. It will
weigh less… for about 20 minutes; at which time its full weight will finally return. Where did the missing mass go to during those 20 minutes? If Einstein’s calculation had been correct, the object would have actually weighed
more after being dropped, not less.
Page –238–
The following Visual Basic documents are publicly available at: http://www.slideshare.net/DavidRossGoben, and at Google Docs at: http://docs.google.com/leaf?id=0B_Dj_dKazINlN2JlY2EwMmEtNGUyMy00NzQzLTliN2QtMDhlZTc5NDUzY2E5&sort=name&layout=list&num=50.