Drag and drop rectangles in list - c#

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;
}

Related

Why does my program draw all the lines from the top left corner? How to change this?

I have written a program that is supposed to connect 10 points generated by mouseclicks, with lines.
I am the following problems:
it draws all lines from the top left corner.
I can think of the solution being something along the lines of setting the first point as the first mouseclick, but i do not know how to do this.
code:
public partial class Form1 : Form
{
Point[] punten = new Point[10];
private int kliks = 0;
private int lijst = 0;
public Form1()
{
InitializeComponent();
}
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
kliks = kliks + 1;
lijst = kliks;
punten[lijst] = e.Location;
if (lijst < 9)
{
punten[lijst] = e.Location;
}
else
{
Pen pen = new Pen(Color.Blue);
Graphics papier = this.CreateGraphics();
papier.DrawLines(pen, punten);
}
}
}
}
The problem for you is using a fixed-size array. Each entry defaults to a point (0,0) and so the Drawlines() method draws the 10 elements, even the ones not set by mouseclicks. This results in the final few points begin at the origin (0,0). In addition, there is a buggy implementation of which clicks to keep and how many have happened in using kliks and lijst for the same thing.
This being C#, it is better to learn how to separate your data model that keeps track of your mouselicks and your display codes that draws the lines.
PointHistory.cs
Consider the following class that uses an array of Point to keep track of the mouseclicks. The class is initialized with the maximum number of points to keep (here 10 for example). There are methods for adding a point AddPoint(point) and to remove the oldest point with RemoveFirstPoint(). The number of points defined is viewed with the .Count property.
Finally, the GetPoints() method, returns a copy of the array with only the elements that have been defined. This method is used in the code that draws the lines.
using System;
using System.Linq;
public class PointHistory
{
private readonly Point[] points;
private readonly int maxPoints;
private int count;
public PointHistory(int pointCapacity)
{
count = 0;
maxPoints = pointCapacity;
points = new Point[pointCapacity];
}
public Point[] GetPoints() { return points.Take(count).ToArray(); }
public int Count { get => count; }
public int Capacity { get => maxPoints; }
public void AddPoint(Point point)
{
if (count < maxPoints)
{
points[count++] = point;
}
}
public void RemoveFirstPoint()
{
if (count > 0)
{
for (int i = 0; i < count - 1; i++)
{
points[i] = points[i + 1];
}
count--;
}
}
}
Form1.cs
Another issue is your drawing, which needs to happen in Paint event handler. Use your MouseClick event to modify the list of points, and the Paint event to do the drawing.
public partial class Form1 : Form
{
readonly PointHistory history;
public Form1()
{
InitializeComponent();
history = new PointHistory(10);
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
this.MouseClick += Form1_MouseClick;
this.Paint += Form1_Paint;
}
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
history.AddPoint(e.Location);
}
else if (e.Button == MouseButtons.Right)
{
history.RemoveFirstPoint();
history.AddPoint(e.Location);
}
this.Invalidate();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
if (history.Count >= 2)
{
e.Graphics.DrawLines(Pens.Blue, history.GetPoints());
}
foreach (var point in history.GetPoints())
{
e.Graphics.FillEllipse(Brushes.Blue, point.X - 2, point.Y - 2, 4, 4);
}
}
}
I have added some extra code to add little dots at the mouse clicks for a better visual effect.
Left button clicks try to add points to the history of mouseclicks, up to the limit set in the constructor for PointHistory. The right mousebutton cicks removes the oldest point first before adding the new point.
Based on the comments, I want to present a version below that does not define a separate class for the logic, and everything is done internally withing the Form1 class. Also no advanced libraries such as Linq are used
public partial class Form1 : Form
{
private readonly Point[] points;
private int count;
public Form1()
{
InitializeComponent();
points = new Point[10];
count = 0;
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
this.Paint += Form1_Paint;
this.MouseClick += Form1_MouseClick;
}
public Point[] GetPoints()
{
Point[] results = new Point[count];
for (int i = 0; i < count; i++)
{
results[i] = points[i];
}
return results;
// Alternative to the loop above
//
//Array.Copy(points, results, count);
//return results;
}
public void AddPoint(Point point)
{
if (count < points.Length)
{
points[count++] = point;
}
}
public void RemoveFirstPoint()
{
if (count > 0)
{
for (int i = 0; i < count - 1; i++)
{
points[i] = points[i + 1];
}
count--;
}
}
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
AddPoint(e.Location);
}
else if (e.Button == MouseButtons.Right)
{
RemoveFirstPoint();
AddPoint(e.Location);
}
this.Invalidate();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
Point[] mouseClicks = GetPoints();
if (count >= 2)
{
e.Graphics.DrawLines(Pens.Blue, mouseClicks);
}
foreach (var point in mouseClicks)
{
e.Graphics.FillEllipse(Brushes.Blue, point.X - 2, point.Y - 2, 4, 4);
}
}
}
The main method to look at is GetPoints() which returns an array of points that have been defined by mouse clicks and ignores any points that have not been defined yet.
There is an alternate implementation commented with uses Array.Copy() instead of a loop which would make for a better imlpementation.

Prevent flickering while previewing shapes WinForms

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

C# drag a snapping control

I am creating a form that when a panel is dragged from a parent panel to another panel, the new panel becomes the parent. I created a version of the program, but I'm not sure what I'm missing. How would I add a timer to the dragged panel it so that it doesn't move immediately after clicking it, and properly create snapping bounds so that it snaps within a certain distance around the parent panel.
using System;
using System.Drawing;
using System.Windows.Forms;
namespace SNAPtest
{
public partial class Form1 : Form
{
Point MousePoint = new Point();
public Form1()
{
InitializeComponent();
panel_white.MouseDown += new MouseEventHandler(White_MouseDown);
panel_white.MouseMove += new MouseEventHandler(White_MouseMove);
}
private void White_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
int mx = Math.Min(Math.Max(panel_white.Location.X - MousePoint.X + e.X, 0), panel_white.Parent.Width - panel_white.Width);
int my = Math.Min(Math.Max(panel_white.Location.Y - MousePoint.Y + e.Y, 0), panel_white.Parent.Height - panel_white.Height);
panel_white.Location = new Point(mx, my);
}
if (panel_white.Location.X == Math.Max(panel_maroon.Location.X, 10) | panel_white.Location.Y == Math.Max(panel_maroon.Location.Y, 100))
{
Console.WriteLine("Maroon has focus");
panel_white.Parent = panel_maroon;
}
if (panel_white.Location.X == Math.Max(panel_blue.Location.X, 10) | panel_white.Location.Y == Math.Max(panel_blue.Location.Y, 100))
{
Console.WriteLine("Blue has focus");
panel_white.Parent = panel_blue;
}
}
private void White_MouseDown(object sender, MouseEventArgs e)
{
panel_white.Parent = this;
panel_white.BringToFront();
if (e.Button == MouseButtons.Left)
{
MousePoint = e.Location;
}
}
}
}

I still can't clear the rectangle i drawn over a control inside OnPaint event why i can'y clear it?

By clear i mean to redraw or paint or color the control back to it's original.
This is the working code:
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using System.Drawing;
namespace FTP_ProgressBar
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
txtHost.TextChanged += anyTextBox_TextChanged;
txtUploadFile.TextChanged += anyTextBox_TextChanged;
txtDir.TextChanged += anyTextBox_TextChanged;
anyTextBox_TextChanged(null, null);
if ((txtHost.Text == "") || txtUploadFile.Text == "")
{
btnUpload.Enabled = false;
}
if (txtDir.Text == "")
{
checkBox1.Enabled = false;
}
}
private void anyTextBox_TextChanged(object sender, EventArgs e)
{
btnUpload.Enabled = txtHost.TextLength > 0 && txtUploadFile.TextLength > 0;
checkBox1.Enabled = txtDir.TextLength > 0;
this.Invalidate();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void btnBrowse_Click(object sender, EventArgs e)
{
if(this.openFileDialog1.ShowDialog() != DialogResult.Cancel)
this.txtUploadFile.Text = this.openFileDialog1.FileName;
}
private void btnUpload_Click(object sender, EventArgs e)
{
if(this.ftpProgress1.IsBusy)
{
this.ftpProgress1.CancelAsync();
this.btnUpload.Text = "Upload";
}
else
{
FtpSettings f = new FtpSettings();
f.Host = this.txtHost.Text;
f.Username = this.txtUsername.Text;
f.Password = this.txtPassword.Text;
f.TargetFolder = this.txtDir.Text;
f.SourceFile = this.txtUploadFile.Text;
f.Passive = this.chkPassive.Checked;
try
{
f.Port = Int32.Parse(this.txtPort.Text);
}
catch { }
this.toolStripProgressBar1.Visible = true;
this.ftpProgress1.RunWorkerAsync(f);
this.btnUpload.Text = "Cancel";
}
}
private void ftpProgress1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.toolStripStatusLabel1.Text = e.UserState.ToString(); // the message will be something like: 45 Kb / 102.12 Mb
this.toolStripProgressBar1.Value = Math.Min(this.toolStripProgressBar1.Maximum, e.ProgressPercentage);
}
private void ftpProgress1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if(e.Error != null)
MessageBox.Show(e.Error.ToString(), "FTP error");
else if(e.Cancelled)
this.toolStripStatusLabel1.Text = "Upload Cancelled";
else
this.toolStripStatusLabel1.Text = "Upload Complete";
this.btnUpload.Text = "Upload";
this.toolStripProgressBar1.Visible = false;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Pen penBorder;
if (txtHost.TextLength <= 0)
{
penBorder = new Pen(Color.Red, 3);
e.Graphics.DrawRectangle(penBorder, txtHost.Location.X, txtHost.Location.Y, txtHost.Width - 1, txtHost.Height - 1);
}
if (txtUploadFile.TextLength <= 0)
{
penBorder = new Pen(Color.Red, 3);
e.Graphics.DrawRectangle(penBorder, txtUploadFile.Location.X, txtUploadFile.Location.Y, txtUploadFile.Width - 1, txtUploadFile.Height - 1);
}
}
}
}
I saw now that without a breakpoint if i minimize form1 when the program is running after typed text in both textBoxes and then resize the form1 it does clear the rectangles.
Strange it seems that it's taking effect only when i minimize and resize back the form1.
In the TextChanged event i tried to add: txtHost.Invalidate(); but it didn't help.
The only way that the rectangle get clear is if i minmize and resize back form1.
Or adding this.Invalidate(); did the trick.
OnPaint() only gets called when the window needs to be updated. This is a basic principle about how Windows works. If you need the window to be updated now then, yes, you need to invalidate the window so that OnPaint() will be called.
But is it ok to redraw all the form?
Sure, but it's not very performant as you are redrawing areas that don't necessarily need redrawing. Invalidate() should have a version that accepts a rectangle argument. Use it to only invalidate the area you want to update.

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

Categories