How to draw line when mouse up? - c#

I am trying to set the starting point when the LMB is down and draw a line from the starting point to the current mouse position when the LMB is up, much like how MSPaint does it.
My problem is that I can't seem to get the line to appear on the picturebox when LMB is up. Can someone enlighten me please?
Edit:Sorry guys I realised the problem was elsewhere, but I learned a bunch of stuff in the process, thanks for all the input.
public partial class FormPaint : Form
{
Point? startPoint = Point.Empty;
Point? endPoint = Point.Empty;
bool isMouseDown = new Boolean();
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
if (Control.MouseButtons == MouseButtons.Left)
{
startPoint = e.Location;
isMouseDown = true;
}
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
brush = new SolidBrush(color);
using (Graphics g = Graphics.FromImage(pictureBox1.Image))
{
g.DrawLine(new Pen(brush), startPoint.Value, endPoint.Value);
pictureBox1.Invalidate();
}
isMouseDown = false;
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
endPoint = e.Location;
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
using (brush = new SolidBrush(color))
{
e.Graphics.DrawLine(new Pen(brush, 5), startPoint.Value, endPoint.Value);
}
}
}

When you call Invalidate it forces the picture box to repaint. The problem is though that it discards everything you painted before. Then it calls the Paint on the picture box.
I would suggest to save the drawing data into a list and perform the painting inside the Paint event of the picture box using that saved data.
Also read How do I draw a circle and line in the picturebox?

Complete sample with line draw preview, enjoy.
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 WinForm
{
public partial class frmMain : Form
{
/// <summary>
/// form constructor
/// </summary>
public frmMain()
{
InitializeComponent();
}
private PictureBox imgCanvas;
private bool isMouseDown;
private Point startPoint;
private Point currentPoint;
/// <summary>
/// form load
/// </summary>
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
imgCanvas = new PictureBox
{
Location = new Point(8, 8),
Size = new Size(this.ClientSize.Width - 16, this.ClientSize.Height - 16),
Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Bottom,
BorderStyle = BorderStyle.Fixed3D,
};
imgCanvas.MouseDown += imgCanvas_MouseDown;
imgCanvas.MouseUp += imgCanvas_MouseUp;
imgCanvas.MouseMove += imgCanvas_MouseMove;
imgCanvas.Paint += imgCanvas_Paint;
this.Controls.Add(imgCanvas);
}
void imgCanvas_Paint(object sender, PaintEventArgs e)
{
if (isMouseDown)
{
e.Graphics.DrawLine(Pens.Red, startPoint, currentPoint);
}
}
void imgCanvas_MouseMove(object sender, MouseEventArgs e)
{
if (isMouseDown)
{
currentPoint = e.Location;
(sender as PictureBox).Refresh();
}
}
void imgCanvas_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
isMouseDown = true;
startPoint = e.Location;
}
}
void imgCanvas_MouseUp(object sender, MouseEventArgs e)
{
if (isMouseDown)
{
if (e.Button == MouseButtons.Left)
{
isMouseDown = false;
PictureBox pb = sender as PictureBox;
// create image
if (pb.Image == null)
{
pb.Image = new Bitmap(pb.ClientSize.Width, pb.ClientSize.Height);
}
// draw
using (Graphics g = Graphics.FromImage(pb.Image))
{
g.DrawLine(Pens.Green, startPoint, e.Location);
pb.Refresh();
}
}
}
}
}
}
Result:

Related

How does it go to its first position if I don't drag the button where I want to drag it?

enter image description hereI have 9 buttons. Three are located above, 3 are located below them. All 3 buttons are on the right side. The antonyms of the words written on the buttons above are written on the buttons on the right. I would like to place the buttons with these antonyms on the buttons at the bottom. The buttons at the bottom are colored and their text is the same as the buttons on the right, but their text is not visible because it is the same as the button color. So the buttons on the bottom look like a colored box. That's what I want to do now; for example, button 1 = cold and button7 = hot. I want to place this button 7 on button 4 located below button 1, but if I want to place it on button 5 I want it to return to its original position. In the code I wrote, as soon as I press button7, it goes directly to button4, I can't try the other buttons. how can I try other buttons and return them to the first position in Visual studio form application?
enter code here
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 materyalll
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
startlocation = new Point(button7.Left, button7.Top);
startlocation2 = new Point(button8.Left, button8.Top);
startlocation3 = new Point(button9.Left, button9.Top);
}
Point location, location2, location3, startlocation, startlocation2, startlocation3;
private void button7_MouseDown(object sender, MouseEventArgs e)
{
location = e.Location;
}
private void button7_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
button7.Left += e.X - (location.X);
button7.Top += e.Y - (location.Y);
}
}
private void button7_MouseUp(object sender, MouseEventArgs e)
{
if (button7.Text == button4.Text)
button7.Location = button4.Location;
else if (button7.Text != button5.Text)
button7.Location = startlocation;
else if (button7.Text != button6.Text)
button7.Location = startlocation;
}
private void button8_MouseDown(object sender, MouseEventArgs e)
{
location2 = e.Location;
}
private void button8_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
button8.Left += e.X - (location2.X);
button8.Top += e.Y - (location2.Y);
}
}
private void button8_MouseUp(object sender, MouseEventArgs e)
{
if (button8.Text == button5.Text)
button8.Location = button5.Location;
else if (button8.Text != button4.Text)
button8.Location = startlocation2;
else if (button8.Text != button6.Text)
button8.Location = startlocation2;
}
private void button9_MouseDown(object sender, MouseEventArgs e)
{
location3 = e.Location;
}
private void button9_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
button9.Left += e.X - (location3.X);
button9.Top += e.Y - (location3.Y);
}
}
private void button9_MouseUp(object sender, MouseEventArgs e)
{
if (button9.Text == button6.Text)
button9.Location = button6.Location;
else if (button9.Text != button4.Text)
button9.Location = startlocation3;
else if (button9.Text != button5.Text)
button9.Location = startlocation3;
}
}
}
Store the original location of the Button in the .Tag property of the Button itself. When the user releases the mouse, see if the RECTANGLES of the correct target button and the current button INTERSECT. If they don't, snap back to the stored location in the Tag; otherwise snap to the location of the correct button.
Here it is with button7:
public Form1()
{
InitializeComponent();
button7.Tag = button7.Location;
button8.Tag = button8.Location;
button9.Tag = button9.Location;
}
private void button7_MouseUp(object sender, MouseEventArgs e)
{
if (button7.Bounds.IntersectsWith(button4.Bounds)) {
button7.Location = button4.Location;
}
else
{
button7.Location = (Point)button7.Tag;
}
}
The code for the other two buttons would be very similar.
Try something like this:
Dictionary<string, Point> originalPoints = new Dictionary<string, Point>();
originalPoints.Add(nameof(button1), new Point(button1.Left, button1.Top));
// repeat this for all buttons
void SetToOriginalPosition(Button button)
{
Point p = originalPoints[nameof(button)];
button.Left = p.X;
button.Top = p.Y;
}

Saving bitmap in a paint program

I am working on a MS paint like program that is programmed entirely in c#. It's very basic, but I have stumbled upon a problem. So I saw another SO post regarding MS paint mock ups. It was about how to save the end result as a .bmp file. I tried using the solutions and answers provided and it worked.
The file saved. However when it saved, it only saved the blank panel ( im making a forms app) . I have only seen one SO post that deals with this issue but I couldn't incorporate to allow the user to interact. The following is my code:
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 Paint
{
public partial class Form1 : Form
{
Graphics g;
Pen p = new Pen(Color.Black, 1);
Point sp = new Point(0, 0);
Point ep = new Point(0, 0);
int k = 0;
public Form1()
{
InitializeComponent();
}
private void pictureBox2_Click(object sender, EventArgs e)
{
p.Color = red.BackColor;
default1.BackColor = red.BackColor;
}
private void blue_Click(object sender, EventArgs e)
{
p.Color = blue.BackColor;
default1.BackColor = blue.BackColor;
}
private void green_Click(object sender, EventArgs e)
{
p.Color = green.BackColor;
default1.BackColor = green.BackColor;
}
private void panel2_MouseDown(object sender, MouseEventArgs e)
{
sp = e.Location;
if (e.Button == MouseButtons.Left)
k = 1;
}
private void panel2_MouseUp(object sender, MouseEventArgs e)
{
k = 0;
}
private void panel2_MouseMove(object sender, MouseEventArgs e)
{
if (k == 1)
{
ep = e.Location;
g = panel2.CreateGraphics();
g.DrawLine(p, sp, ep);
}
sp = ep;
}
private void panel2_MouseLeave(object sender, EventArgs e)
{
k = 0; }
private void panel2_Paint(object sender, PaintEventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
SaveFileDialog dialog = new SaveFileDialog();
if (dialog.ShowDialog() == DialogResult.OK)
{
int width = Convert.ToInt32(panel2.Width);
int height = Convert.ToInt32(panel2.Height);
Bitmap bmp = new Bitmap(width, height);
panel2.DrawToBitmap(bmp, new Rectangle(0, 0, width, height));
bmp.Save(dialog.FileName, System.Drawing.Imaging.ImageFormat.Bmp);
}
}
}
}
So my question is... How do I Succesfully save a .bmp image in my c# forms app , as in how do i not make it save blank. Thanks in advance :)
edit
So I have tried the first answer and also im trying the ideas suggested by the individual in the comments and some how, instead of just saving a blank canvas. the application literally just saves a black image. Here is the code I ended up with. Where did I go wrong?
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 Paint
{
public partial class Form1 : Form
{
Graphics g;
Graphics h;
Pen p = new Pen(Color.Black, 1);
Point sp = new Point(0, 0);
Point ep = new Point(0, 0);
int k = 0;
Bitmap bmp =null;
public Form1()
{
InitializeComponent();
}
private void pictureBox2_Click(object sender, EventArgs e)
{
p.Color = red.BackColor;
default1.BackColor = red.BackColor;
}
private void blue_Click(object sender, EventArgs e)
{
p.Color = blue.BackColor;
default1.BackColor = blue.BackColor;
}
private void green_Click(object sender, EventArgs e)
{
p.Color = green.BackColor;
default1.BackColor = green.BackColor;
}
private void panel2_MouseDown(object sender, MouseEventArgs e)
{
sp = e.Location;
if (e.Button == MouseButtons.Left)
k = 1;
}
private void panel2_MouseUp(object sender, MouseEventArgs e)
{
k = 0;
}
private void panel2_MouseMove(object sender, MouseEventArgs e)
{
if (k == 1)
{
ep = e.Location;
int width = Convert.ToInt32(panel2.Width);
int height = Convert.ToInt32(panel2.Height);
Bitmap bmp = new Bitmap(width, height);
g = Graphics.FromImage(bmp);
g.DrawLine(p, sp, ep);
h = panel2.CreateGraphics();
h.DrawLine(p, sp, ep);
}
sp = ep;
}
private void panel2_MouseLeave(object sender, EventArgs e)
{
k = 0; }
private void panel2_Paint(object sender, PaintEventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
SaveFileDialog dialog = new SaveFileDialog();
if (dialog.ShowDialog() == DialogResult.OK)
{
/*
Bitmap bmp = Bitmap.FromHbitmap(panel2.CreateGraphics().GetHdc());
panel2.DrawToBitmap(bmp, new Rectangle(0, 0, width, height));*/
int width = panel2.Width;
int height = Convert.ToInt32(panel2.Height);
if (bmp == null)
bmp = new Bitmap(width, height);
bmp.Save(dialog.FileName, System.Drawing.Imaging.ImageFormat.Jpeg);
}
}
}
}
Here is a revised version, pretty much what I told you in the comments..:
public partial class Form2 : Form
{
Graphics g;
Graphics h;
Pen p = new Pen(Color.Black, 1);
Point sp = new Point(0, 0);
Point ep = new Point(0, 0);
int k = 0;
Bitmap bmp =null;
public Form2()
{
InitializeComponent();
bmp = new Bitmap(panel2.ClientSize.Width, panel2.ClientSize.Height);
g = Graphics.FromImage(bmp);
g.Clear(panel2.BackColor);
}
private void pictureBox2_Click(object sender, EventArgs e)
{
p.Color = red.BackColor;
default1.BackColor = red.BackColor;
}
private void color_Click(object sender, EventArgs e)
{
Control ctl = sender as Control;
p.Color = ctl.BackColor;
default1.BackColor = ctl.BackColor;
}
private void panel2_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
ep = e.Location;
g.DrawLine(p, sp, ep);
h = panel2.CreateGraphics();
h.DrawLine(p, sp, ep);
}
sp = ep;
}
private void panel2_MouseDown(object sender, MouseEventArgs e)
{
sp = e.Location;
}
private void button1_Click(object sender, EventArgs e)
{
SaveFileDialog dialog = new SaveFileDialog();
if (dialog.ShowDialog() == DialogResult.OK)
{
bmp.Save(dialog.FileName, System.Drawing.Imaging.ImageFormat.Jpeg);
}
}
}
A few notes:
I mapped all the clicks of the palette control into one.
I have eliminated the flag k by doing the button test in the move.
I have kept the cached Graphics g; this is usually not recommended, but as we keep drawing into one and the same bitmap is is ok.
I have remove all duplicate declaration of the bitmap bmp.
I don't know what the picturebox does, so I didn't touch the code.
Drawbacks of the soution:
Since you don''t keep track of all the moves you can't do a good undo.
Since all lines are drawn separately you can't get good results with broader and/or semi-transparent Pens because the overlapping endpoints will look bad.
For a better solution of simple doodeling see here and after you have studied it you can tackle the even better solution, which will allow you to use all sort of drawing tools here..
Use Graphics.GetHdc Method and save it like this:
Bitmap bitMap = Bitmap.FromHbitmap(g.GetHdc());
bitMap.Save(dialog.FileName, System.Drawing.Imaging.ImageFormat.Bmp);

Why does the drawn line disappear when mouse button is released?

I've been playing around with System.Drawing and I've got it working the way that I wanted it to except for one thing. When I release the mouse button, the line disappears.
How do I ensure the line remains exactly where I left it?
using System;
using System.Drawing;
using System.Windows.Forms;
namespace DrawingSample
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.DoubleBuffered = true;
}
Graphics graphics;
Random
color = new Random(1);
Int32
penThickness = 1;
Point
currentCursorLocation, initialTouchLocation, touchOffset;
Boolean
mouseDown;
protected override void OnPaint(PaintEventArgs e)
{
graphics = e.Graphics;
if (mouseDown)
{
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
//DrawRectangle(graphics);
//DrawEllipse(graphics);
DrawLine(graphics);
}
}
private void DrawRectangle(Graphics graphics)
{
graphics.DrawRectangle(new Pen(
Color.FromArgb((color.Next(1,
255)),
(color.Next(1,
255)),
(color.Next(1,
255)))
,
penThickness),
currentCursorLocation.X,
currentCursorLocation.Y,
(this.Width / 2),
(this.Height / 2)
);
}
private void DrawEllipse(Graphics graphics)
{
graphics.DrawEllipse(new Pen(
Color.FromArgb((color.Next(1, 255)),
(color.Next(1, 255)),
(color.Next(1, 255))), penThickness),
new RectangleF(currentCursorLocation, new Size(100, 100)));
}
private void DrawLine(Graphics graphics)
{
graphics.DrawLine(new Pen(
Color.FromArgb((color.Next(1, 255)),
(color.Next(1, 255)),
(color.Next(1, 255))), penThickness),
currentCursorLocation.X,
currentCursorLocation.Y,
touchOffset.X,
touchOffset.Y
);
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
currentCursorLocation = e.Location;
this.Refresh();
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (!mouseDown)
{
touchOffset = e.Location;
mouseDown = true;
}
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
mouseDown = false;
}
}
}
You're asking the code to behave like that. So it does the same thing.
You set mouseDown to false in Form1_MouseUp event.
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
mouseDown = false; //<--Here
}
Then you are checking if (mouseDown) before painting the line.
protected override void OnPaint(PaintEventArgs e)
{
graphics = e.Graphics;
if (mouseDown)//<--Here
{
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
//DrawRectangle(graphics);
//DrawEllipse(graphics);
DrawLine(graphics);
}
}
I guess you don't need if (mouseDown) check in OnPaint method.
Not sure what your intention is, If you need more help drop a comment below the answer.
You probably want to keep a list of all drawn lines in a separate field to be able to redraw them when necessary. In other words, if you define a class like this:
class Line
{
public Point Start { get; set; }
public Point End { get; set; }
}
This allows you to have both a list of already drawn lines, and the currently drawn line (while mouse is held down):
// this is the line currently being drawn (i.e. a temporary line)
private Line _currentLine = null;
// this is the list of all finished lines
private readonly List<Line> _lines = new List<Line>();
Mouse handlers are now straightforward:
protected override void OnMouseDown(MouseEventArgs e)
{
// when button is pressed, create a new _currentLine instance
_currentLine = new Line() { Start = e.Location, End = e.Location };
Invalidate();
base.OnMouseDown(e);
}
protected override void OnMouseMove(MouseEventArgs e)
{
// when mouse is moved, update the End position
if (_currentLine != null)
{
_currentLine.End = e.Location;
Invalidate();
}
base.OnMouseMove(e);
}
protected override void OnMouseUp(MouseEventArgs e)
{
// when button is released, add the line to the list
if (_currentLine != null)
{
_lines.Add(_currentLine);
_currentLine = null;
Invalidate();
}
base.OnMouseUp(e);
}
And the OnPaint method would look something like this:
protected override void OnPaint(PaintEventArgs e)
{
// if you want smoother (anti-aliased) graphics, set these props
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
// draw all existing lines from the list
using (var p = new Pen(Color.Black, 2f))
foreach (var line in _lines)
e.Graphics.DrawLine(p, line.Start, line.End);
// if mouse is down, draw the dashed line also
if (_currentLine != null)
using (var p = new Pen(Color.Salmon, 2f))
{
p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
e.Graphics.DrawLine(p, _currentLine.Start, _currentLine.End);
}
base.OnPaint(e);
}
Also, there is much less flickering if you use the Form.SetStyles method in the constructor to indicate that you'll be doing all painting yourself and don't want Windows to clear the form by itself. This means that your Form's constructor should look something like:
public Form1()
{
InitializeComponent();
SetStyle(
ControlStyles.AllPaintingInWmPaint |
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw |
ControlStyles.UserPaint,
true);
}
Keeping the list of lines is much better than simply drawing them onto the surface, because it allows you to:
Keep the data format as vectors, instead of as a bitmap (allows high-res scaling),
Add editing functionality (i.e. select an existing line, move it, change its color, and similar stuff).

selected area to cut an image

Hi I would do the selected area to cut an image on picturebox control.
I have the following code:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private Rectangle rect;
private Pen p;
public Form1()
{
InitializeComponent();
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
if (this.p == null)
this.p = new Pen(Color.FromArgb(100, 200, 200, 200), 5);
if (this.rect.Width > 0)
e.Graphics.DrawRectangle(this.p, this.rect);
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
if (e.X < this.rect.X)
{
this.rect.Width = this.rect.X - e.X;
this.rect.X = e.X;
}
else
{
this.rect.Width = e.X - this.rect.X;
}
if (e.Y < this.rect.Y)
{
this.rect.Height = this.rect.Y - e.Y;
this.rect.Y = e.Y;
}
else
{
this.rect.Height = e.Y - this.rect.Y;
}
this.Invalidate(this.rect);
}
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
this.rect.X = e.X;
this.rect.Y = e.Y;
}
}
}
It returns an error here:
Application.Run(new Form1());
Why?
thanks for all replies ;p
You shouldn't dispose of the Graphics object that is passed as part of the PaintEventArgs. That is probably what is causing your issue.
Try using this optimized code, if you still get error post it here (edit your original question) and we'll see.
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
if (this.p == nulll)
this.p = new Pen(Color.FromArgb(100, 200, 200, 200), 5);
if (this.rect.Width > 0)
e.Graphics.DrawRectangle(this.p, this.rect);
}
what is the error?
you are leaking Pen's. For every paint message you create a new pen and throw the old without disposing.
Off the top of my head I can't remember if you should dispose of the graphics object you get from the event args

C# graphics flickering

I am working on kind of drawing program but I have a problem with flickering while moving a mouse cursor while drawing a rubberband line. I hope you can help me to remove that flickering of line, here is the code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace GraphicsTest
{
public partial class Form1 : Form
{
int xFirst, yFirst;
Bitmap bm = new Bitmap(1000, 1000);
Graphics bmG;
Graphics xG;
Pen pen = new Pen(Color.Black, 1);
bool draw = false;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
bmG = Graphics.FromImage(bm);
xG = this.CreateGraphics();
bmG.Clear(Color.White);
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
xFirst = e.X;
yFirst = e.Y;
draw = true;
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
bmG.DrawLine(pen, xFirst, yFirst, e.X, e.Y);
draw = false;
xG.DrawImage(bm, 0, 0);
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (draw)
{
xG.DrawImage(bm, 0, 0);
xG.DrawLine(pen, xFirst, yFirst, e.X, e.Y);
}
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
xG.DrawImage(bm, 0, 0);
}
}
}
First don't use CreateGraphics() unless you absolutely have to. Bind an event handler to OnPaint and call Invalidate() when you want to refresh the surface.
If you don't want it to flicker you'll need to double buffer your drawing surface. The easiest way to do this is to set your form's DoubleBuffered property to True.
I would highly recommend if you plan on extending this to do your drawing to the PictureBox control. PictureBox is double-buffered by default and allows you to control your drawing region much more simply.
In code:
public partial class Form1 : Form
{
int xFirst, yFirst;
Bitmap bm = new Bitmap(1000, 1000);
Graphics bmG;
Pen pen = new Pen(Color.Black, 1);
bool draw = false;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
bmG = Graphics.FromImage(bm);
bmG.Clear(Color.White);
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
xFirst = e.X;
yFirst = e.Y;
draw = true;
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
bmG.DrawLine(pen, xFirst, yFirst, e.X, e.Y);
draw = false;
Invalidate();
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (draw)
{
Invalidate();
}
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
if (draw) {
e.Graphics.DrawImage(bm, 0, 0);
e.Graphics.DrawLine(pen, xFirst, yFirst, e.X, e.Y);
} else {
e.Graphics.DrawImage(bm, 0, 0);
}
}
}
Edit:
Another issue, you are creating a private Pen member. Pens (and Brushes, as well as many GDI+ objects) represent handles to unmanaged objects that need to be disposed otherwise your program will leak. Either wrap them in using statements (the preferred and exception-safe way) or explicitly dispose of them in the form's Dispose method.
Alternatively in System.Drawing you can access some pre-built Pens and Brushes that don't need to be (and shouldn't be) disposed. Use them like:
private void Form1_Paint(object sender, PaintEventArgs e)
{
if (draw) {
e.Graphics.DrawImage(bm, 0, 0);
e.Graphics.DrawLine(Pens.Black, xFirst, yFirst, e.X, e.Y);
} else {
e.Graphics.DrawImage(bm, 0, 0);
}
}
The reason it is flickering is that you are drawing the background (which is immediately displayed on screen, wiping away the line) and then superimpose the line. Thus the line keeps disappearing and appearing, giving a flickering display.
The best solution to this is called Double Buffering. What you do is draw the whole image to an "offscreen" bitmap, and only show it on the screen when it is completed. Because you only ever display the completed image, there is no flickering effect. You should just be able to set this.DoubleBuffered = true to get WinForms to do all the hard work for you.
NB: You shouldn't really be drawing outside of your paint handler - ideally, you should Invalidate() the area that needs redrawing, and then your paint handler will redraw just that area (with any overlaid lines etc as needed).
Fixed and working code.
public partial class Form1 : Form
{
int x1, y1, x2, y2;
bool drag = false;
Bitmap bm = new Bitmap(1000, 1000);
Graphics bmg;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
bmg = Graphics.FromImage(bm);
}
private void pictureBox_MouseDown(object sender, MouseEventArgs e)
{
drag = true;
x1 = e.X;
y1 = e.Y;
}
private void pictureBox_MouseUp(object sender, MouseEventArgs e)
{
drag = false;
bmg.DrawLine(Pens.Black, x1, y1, e.X, e.Y);
pictureBox.Invalidate();
}
private void pictureBox_MouseMove(object sender, MouseEventArgs e)
{
if (drag)
{
x2 = e.X;
y2 = e.Y;
pictureBox.Invalidate();
}
}
private void pictureBox_Paint(object sender, PaintEventArgs e)
{
if (drag) {
e.Graphics.DrawImage(bm, 0, 0);
e.Graphics.DrawLine(Pens.Black, x1, y1, x2, y2);
}
else {
e.Graphics.DrawImage(bm, 0, 0);
}
}
}
I Use this to manage the double buffering into a panel:
myPanel.GetType().GetMethod("SetStyle",
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.NonPublic).Invoke(myPanel,
new object[]
{
System.Windows.Forms.ControlStyles.UserPaint |
System.Windows.Forms.ControlStyles.AllPaintingInWmPaint |
System.Windows.Forms.ControlStyles.DoubleBuffer, true
});

Categories