Click here to Skip to main content
15,886,518 members
Articles / Multimedia / GDI+
Article

C# Rubber Rectangle

Rate me:
Please Sign up or sign in to vote.
4.33/5 (13 votes)
30 Jun 2008CPOL3 min read 103.3K   2.7K   45   20
Implements a Rubber Rectangle in C#.

Introduction

I've been experimenting recently with the GDI+ draw methods to make a Plot control in C#. The goal was to build a Plotter that can display multiple graphs, fit to screen, zoom in/out, pan, etc. It had to be extremely easy to incorporate into existing projects, and was actually created to totally replace a similar legacy version of a Plotter that was written in LISP.

One of the largest problems I ran into was how to create a rubber rectangle. There are some ideas floating around on the Internet, there are a lot of hacks, and I finally decided upon a method that I think works well for me. Unfortunately, those of you using Mono won't get this working as easily as I did, but for the majority of you who use Windows, you should have no problems at all.

The Problem

Imagine a screen with lots of color data on it. You want to non-destructively draw the outline of a re-sizable box. Your first thought may be to draw a box with no fill color, with a 1px width black line for a border. However, when you re-size that box, you will find that you leave a big trail of black lines behind. You could then try and erase the black lines with a white line, but that will just leave white lines behind, still effectively destroying your drawings.

whatamess.GIF

The Solution

Draw a line with a XOR pen, which will exclusive-or (XOR) all the pixel color information. So, white will turn black, black will turn white, etc. Then, draw back over your box with a second XOR pen to return your drawing to its previous state. This is non-destructive drawing! Unfortunately, there is no method (that I know of) in GDI+ to create a XOR pen.

My solution to this lack of XOR pen was to use the gdi32.dll, and then call the GDI methods from C#. Here's how I did it.

The GDI32 Class

I first created a new class that I called GDI32. Then, I used Interop to get the external methods.

C#
[System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
private static extern bool Ellipse(IntPtr hdc, int x1, int y1, int x2, int y2);
[System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
private static extern bool Rectangle(IntPtr hdc, int X1, int Y1, int X2, int Y2);
[System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
private static extern IntPtr MoveToEx(IntPtr hdc, int x, int y, IntPtr lpPoint);
[System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
private static extern bool LineTo(IntPtr hdc, int x, int y);
[System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
private static extern IntPtr CreatePen(PenStyles enPenStyle, int nWidth, int crColor);
[System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
private static extern IntPtr CreateSolidBrush(BrushStyles enBrushStyle, int crColor);
[System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
private static extern bool DeleteObject(IntPtr hObject);
[System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
private static extern IntPtr SelectObject(IntPtr hdc, IntPtr hObject);
[System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
private static extern IntPtr GetStockObject(int brStyle);
[System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
private static extern int SetROP2(IntPtr hdc, int enDrawMode);

The only ones you really need here are SetROP2, SelectObject, DeleteObject, CreatePen, Rectangle, and CreateSolidBrush.

Then, I created a method that can initialize the pen and brush, and a method to dispose of the pen and brush after they've been used. One thing to note, R2_XORPEN is the XOR pen. You can pass (int)7 as an equivalent if you will just be using a XOR pen.

C#
/// <summary>
/// Initializes the pen and brush objects. Stores the old pen
/// and brush so they can be recovered later.
/// </summary>
protected void InitPenAndBrush(Graphics g)
{
    hdc = g.GetHdc();
    gdiPen = CreatePen(penStyle, lineWidth, GetRGBFromColor(PenColor));
    gdiBrush = CreateSolidBrush(brushStyle, GetRGBFromColor(fillColor));
    if (PenColor == Color.Transparent) SetROP2(hdc, (int)RasterOps.R2_XORPEN);
    oldPen = SelectObject(hdc, gdiPen);
    oldBrush = SelectObject(hdc, gdiBrush);
}

/// <summary>
/// Reloads the old pen and brush.
/// Deletes the pen that was created by InitPenAndBrush(g).
/// Releases the handle to the device context
/// and then disposes of the Graphics object.
/// </summary>
protected void Dispose(Graphics g)
{
    SelectObject(hdc, oldBrush);
    SelectObject(hdc, oldPen);
    DeleteObject(gdiPen);
    DeleteObject(gdiBrush);
    g.ReleaseHdc(hdc);
    g.Dispose();
}

Finally, I built up methods for drawing objects such as lines, rectangles, ellipses, etc.

C#
/// <summary>
/// Draws a rectangle with the pen and brush
/// that have been set by the user. Uses gdi32->Rectangle
/// </summary>
/// <param name="g">Graphics object. You can use CreateGraphics().</param>
/// <param name="p1">First corner of rectangle.</param>
/// <param name="p2">Second corner of rectangle.</param>
public void DrawRectangle(Graphics g, Point p1, Point p2)
{
    InitPenAndBrush(g);
    Rectangle(hdc, p1.X, p1.Y, p2.X, p2.Y);
    Dispose(g);
}

Using the Code

To use the code, just include it in your project and create a new GDI32 object.

C#
GDI32 gdi = new GDI32();

Then, you can use any of the public methods of the GDI32 class. All of them are documented, and are pretty easy to extend.

C#
gdi.DrawRectangle(CreateGraphics(), mouseDown, new Point(e.X, e.Y)); 
gdi.DrawRectangle(CreateGraphics(), _mouseDown, oldMouse); 
oldMouse = new Point(e.X, e.Y);

The above code would go in OnMouseMove(MouseEventArgs e), and will draw a black rectangle from points mouseDown to the new point provided by the MouseEventArgs.

nomoretrails.GIF

Points of Interest

Nothing super interesting or hardcore is going on here... I just found it to be an annoying issue, with many hacks available on the internet. I think this is a pretty clean way to do it, although it does force you to stray from GDI+.

A Rubber Rectangle

Here's what you need to build your own rubber rectangle with this class. You need to override the OnMouseDown, OnMouseMove, and OnMouseUp methods. When the mouse is first pressed, you need to store the original mouse position.

C#
protected override void OnMouseDown(MouseEventArgs e)
{
    base.OnMouseDown(e);
    if (e.Button == MouseButtons.Left)
        oldMouse = mouseDownAt = new Point(e.X, e.Y);
}

Then, you need to draw two rectangles. One that draws the visible border, and one that erases the previous border. This happens in OnMouseMove.

C#
protected override void OnMouseMove(MouseEventArgs e)
{
    base.OnMouseMove(e);
    if (e.Button == MouseButtons.Left)
    {
        gdi.DrawRectangle(CreateGraphics(), mouseDownAt, new Point(e.X, e.Y));
        gdi.DrawRectangle(CreateGraphics(), mouseDownAt, oldMouse);
        oldMouse = new Point(e.X, e.Y);
    } 
}

Finally, you need to erase the last rectangle when the mouse is lifted.

C#
protected override void OnMouseUp(MouseEventArgs e)
{
    base.OnMouseUp(e);
    if (e.Button == MouseButtons.Left)
    {
        gdi.DrawRectangle(CreateGraphics(), mouseDownAt, oldMouse);
    }
}

History

  • June 30, 2008 - First post.
  • June 30, 2008 - Added a demo project.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Engineer
Canada Canada
I'm currently a student at the University of British Columbia, where I am enrolled as an Electrical Engineer.

I randomly picked up a copy of Visual Studio C# 2008, and have been playing with it ever since. I probably have around 6 months of actual C# coding experience. I worked with some C++ and MFC prior to that. My first language was Hypertalk way back in the day of Hypercard and the Apple SE.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Manoj Kumar Choubey22-Feb-12 0:17
professionalManoj Kumar Choubey22-Feb-12 0:17 
QuestionWhy can't I change the PenColor ? Pin
sualkbn4-Feb-09 12:22
sualkbn4-Feb-09 12:22 
GeneralSetting a clip rectangle. Pin
Xfixium1-Jan-09 17:15
Xfixium1-Jan-09 17:15 
GeneralRe: Setting a clip rectangle. Pin
Giawa25-Feb-10 8:10
Giawa25-Feb-10 8:10 
QuestionIt does not work in Visual studio 2005. Why? Pin
Crazy4Program1-Dec-08 12:23
Crazy4Program1-Dec-08 12:23 
AnswerRe: It does not work in Visual studio 2005. Why? Pin
Giawa25-Feb-10 8:08
Giawa25-Feb-10 8:08 
AnswerRe: It does not work in Visual studio 2005. Why? Pin
Giawa25-Feb-10 14:58
Giawa25-Feb-10 14:58 
GeneralRe: It does not work in Visual studio 2005. Why? Pin
barrd15-Jan-14 8:51
professionalbarrd15-Jan-14 8:51 
GeneralXOR plotting Pin
supercat93-Jul-08 15:19
supercat93-Jul-08 15:19 
GeneralRe: XOR plotting Pin
Giawa3-Jul-08 19:51
Giawa3-Jul-08 19:51 
GeneralAnother way around Pin
Derek Bartram2-Jul-08 13:07
Derek Bartram2-Jul-08 13:07 
GeneralRe: Another way around Pin
Giawa2-Jul-08 20:45
Giawa2-Jul-08 20:45 
GeneralRe: Another way around Pin
Derek Bartram3-Jul-08 6:36
Derek Bartram3-Jul-08 6:36 
GeneralRe: Another way around Pin
Giawa3-Jul-08 8:40
Giawa3-Jul-08 8:40 
GeneralRe: Another way around Pin
Derek Bartram6-Jul-08 2:06
Derek Bartram6-Jul-08 2:06 
Questionwhy invent thing that exist? Pin
Marcin Cuprjak1-Jul-08 0:57
Marcin Cuprjak1-Jul-08 0:57 
AnswerRe: why invent thing that exist? Pin
Giawa1-Jul-08 9:00
Giawa1-Jul-08 9:00 
GeneralRe: why invent thing that exist? Pin
Marcin Cuprjak1-Jul-08 9:47
Marcin Cuprjak1-Jul-08 9:47 
GeneralRe: why invent thing that exist? Pin
Giawa1-Jul-08 10:15
Giawa1-Jul-08 10:15 
AnswerRe: why invent thing that exist? Pin
Southmountain20-May-22 13:54
Southmountain20-May-22 13:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.