Combining GDI and GDI+ to Draw Rubber Band Rectangles






4.75/5 (20 votes)
Sep 10, 2003
6 min read

274738

3839
Demonstrates drawng rubber band rectangles using GDI in a .NET GDI app
Introduction
I have been frustrated, like many others apparently, by the lack of XOR drawing capabilities in the .NET Framework. This is of particular interest when it’s needed to outline a selected Region of Interest in a graphic. I’ve seen several approaches to solve this problem that have, for the most part, stayed within the .NET Framework context, but they were fairly complex. Thus, I was inspired when I saw the article "Using GDI and GDI+ mixed drawing rubber band lines" by sedatkurt in the MFC/C++ >> GDI >> Unedited Reader Contributions in The CodeProject. I was sure it could be extended to rectangles as well.
Background
Anyone who has used a graphics program has probably also used a selection rectangle to act on only a portion of the displayed image. In historical Windows® applications, this selection rectangle is normally drawn by the mouse from a starting corner with the left mouse button depressed until the button is released. The mouse position at each move event is the second corner of the drawn rectangle. Along the way, the rectangle is therefore drawn and erased many times until the final version is drawn at mouse button up. If the drawing mode is set to an XOR raster operation (ROP), the rectangle is usually completely visible since all bits of the pixels are XORed with the color of the drawing pen. More importantly, redrawing in XOR mode along the same rectangle with the same pen automatically restores the original pixel colors, effectively erasing the rectangle. Unfortunately, the .NET Framework Team did not include ROP drawing control in GDI+.
The Core Code
While I adopted the basic concept that sedatkurt showed in his code, I was
primarily interested in developing a strictly rubber band rectangle drawing
capability. Consequently, all and only the rubber band rectangle related code
is concentrated into the RubberbandRectangle
class in the
RubberbandRects
namespace in the file RubberbandRects.cs. This means hardcoding the pen style
to draw the rectangle border as a dotted line (PS_DOT), an XOR drawing mode for
the pen (R2_XORPEN
), a pen width of 1 pixel (doesn't have to be pretty, just
visible), and a brush style to NOT fill in the rectangle (NULL_BRUSH
) so the
underlying graphics are still visible. The resultant public API of
RubberbandRectangle
is quite compact:
public class RubberbandRectangle
{
public RubberbandRectangle();
public int PenStyle { get/set }
public void DrawXORRectangle( Graphics grp,
int X1, int Y1,
int X2, int Y2 );
}
The default constructor sets the pen style to the default value of PS_DOT
. I was
not originally going to include a capability to reset this value, but I relented
and made the pen style a property with get/set capability. The pen color is
fixed internally in the class as a predefined BLACK_PEN
in the
CreatePen
call,
although I kept sedatkurt's RGB conversion macro in case.
The process of drawing the XORed rectangle begins with extracting the Win32 GDI
device context from the GDI+ Graphics object passed to the function. A black
dotted pen is created one pixel wide. The ROP drawing mode is then set to XOR and
the new pen selected into the device context; the old pen's handle is saved for
replacement later (always clean up resources when you're finished with them). A
stock NULL_BRUSH
is created and simultaneously selected into the device context,
again saving the old brush handle for later. The drawing is now performed, the old
brush and pen put back into the device context, and the new pen deleted. Note that
stock resources do not need to be deleted since they're only borrowed anyway. The
device context is released and the function is finished.
Using the Code
The demo application is in the file MainForm.cs. It is a simple
WindowsForm
with a number of rectangles painted on its client area in the MainForm_Paint
event handler. However, I've also added a call to DrawXORRectangle()
if
a rubber band rectangle was present (the flag haveRect
is set) so that the dotted rectangle is also then redrawn.
The rectangle drawing functionality is nearly the same as in sedatkurt's code.
The rubber banding operation is initiated in MainForm_MouseDown
where the
mouseDown
flag is set to indicate that the mouse button is down. The initial point of the
mouse down event is stored in XDown
and YDown
. All of this presumes that it was
the Left mouse button that is being pressed. I use a Right mouse button press
to initiate an operation to clear the rubber band rectangle from the screen
(clear the haveRect
flag and call Invalidate()
).
The stage is now set for the actual drawing, which takes place in the
MainForm_MouseMove
event handler. Drawing will actually only occur if the mouse
button is down. This prevents an attempt at drawing when the mouse is simply
run across the app. If the mouse is down and moving, a rectangle has already
been drawn and must be erased with a call to DrawXORRectangle()
. The
rectangle is then redrawn through a call to DrawXORRectangle()
with one
corner at the new mouse coordinates. The new coordinates are saved and the
moving flag set.
The final part of the rubber band rectangle drawing occurs in the
MainForm_MouseMove
event handler. The mouseDown
and
mouseMove
flags are cleared and the haveRect
flag is set. We now have a dotted
rectangle in the client area of the app window.
Points of Interest
This code was developed in SharpDevelop, an IDE available for free at http://www.icsharpcode.net. It is an evolving, beta level project written in C# and comes with full source code. While it still has warts, it is improving by the month and the price is right. For anything but large team, production code development, it works fine. I used version 0.96 which compiles with .NET Framework v1.1, so you will need the latest .NET version to run the demo app. There is no apparent reason it won't compile with Visual Studio 2003 or any other Visual Studio / .NET Framework pair as well.
What's Next
The code in the RubberbandRectangle
class is a stripped down version I had evolved
keeping a lot of the enums and functions implied by sedatkurt's code and adding the
enums and functions appropriate for the rectangle (or rounded rectangle or ellipse or
polygon) drawing cases. In the end, I stripped it all out since I had no interest in
demonstrating it and the extension of what's left is fairly straight forward. A possible
extension in the same vein as rubber banding is the ability to move bitmaps around the
client area with the mouse. A brush made from the bitmap should be as effecting in
XOR drawing mode as a dotted pen. At any rate, enjoy.
History
- New code - no changes.