Flickerless Drag and Drop of Graphic Primitives Using .Net GDI


Introduction

This article shows you how dragging and dropping graphic primitives smoothly onto the screen without flicker is easily achieved using .Net's GDI. 

The hurdles to over come are as follows:

  1. How to select a shape on the screen.
  2. How to move a shape without having to re-draw all of the other shapes.
  3. How to avoid screen flicker as the object is moved.

Implementation

Using an object-oriented design almost always simplifies the coding.  We know we are going to have shapes that need to be drawn and that we will need something to manage these shapes.   So I created an IShape interface and a User Control to manage a collection of shape objects.  A concrete ShapeRectangular class inherits the IShape interface.  I've given the Shape object the responsibility of drawing its self and determining if the mouse cursor is over it.   It also has a property to indicate that it's in the process of being dragged.  If we are dealing with just rectangles the collision detection is already done for us mostly by using .Net's Rectangle's IntersectsWith method. Our ShapeRectangular class only needs to create a Rectangle object to define its dimension.  Anytime we want to test to see if the mouse cursor is over it then we just pass the mouse cursor's point to an instance of the ShapeRectangular and create a small rectangle where there mouse's point is and then pass this to the Rectangle's IntersectsWith method.  Listing 1 shows a couple of the significant methods in the Shape object.  Figure 1 shows the complete class diagram for the Shapes.  Note that A Shape class has also been created to contain the common properties and methods to avoid duplicating code.

Listing 1 - Methods in the ShapeRectangle class

/// <summary>

/// Draw the rectangle to the graphics

/// </summary>

/// <param name="pGraphics"></param>

void IShape.Draw(Graphics pGraphics)

{

          pGraphics.FillRectangle(_brush, _rectRegion);

}

 

/// <summary>

/// returns true if the mouse pointer is inside the shape

/// </summary>

/// <param name="pPoint"></param>

/// <returns></returns>

bool IShape.IsCollision(Point pPoint)

{

          // determine type of collision

          /*

          * For now just see if there is any kind of collision

          * with the mouse cursor

          */

          Rectangle lrectCursorRect=new Rectangle(pPoint,new Size(2,2));

          if (this._rectRegion.IntersectsWith(lrectCursorRect))

          {

                   this._cursor = Cursors.SizeAll;

                   return true;

          }

          return false;

}

Figure 1 - Class Diagram for Shapes

 

That doesn't leave that much for the User control to do.  Its main responsibility will be to manage the canvas.  To avoid flicker we'll use double buffering which means we will first draw our shapes onto a bitmap and then draw our bitmap to the control's surface.  This will easily allow us to avoid re-drawing all of the other shapes during the drag process as well.   We do this by when someone clicks the mouse to select the drag shape.  We will put code in the mouse click event to draw all the shapes not being dragged onto a persisted bitmap.  Then cloning the persisted bitmap will create a temporary bitmap on which we will draw the shape being dragged.  At the end of the method we'll draw this bitmap to the control's surface.  Then on the mouse move event we will just clone the persisted bitmap having all the shapes not being dragged and draw only the shape being dragged as the user moves the mouse.  The temporary bitmap will again be drawn to the control's surface at the end of the mouse move event.  Listing 2 shows some of the significant methods in the User Control.  Both lBitmapdrawingArea and lBitmapOriginalDrawingArea are persisted class variables.  The lBitmapOriginalDrawingArea is a blank canvas where as the lBitmapdrawingArea will always have the non-dragged shapes drawn on it.   The lBitMapTemp is a local method variable and is created by cloning the lBitmapdrawingArea and then drawing the Shapes being dragged onto it.  Figure 2 shows the class diagram for the User Control. 

Listing 2 - Code in the User Control to manage the Canvas

/// <summary>

/// On mouse down either switch a dragged shape to a static

/// shape

/// or change a static shape to a dragged shape.

/// </summary>

private void DragImages_MouseDown(object sender, MouseEventArgs e)

{

          int lX = e.X ;

          int lY = e.Y ;

          Point lpntMouseLocation = new Point(lX, lY);

          lBitmapdrawingArea =(Bitmap)lBitmapOriginalDrawingArea.Clone();

          Graphics lGraphics = Graphics.FromImage(lBitmapdrawingArea);

 

          //Draw out the non dragged shapes and non selected on

          // the Canvas to be saved;

          foreach (IShape pShape in this.lListShapes)

          {

                   /*

                   * If we are not a dragged shape but our mouse is contained in our

                   * shape then we want to become a dragged shape

                   */

                   if (!pShape.IsDragged && pShape.IsCollision(lpntMouseLocation))

                   {

                             Point lpntOffset = new Point(pShape.Location.X -

                                      lpntMouseLocation.X,pShape.Location.Y -

                                                   lpntMouseLocation.Y);

                             pShape.MouseOffset = lpntOffset;

                             pShape.IsDragged = true;

                             continue;

                   }

                   else

                   {

                             // we are just a static shape that needs to

                             //be drawn to the canvas

                             pShape.IsDragged = false;

                             pShape.Brush = Brushes.Red;

                             this.Cursor = this.DefaultCursor;

                             pShape.Draw(lGraphics);

                   }

          }

          lGraphics.Dispose();

          // Draw out the dragged shapes on the Cavas not to save;

          Bitmap lBitMapTemp = (Bitmap)lBitmapdrawingArea.Clone();

          lGraphics = Graphics.FromImage(lBitMapTemp);

          foreach (IShape pShape in this.lListShapes)

          {

                    if (pShape.IsDragged)

                    {

                             pShape.Location = lpntMouseLocation;

                             pShape.Brush = Brushes.Blue;

                             pShape.Draw(lGraphics);

                    }

          }

          lGraphics.Dispose();

          // draw the canvas to the control's surface

          Graphics lGraphicsForm = this.CreateGraphics();

          lGraphicsForm.DrawImage(lBitMapTemp, new Point(0, 0));

          lGraphicsForm.Dispose();

}

 

/// <summary>

/// Draw our dragged images as the user moves the mouse

/// Change the mouse cursor when over a static shape if nothing

/// is being dragged

/// </summary>

private void DragImages_MouseMove(object sender, MouseEventArgs e)

{

          Bitmap lBitmap = (Bitmap)lBitmapdrawingArea.Clone();

          Graphics lGraphics = Graphics.FromImage(lBitmap);

          int lX = e.X ;

          int lY = e.Y;

          Point lLocation = new Point(lX, lY);

          Cursor lCursor = null;

          foreach (IShape pShape in this.lListShapes)

          {

                   //if the mouse cursor is over a shpae then change it.

                   if (pShape.IsCollision(new Point(lX, lY)))

                   {

                             lCursor = pShape.GetCursor();

                   }

                   // if dragged then change its location

                   if (pShape.IsDragged)

                   {

                             pShape.Location = lLocation;

                             pShape.Draw(lGraphics);

                   }

          }

 

          // change the cursor if over a shape

          if (lCursor != null)

          {

                   this.Cursor = lCursor;

          }

          else

          {

                   this.Cursor = this.DefaultCursor;

          }

          lGraphics.Dispose();

 

          // draw the bitmap canvas to the control's surface

          Graphics lGraphicsForm = this.CreateGraphics();

          lGraphicsForm.DrawImage(lBitmap, new Point(0, 0));

          lGraphicsForm.Dispose();

} 

}

Figure 2 - User Control Class Diagram

Conclusion

This article shows how easy it is to do double buffering and implementing drag and drop with graphic primitives in .Net.  The sample code is written generically enough so that it could be extended to drag other shapes. For example for fun you could modify the code to drag and drop poker chips in a Black Jack game or maybe even something useful.

Next Recommended Readings