I'm trying to preview and resize shapes (just like you would do in Paint) before drawing them. The problem I encountered is that even though I'm using double buffering, I still get horrible flickering, which is a logical result of calling Invalidate() for the region containing the temporary element.
Another issue is that previewing lines using this method causes graphical residue to build up.
The shape also disappears when I stop moving the mouse, with the left mouse button still held down.
PaintForm.cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
namespace MyPaint
{
public partial class PaintForm : Form
{
// Where user clicks the first time.
private Point start = Point.Empty;
// Where user releases the mouse button.
private Point end = Point.Empty;
private DrawingElementType currentElementType = DrawingElementType.Line;
private List<DrawingElement> drawingElements;
// Used for double buffering (aka first drawing on a bitmap then drawing the bitmap onto the control).
private Bitmap bmp;
public PaintForm()
{
InitializeComponent();
drawingElements = new List<DrawingElement>();
bmp = new Bitmap(pbCanvas.Width, pbCanvas.Height, PixelFormat.Format24bppRgb);
Graphics.FromImage(bmp).Clear(Color.White);
}
private void pbCanvas_Paint(object sender, PaintEventArgs e)
{
if (bmp != null)
{
bmp.Dispose();
bmp = null;
}
bmp = new Bitmap(pbCanvas.Width, pbCanvas.Height, PixelFormat.Format24bppRgb);
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(Color.White);
g.SmoothingMode = SmoothingMode.AntiAlias;
foreach (var elem in drawingElements)
{
switch (elem.Type)
{
case DrawingElementType.Line:
g.DrawLine(new Pen(Color.BlueViolet), elem.Start, elem.End);
break;
case DrawingElementType.Rectangle:
g.FillRectangle(Brushes.Black, elem.Container);
break;
case DrawingElementType.Ellipse:
g.FillEllipse(Brushes.Bisque, elem.Container);
break;
default:
break;
}
}
}
e.Graphics.DrawImageUnscaled(bmp, new Point(0, 0));
}
private void pbCanvas_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
start = e.Location;
}
}
private void pbCanvas_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
var temp = new DrawingElement(currentElementType, start, e.Location);
using (Graphics g = pbCanvas.CreateGraphics())
{
switch (currentElementType)
{
case DrawingElementType.Line:
g.DrawLine(new Pen(Color.BlueViolet), temp.Start, temp.End);
break;
case DrawingElementType.Rectangle:
g.FillRectangle(Brushes.Black, temp.Container);
break;
case DrawingElementType.Ellipse:
g.FillEllipse(Brushes.Bisque, temp.Container);
break;
default:
break;
}
}
pbCanvas.Invalidate(temp.Container);
}
}
private void pbCanvas_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
end = e.Location;
var elem = new DrawingElement(currentElementType, start, end);
drawingElements.Add(elem);
// This triggers the paint event and only repaints the region of the new element.
pbCanvas.Invalidate(elem.Container);
}
}
private void btnLine_Click(object sender, EventArgs e)
{
currentElementType = DrawingElementType.Line;
}
private void btnEllipse_Click(object sender, EventArgs e)
{
currentElementType = DrawingElementType.Ellipse;
}
private void btnlRectangle_Click(object sender, EventArgs e)
{
currentElementType = DrawingElementType.Rectangle;
}
}
}
DrawingElement.cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyPaint
{
enum DrawingElementType
{
Line,
Rectangle,
Ellipse
}
class DrawingElement
{
public Point Start { get; set; }
public Point End { get; set; }
public DrawingElementType Type { get; }
// Returns the region of the control that contains the element.
public Rectangle Container
{
get
{
int width = Math.Abs(End.X - Start.X);
int height = Math.Abs(End.Y - Start.Y);
return new Rectangle(Math.Min(Start.X, End.X), Math.Min(Start.Y, End.Y), width, height);
}
}
public DrawingElement(DrawingElementType type, Point start, Point end)
{
Type = type;
Start = start;
End = end;
}
}
}
I probably fried my brain trying to find workarounds.
Preview of the application
Related
I recently started programming in C# obviously and was trying to do a simple WinForms app that takes mouse coordinates and scales a Rectangle according to the coordinates.
The issue I am facing is that I don't know how to call a method that uses more arguments (in this case is x, y and PaintEventArgs). Or, I do know what to do with the PaintEvent.
Here is the whole code, since its pretty short and rather simple:
using System;
using System.Drawing;
using System.Windows.Forms;
public partial class Form1 : Form
{
public void Form1_MouseMove(object sender, MouseEventArgs e)
{
int x = e.X;
int y = e.Y;
String data = (x.ToString() + " " + y.ToString());
DrawRect(Something, x, y);
}
PaintEventArgs pEventArgs;
private void Form1_Paint(object sender, PaintEventArgs e)
{
}
public void DrawRect(PaintEventArgs e, int rey, int rex)
{
Graphics gr = e.Graphics;
Pen pen = new Pen(Color.Azure, 4);
Rectangle rect = new Rectangle(0, 0, rex, rey);
gr.DrawRectangle(pen, rect);
}
}
I'm trying to call the DrawRect() method to draw the Rectangle with width and height according to the mouse coordinates.
So how can I call the DrawRect() with coordinates and PaintEventArgs?
When drawing on a Control's surface, you always use the Paint event of that Control or override the OnPaint method of a Custom/User Control.
Do not try to store its Graphics object: it becomes invalid as soon as the Control is invalidated (repainted).
Use the Graphics object provided by the PaintEventArgs object.
When a more complex procedure is required to draw different shapes, you can pass the e.Graphics object to different methods which will use this object to perform specialized drawings.
In the example, the coordinates and other properties of each drawn shape are assigned to a specialized class, DrawingRectangle (a simplified structure here, it can hold more complex functionalities).
A List<DrawingRectangle>() stores the references of all the DrawingRectangle objects generated in a session (until the drawing is cleared).
Each time a Left MouseDown event is generated on the Control's surface, a new DrawingRectangle object is added to the List.
The e.Location is stored both as the DrawingRectangle.StartPoint (a value that doesn't change) and the DrawingRectangle.Location: this value will be updated when the mouse pointer is moved.
When the Mouse is moved, the current e.Location value is subtracted from the starting point coordinates previously stored. A simple calculation allows to draw the shapes from all sides.
This measure determines the current Size of the Rectangle.
To remove a Rectangle from the drawing, you just need to remove its reference from the List and Invalidate() the Control that provides the drawing surface.
To clear the drawing surface, clear the List<DrawingRectangle>() (drawingRects.Clear()) and call Invalidate().
Some other examples here:
Transparent Overlapping Circular Progress Bars
GraphicsPath and Matrix classes
Connecting different shapes
Drawing Transparent/Translucent Custom Controls
// Assign the Color used to draw the border of a shape to this Field
Color SelectedColor = Color.LightGreen;
List<DrawingRectangle> drawingRects = new List<DrawingRectangle>();
public class DrawingRectangle
{
public Rectangle Rect => new Rectangle(Location, Size);
public Size Size { get; set; }
public Point Location { get; set; }
public Control Owner { get; set; }
public Point StartPosition { get; set; }
public Color DrawingcColor { get; set; } = Color.LightGreen;
public float PenSize { get; set; } = 3f;
}
private void form1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
DrawingRects.Add(new DrawingRectangle() {
Location = e.Location,
Size = Size.Empty,
StartPosition = e.Location,
Owner = (Control)sender,
DrawingcColor = SelectedColor // <= Shape's Border Color
});
}
private void form1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
var dr = DrawingRects[DrawingRects.Count - 1];
if (e.Y < dr.StartPosition.Y) { dr.Location = new Point(dr.Rect.Location.X, e.Y); }
if (e.X < dr.StartPosition.X) { dr.Location = new Point(e.X, dr.Rect.Location.Y); }
dr.Size = new Size(Math.Abs(dr.StartPosition.X - e.X), Math.Abs(dr.StartPosition.Y - e.Y));
this.Invalidate();
}
private void form1_MouseUp(object sender, MouseEventArgs e)
{
// The last drawn shape
var dr = DrawingRects.Last();
// ListBox used to present the shape coordinates
lstPoints.Items.Add($"{dr.Location}, {dr.Size}");
}
private void form1_Paint(object sender, PaintEventArgs e)
{
DrawShapes(e.Graphics);
}
private void DrawShapes(Graphics g)
{
if (DrawingRects.Count == 0) return;
g.SmoothingMode = SmoothingMode.AntiAlias;
foreach (var dr in DrawingRects) {
using (Pen pen = new Pen(dr.DrawingcColor, dr.PenSize)) {
g.DrawRectangle(pen, dr.Rect);
};
}
}
// A Button used to save the current drawing to a Bitmap
private void btnSave_Click(object sender, EventArgs e)
{
using (var bitmap = new Bitmap(panCanvas.ClientRectangle.Width, panCanvas.ClientRectangle.Height))
using (var g = Graphics.FromImage(bitmap)) {
DrawShapes(g);
bitmap.Save(#"[Image Path]", ImageFormat.Png);
// Clone the Bitmap to show a thumbnail
}
}
// A Button used to clear the current drawing
private void btnClear_Click(object sender, EventArgs e)
{
drawingRects.Clear();
this.Invalidate();
}
app that takes mouse coordinates and scales rectangle according to the coordinates
I'd expect to see something like this (pseudocode):
Point _point;
void Form1_MouseMove(object sender, MouseEventArgs e)
{
... // calculate new coordinates/scale factor/whatever here
_point = ... ; // store results in fields
Invalidate(); // this will cause repaint every time you move mouse
}
void Form1_Paint(object sender, PaintEventArgs e)
{
... // take values from fields
e.Graphics.DrawRectangle(pen, rect); // draw
}
It's pretty simply. Painting is a combination of Invalidate() calls, which rise up paint event. The variables you pass using fields.
The PaintEventArgs allows you to access to the Graphics object, you need that one to draw something.
If you don't want to use the PaintEventArgs, i suggest that you call the CreateGraphics() method of your Form, and it will allow you to draw the rectangle.
To improve performance, i suggest that you use the using(...){ } keywork in order to dispose the Graphics object and the Pen object.
You need to include System.Drawing in order to use Graphics and Pen.
You're code will look like that :
using System.Drawing;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApp2
{
public partial class Form1 : Form
{
Point _coordinates;
public Form1()
{
this._coordinates = new Point();
this.InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
public void Form1_MouseMove(object sender, MouseEventArgs e)
{
this._coordinates = new Point(e.X, e.Y);
this.Invalidate();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
// Don't draw on first Paint event
if(this._coordinates.X != 0 && this._coordinates.Y != 0)
{
this.DrawRect(e);
}
}
public void DrawRect(PaintEventArgs e)
{
using (Pen pen = new Pen(Color.Azure, 4))
{
Rectangle rect = new Rectangle(0, 0, this._coordinates.X, this._coordinates.Y);
e.Graphics.DrawRectangle(pen, rect);
}
}
}
}
Drawing in WinForms application works in a slightly different way then you probably expect. Everything on screen now is considered to be temporary, if you e.g. minimize and restore your window, the onscreen stuff will be erased and you'll be asked to repaint it again (your window's Paint event will be fired by the system).
That's why that DrawRect method expects PaintEventArgs argument: it is supposed to be called only withing your Paint event handler. If you call it from outside (like it is suggested in other answer) your rectangles might behave inconsistently.
I would suggest remember your rectangles in some internal variable and then repaint them when asked for that by the system:
private Point pointToDrawRect = new Point(0,0);
public void Form1_MouseMove(object sender, MouseEventArgs e)
{
int x = e.X;
int y = e.Y;
String data = (x.ToString() + " " + y.ToString());
pointToDrawRect= new Point(x, y);
Invalidate();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
if(pointToDrawRect.X != 0 || pointToDrawRect.Y != 0)
{
DrawRect(e, pointToDrawRect.X, pointToDrawRect.Y);
}
}
public void DrawRect(PaintEventArgs e, int rey, int rex)
{
using (Pen pen = new Pen(Color.Azure, 4))
{
Rectangle rect = new Rectangle(0, 0, rex, rey);
e.Graphics.DrawRectangle(pen, rect);
}
}
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);
I have made a simple app in c# that lets me draw a rectangle and than move it by mouse. Now I want to draw multiple rectangles I also add them to a list, this also works, but I want to be able to move each rectangle by itself. This goes wrong. I can only move the first rectangle I created. If I try to move the other rectangle the first rectangle teleports to my mouse but only if I click on the second rectangle, if i click anywhere else it crashes with a nullpointer (I know how to fix this but its not the problem) What I cant figure out is why cant I move the second rectangle
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
namespace TekenTest
{
public partial class Form1 : Form
{
bool isMouseDown;
List<Item> _Items;
Item i;
public Form1()
{
InitializeComponent();
_Items = new List<Item>();
isMouseDown = false;
}
private void tekenVel_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
foreach (Item i in this._Items)
{
i.drawItem(g);
}
}
private void tekenVel_MouseDown(object sender, MouseEventArgs e)
{
this.i = _Items.Find(Item => ((i.X <= e.X && (i.WIDTH + i.X) >= e.X) &&
(i.Y <= e.Y && (i.HEIGTH + i.Y) >= e.Y)));
i.note = Color.Azure;
isMouseDown = true;
}
private void tekenVel_MouseMove(object sender, MouseEventArgs e)
{
if (isMouseDown == true)
{
i.X = e.X;
i.Y = e.Y;
Refresh();
}
}
private void tekenVel_MouseUp(object sender, MouseEventArgs e)
{
isMouseDown = false;
}
private void itemToolStripMenuItem_Click(object sender, EventArgs e)
{
this.i = new Item();
this._Items.Add(i);
this.Refresh();
}
}
}
Object class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TekenTest
{
class Object
{
public int X
{
get;
set;
}
public int Y
{
get;
set;
}
public int HEIGTH
{
get;
set;
}
public int WIDTH
{
get;
set;
}
public Object()
{
}
}
}
Item class
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TekenTest
{
class Item : Object
{
public Rectangle rect;
public String text;
public Font font;
public Brush textb;
public Color note;
public Item()
{
this.X = 200;
this.Y = 200;
this.WIDTH = 200;
this.HEIGTH = 200;
font = new Font("Arial", 12, FontStyle.Bold, GraphicsUnit.Point);
text = "Ik ben tekst";
note = Color.Yellow;
textb = Brushes.Black;
}
public void drawItem(Graphics g)
{
this.rect = new Rectangle(X, Y, WIDTH, HEIGTH);
// g.DrawRectangle(new)
g.FillRectangle(new SolidBrush(note), rect);
g.DrawString(text, font, textb, rect);
}
}
}
I would suggest a different approach. I would not make the Item a class variable or a property. Every control has a Property named Tag of type object. It can be used for any purposes. What I would typically do is to upon creation set the item as the tag of the represented control. Then In the Move triggers I would handle extract the Item from the Tag property, cast it from object and then manipulate it directly via values.
private void itemToolStripMenuItem_Click(object sender, EventArgs e)
{
var i = ((Control)sender.Tag) as Item;
this.i = new Item();
this._Items.Add(i);
// you dynamically create a control and set the Tag property
someControl.Tag = i;
this.Refresh();
}
private void tekenVel_MouseDown(object sender, MouseEventArgs e)
{
var i = ((Control)sender.Tag) as Item;
if(i!=null)
{
i.note = Color.Azure;
isMouseDown = true;
}
}
private void tekenVel_MouseMove(object sender, MouseEventArgs e)
{
if (isMouseDown == true)
{
i.X = e.X;
i.Y = e.Y;
Refresh();
}
}
You have probably the problem that the instance is not being found by the find method and you are trying to manipulate a null object. This way this is always going to work on a specific object and you you do not have to search for it. It cleans up the code and it runs much smoother.
EDIT
Also, I would Suggest renaming your class from Object to something else. just so it does not get confused with the .NET object root type
Problem can be because of in function tekenVel_Paint in foreach loop you are using name for Item the same as for instance variable i. Change it to other like here:
private void tekenVel_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
foreach (Item obj in _Items)
{
obj.drawItem(g);
}
}
And similar problem in tekenVel_MouseDown. You also should change name in conditional statement in function Find.
private void tekenVel_MouseDown(object sender, MouseEventArgs e)
{
this.i = _Items.Find(item => ((item.X <= e.X && (item.WIDTH + item.X) >= e.X) &&
(item.Y <= e.Y && (item.HEIGTH + item.Y) >= e.Y)));
i.note = Color.Azure;
isMouseDown = true;
}
Also you don't need isMouseDown variable. You can check if mouse button is pressed using MouseEventArgs e in tekenVel_MouseMove function.
On MouseUp event you should set usual color for your active item i and set it to null. And check in OnMouseMove event if i is not null in case if user click inside your control but not inside any object.
I fixed by changing my List searching method. I now the for each loop isnt the best way to go so I will change that later:
private Item selectItem(MouseEventArgs e)
{
IEnumerable<Item> itemQuerry =
from it in _Items
where it.X <= e.X && it.WIDTH + it.X >= e.X && it.Y <= e.Y && it.HEIGTH + it.Y >= e.Y
select it;
foreach (Item foundItem in itemQuerry)
{
this.mItem = foundItem;
}
mItem.note = Color.Azure;
return mItem;
}
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:
Is it possible to draw lines and figures like shown below in GDI+ and c#?
May be there is some way to do that in easy way in c#?
Update:
I mean that I need to imitate Hand-Drawing effect in GDI+
I would like to write something like:
graphics.DrawHandDrawnLine(Pens.Black, x1, y1, x2, y2);
and see something like this
I believe this will be hard to top in the 'more easy' department..:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing.Imaging;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
namespace Doodle
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
List<Point> curPoints = new List<Point>();
List<List<Point>> allPoints = new List<List<Point>>();
private void pnlPaint_MouseDown(object sender, MouseEventArgs e)
{
if (curPoints.Count > 1)
{
// begin fresh line
curPoints.Clear();
// startpoint
curPoints.Add(e.Location);
}
}
private void pnlPaint_MouseUp(object sender, MouseEventArgs e)
{
if (curPoints.Count > 1)
{
// ToList creates a copy
allPoints.Add(curPoints.ToList());
curPoints.Clear();
}
}
private void pnlPaint_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
// here we should check if the distance is more than a minimum!
curPoints.Add(e.Location);
// let it show
pnlPaint.Invalidate();
}
private void pnlPaint_Paint(object sender, PaintEventArgs e)
{
using (Pen pen = new Pen(Color.Black, 3f))
{
// regular edges:
pen.MiterLimit = 1.5f
// current lines
if (curPoints.Count > 1) e.Graphics.DrawCurve(pen, curPoints.ToArray());
// other lines
foreach (List<Point> points in allPoints)
if (points.Count > 1) e.Graphics.DrawCurve(pen, points.ToArray());
}
}}
private void btn_undo_Click(object sender, EventArgs e)
{
if (allPoints.Count > 0)
{
allPoints.RemoveAt(allPoints.Count - 1);
pnlPaint.Invalidate();
}
}
private void btn_save_Click(object sender, EventArgs e)
{
string fileName = #"d:\sketch.png";
Bitmap bmp = new Bitmap(pnlPaint.ClientSize.Width, pnlPaint.ClientSize.Width);
pnlPaint.DrawToBitmap(bmp, pnlPaint.ClientRectangle);
bmp.Save(fileName, ImageFormat.Png);
}
}
class DrawPanel : Panel
{
public DrawPanel ()
{
DoubleBuffered = true;
}
}
}
Just add one DrawPanel and two Buttons..
(I really should have used my Wacom, and a little more space..)
Update: Instead of a Panel, which is a Container control and not really meant to draw onto you can use a Picturebox or a Label (with Autosize=false); both have the DoubleBuffered property turned on out of the box and support drawing better than Panels do.
I found this solution Creating a Hand-Drawn effect using .NET
Maybe there is something more easy, for example something with transformations?