copying free hand drawing from panel in visual studio 2013

Looking at your code I’m afraid I have to say: This is all wrong.

Sorry to be so blunt, but you must never use control.CreateGraphics!!

  • The first thing to do is to throw away the Graphics objgraphics object.

It is (almost) always wrong to store a Graphics object!

Instead you have to use the one you get from the e.Graphics parameter in the Paint events of your controls.

Note that Graphics doesn’t contain any graphics, it is a tool used to draw onto an associated Bitmap or a control’s surface.

  • The next thing is to do is to understand about drawing freehand lines. Often one can see the code you have; but it is useless and only an example of how many stupid things you find in introductions. Forget it. It will always look like crap as the circles simply never look smooth and as soon as you move the mouse faster the pseudo-lines completely fall apart.

There is a nice method DrawCurve that will draw smooth lines. You feed it a Pen and an array of Points.

This is what we will use.

Let’s return to the basics: How to create a graphic? Now we know that you need to call DrawCurve in the Paint event:

e.Graphics.DrawCurve(somePen, somePointsArray);

This brings up the next questions:

  • what’s somePen
  • what’s somePointsArray

There is a hidden third question:

  • what about drawing more lines?

The first one is simple; you create a Pen with a stroke width of 5.5 pixels as

Pen somePen = new Pen(Color.Blue, 5.5f); 

If you want to you can give it a linestyle (dashes), too.

Now for the array: In its simplest form this is easy as well..

In the MouseMove event you store the current Location in a list of points. first we declare it at class level:

List<Point> currentLine = new List<Point>();

Then we start filling it as long as the left button is pressed:

private void panel1_MouseMove_1(object sender, MouseEventArgs e)
{
    if (e.Button == System.Windows.Forms.MouseButtons.Left)
    {
        currentLine.Add(e.Location);
        panel1.Invalidate();
    }  
}

Note the last line: Calling Invalidate on a control triggers the system to invoke the Paint event. It may look complicated but this is the only correct way as it guarantees that the very same drawing will also happen when some other reason makes it necessary.

We need to draw because we have changes the data that should be drawn. But there are many outside reasons, most notoriously the Minimize/maximize sequence that will clear the drawing and trigger the Paint event, too. So we need to cooperate with the way windows draws its controls! Only this way the graphics will persist.

Also note we don’t use an array as we don’t know how many Points we will need. Instead we use a List and later cast it to Array..

Lets code the Paint event for our simple case:

private void panel1_Paint(object sender, PaintEventArgs e)
{
   using (Pen somePen = new Pen(Color.Blue, 5.5f) )
     if (currentLine.Count > 1) e.Graphics.DrawCurve(yourPen , currentLine.ToArray());
}

Note that I have created the Pen in a using clause. This is a cheap and safe way to ensure that the Pen is disposed of properly.

Also note how we cast the the List to an Array!

The above code alone would work and allow you free-hand drawing a line.

But what about the next line? It should not connect to the first one so we can’t just add more points!

So we need not just one list of points but more than that, in fact a list of list of points is called for:

List<List<Point>> curves = new List<List<Point>>();

And we add the current curve to it whenever the mouse is released:

private void panel1_MouseUp(object sender, MouseEventArgs e)
{
    if (currentLine.Count > 1) curves.Add(currentLine.ToList());  // copy!!
    currentLine.Clear();
    panel1.Invalidate();
}

Note how I use a cast from List to List to enforce a copy or else only the reference would be assigned and then, in the next line cleared..

Again we trigger the Paint event as the final thing to do.

We now should change the Paint event to display all the lines, both the one currently being drawn and all the earlier ones..:

private void panel1_Paint(object sender, PaintEventArgs e)
{
    using (Pen somePen = new Pen(Color.Blue, 5.5f) )
    {
       if (currentLine.Count > 1) e.Graphics.DrawCurve(somePen, currentLine.ToArray());
       foreach (List<Point> lp in curves)
          if (lp.Count > 1) e.Graphics.DrawCurve(somePen, lp.ToArray());
    }
}

Now we are basically done with the free-hand drawing part.

So we return to your original question: How can you copy the drawing to a second Panel?

Well, you have stored everything in the curves data structure.

So you have two options: Either simply use the same data in the panel2_Paint event or if you need to copy and change the data to, maybe adapt to a different size..

SO is not a code writing service. So usually I shouldn’t give you much more hints that what I wrote in the above comments. But as this question comes up so often I wrote up the full code for a very basic doodle application..

Here are things still missing:

  • Saving the data, saving the drawing (Serialize, DrawToBitMap)
  • Drawing with varying pens & colors (create a drawAction class to store all you need)
  • Using other tools like Line or Rectangle (create a drawAction class)
  • Clearing the drawing (see below)
  • Undo and Redo (look into Stacks an Queues)
  • Caching when there are a great number of lines ( draw the first portion into a BackgroundImage Bitmap)

Here is a clearing code:

curves.Clear(); currentLine .Clear(); panel1.Invalidate();

I noted that your original code lets you draw with two different stroke widths using the left and right button. This alone shows that this code is not very good. Who would a) think of that and b) be satisfied with only two stroke widths..

Please read this post where I explain a little about creating a class that can store a pen width, a color etc so that you can change then between lines you draw..

Leave a Comment