copying free hand drawing from panel in visual studio 2013
Solution 1:
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
anQueues
) - 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..