Beginner coder here getting into C#.. I'm making a program that involves drawing. Basically whenever I'm moving my mouse to draw, the actual line on the image appears delayed - and it's more.. straight, than it's supposed to be. It used to work fine, but at some point I guess something went wrong - can't remember what I did at the time so it's hard to retrace.. I tried to replicate just the drawing part of the program in a new solution, and it seems to work fine..
I'd post the .exe file so you can see what I mean but I'm not sure if we're allowed to post executables around here.
Edit: I've confirmed that the code works fine, look at the answer by sa_ddam213 for an example of the code. It seems as though it works fine in other peoples computers, so I'm completely confused.
Yo are creating a new Graphic and Pen object with every mouse move event, this will be a lot slower than creating these variables once in the Mouse_Down event.
Something like this may be a bit faster.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
b = new Bitmap(this.Width, this.Height);
}
private Graphics _graphics;
private Pen _pen;
private int pX = 0;
private int pY = 0;
private bool paint = false;
private Bitmap b;
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
pX = e.X;
pY = e.Y;
_graphics = Graphics.FromImage(b);
_pen= new Pen(Color.Black, 3);
paint = true;
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (paint)
{
_graphics.DrawLine(_pen, pX, pY, e.X, e.Y);
pictureBox1.BackgroundImage = b;
pictureBox1.Refresh();
pX = e.X;
pY = e.Y;
}
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
paint = false;
_graphics.Dispose();
_pen.Dispose();
}
}
Instead of pictureBox1.Invalidate(), try using pictureBox1.Refresh()
You might also need to move it AFTER the pictureBox1.BackgroundImage = b
Also, in your MouseDown, you need to set this.Capture = true, and in your MouseUp you should set this.Capture = false. If you don't do that and you release the mouse button while your mouse cursor is over a different application, your's will never receive the MouseUp message.
May be It's a problem with your VGA . Check with another PC and let us know
Related
I want to draw free hand in a form (picture box) on visual studio and copy the same figure (that I draw) on another panel/picture box.
Also they should not be dots forming one line but a continuous line. Please help.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
Pen p_white;
bool draw = true;
private Graphics objgraphics;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
Pen p_black = new Pen(new SolidBrush(Color.Black));
if (draw)
{
objgraphics = panel1.CreateGraphics();
}
}
/*private void panel1_MouseDown(object sender, MouseEventArgs e)
{
bool draw = true;
}*/
private void panel1_MouseMove_1(object sender, MouseEventArgs e)
{
Rectangle rEllipse = new Rectangle();
switch (e.Button)
{
case MouseButtons.Left:
rEllipse.X = e.X;
rEllipse.Y = e.Y;
rEllipse.Width = 5;
rEllipse.Height = 5;
objgraphics.DrawEllipse(System.Drawing.Pens.Black, rEllipse);
break;
case MouseButtons.Right:
rEllipse.X = e.X;
rEllipse.Y = e.Y;
rEllipse.Width = 3;
rEllipse.Height = 3;
objgraphics.DrawEllipse(System.Drawing.Pens.Black, rEllipse);
break;
default:
return;
}
}
/*private void panel1_MouseUp(object sender, MouseEventArgs e)
{
bool draw = false;
} */
private void form_Paint(object sender, EventArgs e)
{
}
private void panel2_Paint(object sender, PaintEventArgs e)
{
Pen p_black = new Pen(new SolidBrush(Color.Black));
if (draw)
{ objgraphics = panel1.CreateGraphics();
}
}
private void button2_Click(object sender, EventArgs e)
{
this.Close();
}
}
}
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.
Let's 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 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 lists 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.
Here are the 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.
My problem is that within my Windows Form Application, I want to draw an Ellipse everytime the mouse is clicked within a specific picture box, and I want the previously drawn ellipses to remain present in the picture box.
In its current state, once the mouse is clicked, the previously drawn ellipse will be replaced with the new one drawn at the cursor's new location.
Ball.Paint draws an ellipse.
Here is the relevant code to the problem:
private Ball b;
private void pbField_Paint(object sender, PaintEventArgs e)
{
if (b != null)
b.Paint(e.Graphics);
}
private void pbField_MouseClick(object sender, MouseEventArgs e)
{
int width = 10;
b = new Ball(new Point(e.X - width / 2, e.Y - width / 2), width);
Refresh();
}
If there is any more needed code or information I am able to provide it.
You need some sort of data structure to store prior ellipses. One possible solution is below:
private List<Ball> balls = new List<Ball>(); // Style Note: Don't do this, initialize in the constructor. I know it's legal, but it can cause issues with some code analysis tools.
private void pbField_Paint(object sender, PaintEventArgs e)
{
if (b != null)
{
foreach(Ball b in balls)
{
b.Paint(e.Graphics);
}
}
}
private void pbField_MouseClick(object sender, MouseEventArgs e)
{
int width = 10;
b = new Ball(new Point(e.X - width / 2, e.Y - width / 2), width);
balls.Add(b);
Refresh();
}
If you want more than one ball to be painted, you need to keep track of a list of balls, rather than just b. Every time the control is refreshed, it is expected to redraw all its contents. That means that in pbField_Paint, you need to be ready to draw as many balls as have been added to the scene.
I have make a playbar for my new MP3 player but I have a problem as shown in attachement
and this is my simple code :
public void setpercent(int percent)
{
pic1.Width = pic2.Width / 100 * percent;
pic3.Left = pic1.Width - ( pic3.Width /2);
}
I thought to use Graphics but I can't move it after.
and the method of ".Parent" doesn't work in this case.
In the Load() event of your Form, modify the Region() property of pic3 so that it becomes circular instead of rectangular. This can be done by construction a GraphicsPath and adding an Ellipse to it. If this doesn't line up properly with your image, then manually determine the correct bounding box for the ellipse and use that instead:
private void Form1_Load(object sender, EventArgs e)
{
System.Drawing.Drawing2D.GraphicsPath GP = new System.Drawing.Drawing2D.GraphicsPath();
GP.AddEllipse(pic3.ClientRectangle);
pic3.Region = new Region(GP);
}
I am looking to draw an ellipse on the screen during the MouseMove() event but am wanting it to only draw the most recent ellipse from a start point to the current mouse position. At the moment it is drawing an ellipse for every mouse position that is getting registered. I would be able to draw it easily enough without showing the ellipse if i simply used the MouseDown() and MouseUp() events, but I am wanting the user to be able to see the ellipse as they move the mouse around, so they can know exactly where they are placing it. Does anyone know how I could achieve this?
My current code is as follows:
private void pnlDraw_MouseDown(object sender, MouseEventArgs e)
{
initialX = e.X;
initialY = e.Y;
previousX = e.X;
previousY = e.Y;
isPainting = true;
}
private void pnlDraw_MouseMove(object sender, MouseEventArgs e)
{
if (isPainting)
{
switch (currentDrawType)
{
case DRAWTYPE.ELLIPSE:
{
DrawEllipse(e);
break;
}
default:
{
break;
}
}
}
}
private void DrawEllipse(MouseEventArgs e)
{
pen = new Pen(Color.Black);
Graphics graphics = pnlDraw.CreateGraphics();
graphics.DrawEllipse(pen, initialX, initialY, e.X - initialX, e.Y - initialY);
previousX = e.X;
previousY = e.Y;
graphics.Dispose();
}
Any help would be greatly appreciated!
The first thing you should do when you are drawing something on screen is to clear what was previously displayed. Otherwise, you simply draw on top of existing drawing.
In WinForm, it's normally handled in the OnBackgroundPaint() method, which is a simple way of saying that it's there you should clear the background.
It should looks like this:
e.Graphics.FillRectangle(new SolidBrush(MyBackgroundColor), e.ClipRectangle);
or if it's not a drawing event:
graphics.FillRectangle(new SolidBrush(MyBackgroundColor), 0, 0, MyWidth, MyHeight);
And should be called before drawing anything on top of it.
It is also a guaranty that your drawing zone is ready to be drawn on it.
How can I clear the fill of a rectangle? I only want to keep the border.
g.FillRectangle(Brushes.Transparent, x, y, w, h);
Didn't work, neither did aRGB with alpha, I want to delete the fill so there's only the border left.
So what you want is
g.DrawRectangle(Pens.Black,x,y,w,h);
I think
EDIT: due to a change in the OP requirements this is not exactly the answer he wants, though it is not incorrect, therefore I choose to leave it here, for now.
you must set new clip for your graphics after set clip clear it, then restore clip to normal.
g.SetClip(new Rectangle(x,y,w,h), CombineMode.Replace);
g.Clear(Color.Transparent);
Ok so you are after a selection tool, you might have wanted to tell us that in the first place.
Create a new windows form application.
in the form events use mousedown, mouseup and mousemove
public Point MouseXY = new Point(0, 0);
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
MouseXY = e.Location;
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
int width = e.Location.X - MouseXY.X;
int height = e.Location.Y-MouseXY.Y;
this.Refresh();
CreateGraphics().DrawRectangle(Pens.Blue, new Rectangle(MouseXY, new Size(width,height)));
}
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
this.Refresh();
}
This code is not perfect and I don't pretend it is. What this will do is draw a blue rectangle that starts where you click and follows your mouse. It does not draw a negative rectangle, you would have to determine whether your mouse is currently to the left or up from your starting point then draw the rectangle accordingly, but I think you can figure that out on your own. as well the rectangle is not persistent, though I do not believe you would want it to be.