I am getting
System.InvalidOperationException: 'Collection was modified; enumeration operation may not execute.
every time I want to remove an item from the array, so I could draw (fill) another rectangle at that place.
Everything is happening on MouseDown event on right click black rectangle should be drawn (filled) and on left white one.
I made 2 lists of rectangles, one for white ones, and one for black ones.
I am getting exception thrown when I try to draw (fill) rectangle of opposite color of the one that is.
Everything is written into a bitmap and later bitmap is draw as an image in Paint event.
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
using (Graphics rectGraphics = Graphics.FromImage(rectBitmap))
{
rBlack = new Rectangle((e.X / 20) * 20, (e.Y / 20) * 20, 20, 20);
rectGraphics.SmoothingMode = SmoothingMode.HighSpeed;
foreach (Rectangle r in whiteRectangles) // place where exception is thrown if I want to fill black rectangle on the place where white is
{
if (r.X - 1 == rBlack.X && r.Y - 1 == rBlack.Y)
{
int index = whiteRectangles.IndexOf(r);
whiteRectangles.RemoveAt(index);
}
rectGraphics.FillRectangle(brushWhite, r);
}
blackRectangles.Add(rBlack);
foreach (Rectangle r in blackRectangles)
{
rectGraphics.FillRectangle(brushBlack, r);
}
}
}
if (e.Button == MouseButtons.Left)
{
using (Graphics rectGraphics = Graphics.FromImage(rectBitmap))
{
rWhite = new Rectangle((e.X / 20) * 20 +1, (e.Y / 20) * 20 +1, 19, 19);
rectGraphics.SmoothingMode = SmoothingMode.HighSpeed;
foreach (Rectangle r in blackRectangles) // place where exception is thrown if I try to fill white rectangle on the place where black is
{
if (r.X + 1 == rWhite.X && r.Y + 1 == rWhite.Y)
{
int index = blackRectangles.IndexOf(r);
blackRectangles.RemoveAt(index);
}
rectGraphics.FillRectangle(brushBlack, r);
}
whiteRectangles.Add(rWhite);
foreach (Rectangle r in whiteRectangles)
{
rectGraphics.FillRectangle(brushWhite, r);
}
}
}
this.Refresh();
}
You probably need to use a for loop instead of a foreach. Set your upper limit using the Count of the list, then delete by index.
for (int i = 0; i < list.Count; i++)
{
//Delete list[i] here
}
Or, in reverse
for (int i = list.Count - 1; i >= 0; i--)
{
//Delete list[i] here
}
Related
I'm working on a connect 4 game and I've got the following part done:
I've made the board which is able to display tiles of the board in 3 colors (white, red and yellow). I've also written some code to display a box around the column on which the user is hovering such that he can see where he is placing a dish.
I've also created the code which allows a dish to be added to the board. Both of these processes use the following code:
//Calculate the size of a single cell
int cellX = this.Size.Width / this.Columns;
//calculate the cell which was clicked
int nColumn = (int)Math.Floor((Double)x / cellX);
x is the value of MouseEventArgs.X of the pannel on which this is called. For drawing a boundry box this code works perfectly but for dropping a dish it doesn't. sometimes it drops 1 left of where I want it sometimes one right.
Here is the code for both events:
//draws the bounding box around the column for a given x value.
public void drawBoundingBox(int x)
{
//Calculate the size of a single cell
int cellX = this.Size.Width / this.Columns;
//calculate the cell which was clicked
int nColumn = (int)Math.Floor((Double)x / cellX);
if (nColumn != this.lastColumn)
{
this.Refresh();
Graphics g = CreateGraphics();
Pen pRed = new Pen(Color.Red, 3);
if (nColumn < this.Columns)
{
Rectangle rect = new Rectangle(new Point(nColumn * cellX, 0),
new Size(cellX, this.Size.Height));
g.DrawRectangle(pRed, rect);
}
}
this.lastColumn = nColumn;
}
public Boolean move(int mousePosition) {
int cellX = this.Size.Width / this.Columns;
int nColumn = (int)Math.Floor((Double)mousePosition / cellX);
return board.Move(nColumn);
}
Here is the code for board.move():
public bool Move(int x)
{
bool found = false;
for (int i = Rows - 1; i >= 0 && !found; i--)
{
Console.WriteLine("X equals: " + x + " i equals: " + i);
if (States[i,x] == 0) {
found = true;
States[i, x] = (byte)(((moves++) % 2) + 1);
}
}
return found;
}
Here is a gif showing what I mean:
To me it seams like a rounding error but it's wierd to me that the bounding box works well with the mouse but the clicking action doesn't...
I have a jpeg image in a picturebox, on page load i am drawing the rectangle based on the x(150) and y(440) coordinates. now when i mousemove on the picturebox i need to identify the rectangle by their coordinates and highlight the image. for example see the below image ..
lets take the first rectangle, on mouse move any points inside the rectangle i need to perform some actions.. how to find the coordinates between these x and y for the rectangle?..
A rectangle has 4 Points (edges):
Left
Top
Right
Bottom
If your mouse coordinates (MouseEventArgs properties) are between them, the mouse pointer is in the rectangle.
Are the mouse coordinates greater than right or bottom or lower than left / top, your mouse is outside the rectangle.
Taking #samgak`s Comment:
if(
(point.x >= rectangle.min_x) && (point.x <= rectangle.max_x) &&
(point.y >= rectangle.min_y) && (point.y <= rectangle.max_y)) {
//do something
}
and replacing point with e is exactly what you want.
Maybe the following link will help to understand:
How to check if a point is inside a rectangle
Provided images are the same size and assuming it is stored in variable imageSize (of type System.Drawing.Size) then:
Size imageSize = new Size(...) // define the size here
...
int row = point.y / imageSize.height;
int col = point.x / imageSize.width;
var rect = new Rectangle(col * imageSize.Width, row * imageSize.Height, imageSize.Width, imageSize.Height);
You can then use rect to draw you frame around the image (you may want to inflate the rectangle by a couple of pixels)
Hi Samgak /Clijsters,
I have completed my functionality
// page level declaration
private Rectangle SelectedRect;
public List<Rectangle> listRec = new List<Rectangle>();
// on page load add all the rectangle in the rectanglelist.
private void Highlightimage_Load(object sender, EventArgs e)
{
for (int i = 0; i < table.Rows.Count; i++)
{
int x = Convert.ToInt32(table.Rows[i][0]);
int y = Convert.ToInt32(table.Rows[i][1]);
int width = Convert.ToInt32(table.Rows[i][2]);
int height = Convert.ToInt32(table.Rows[i][3]);
SelectedRect.Size = new Size(width, height);
SelectedRect.X = x;
SelectedRect.Y = y;
listRec.Add(SelectedRect);
}
}
// draw the rectangle
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{ Graphics g = e.Graphics;
foreach (Rectangle rec in listRec)
{
Pen p = new Pen(Color.Red);
g.DrawRectangle(p, rec);
}
}
private Rectangle MakeRectangle(int x0, int y0, int x1, int y1)
{
return new Rectangle(
Math.Min(x0, x1),
Math.Min(y0, y1),
Math.Abs(x0 - x1),
Math.Abs(y0 - y1));
}
//finally on mouse move checking the condition
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
foreach (Rectangle rec in listRec)
{
SelectedRect = MakeRectangle(rec.Left, rec.Top, rec.Right, rec.Bottom);
if (
(e.X >= SelectedRect.Left) && (e.X <= SelectedRect.Right) &&
(e.Y >= SelectedRect.Top) && (e.Y <= SelectedRect.Bottom))
{
MessageBox.Show("test");
}
}
}
Also refered this link
Drawing Multiple Rectangles c#
I thought this will help some one.
Thanks
Dev
I have a very simple method that takes a signature as a list of points and draws them as lines on a bitmap. And I want to take that bitmap and save it to a file, but when I call the Save method Im getting "A generic error occured in GDI+" and no Inner Exception.
This seems like pretty straight forward code so Im not sure what the problem is.
using (var b = new Bitmap(width, height))
{
var g = Graphics.FromImage(b);
var lastPoint = points[0];
for (int i = 1; i < points.Length; i++)
{
var p = points[i];
// When the user takes the pen off the device, X/Y is set to -1
if ((lastPoint.X >= 0 || lastPoint.Y >= 0) && (p.X >= 0 || p.Y >= 0))
g.DrawLine(Pens.Black, lastPoint.X, lastPoint.Y, p.X, p.Y);
lastPoint = p;
}
g.Flush();
pictureBox.Image = b;
b.Save("C:\\test.bmp");
}
I've tried saving with all the possible ImageFormats, put the graphics object in a using statement, and as a long shot I even tried:
using (var ms = new MemoryStream())
{
b.Save(ms, ImageFormats.Bmp); // And b.Save(ms, ImageFormats.MemoryBmp);
Image.FromStream(ms).Save("C:\\test.bmp");
}
The strange thing is, if I remove the b.Save (or ignore the exception), the pictureBox displays the image perfectly.
Any help would be appreciated.
I would draw to the PictureBox using the corresponding Graphics from the Paint event and then save to a bitmap:
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
MyDrawing(e.Graphics);
Bitmap b = new Bitmap(pictureBox1.Width, pictureBox1.Height);
pictureBox1.DrawToBitmap(b, pictureBox1.ClientRectangle);
b.Save(#"C:\test.bmp");
}
private void MyDrawing(Graphics g)
{
var lastPoint = points[0];
for (int i = 1; i < points.Count; i++)
{
var p = points[i];
// When the user takes the pen off the device, X/Y is set to -1
if ((lastPoint.X >= 0 || lastPoint.Y >= 0) && (p.X >= 0 || p.Y >= 0))
g.DrawLine(Pens.Black, lastPoint.X, lastPoint.Y, p.X, p.Y);
lastPoint = p;
}
g.Flush();
}
Saved BMP:
Your code has two issues, one hiding the other:
Your application probably doesn't have writing rights on the root of C:, so the Save fails.
You also can't have a PictureBox display a Bitmap which you then destroy in the using block.
So you should change the code to something like this:
var b = new Bitmap(width, height);
using (Graphics g = Graphics.FromImage(b))
{
var lastPoint = points[0];
for (int i = 1; i < points.Length; i++)
{
var p = points[i];
// When the user takes the pen off the device, X/Y is set to -1
if ((lastPoint.X >= 0 || lastPoint.Y >= 0) && (p.X >= 0 || p.Y >= 0))
g.DrawLine(Pens.Black, lastPoint.X, lastPoint.Y, p.X, p.Y);
lastPoint = p;
}
// g.Flush(); not necessary
pictureBox1.Image = b;
b.Save("C:\\temp\\test.bmp");
}
You also should include a check on the Length of the array and consider using a List<Points> instead, also checking its Count and using DrawLines on points.ToArray() for better line joins, especially with thick, semi-transparent lines!
private void pictureBox1_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
points.Add(new PointF(e.X * xFactor, e.Y * yFactor));
pictureBox2.Invalidate();
label5.Visible = true;
label5.Text = String.Format("X: {0}; Y: {1}", e.X, e.Y);
counter += 1;
label6.Visible = true;
label6.Text = counter.ToString();
}
}
private void pictureBox2_Paint(object sender, PaintEventArgs e)
{
Pen p;
p = new Pen(Brushes.Green);
foreach (PointF pt in points)
{
e.Graphics.FillEllipse(Brushes.Red, pt.X, pt.Y, 3f, 3f);
}
foreach (PointF pt in points)
{
if (points.Count > 1)
{
e.Graphics.DrawLine(p, pt.X, pt.Y, 3f, 3f);
}
}
}
When i click on pictureBox1 its drawing a point on pictureBox2.
In the pictureBox2 paint im doing a loop over the List of points and drawing.
Then i did another loop and in the DrawLine i want to connect the last point with the next one with a line how can i do it ?
Tried this one now:
for (int i = 0; i < points.Count; i++)
{
if (points.Count > 1)
{
e.Graphics.DrawLine(p, points[i].X, points[i].Y, points[i+1].X, points[i+1].Y);
break;
}
}
But this will connect only the first two points not all the others.
I want that each time i make a click and draw a new point it will be automatic connected with a line to the last drawed point.
The break only allows one line to be drawn. Since you need to access 2 points at a time, you have to be careful to keep the array index within bounds. If you pick the 2nd point by doing i+1 then you need your for loop to stop when i < points.Count - 1 that gives you room at the end.
you should use the GraphicsPath take a look at this link class to do this for example to draw a line use this
private void AddLineExample(PaintEventArgs e)
{
//Create a path and add a symetrical triangle using AddLine.
GraphicsPath myPath = new GraphicsPath();
myPath.AddLine(30, 30, 60, 60);
myPath.AddLine(60, 60, 0, 60);
myPath.AddLine(0, 60, 30, 30);
// Draw the path to the screen.
Pen myPen = new Pen(Color.Black, 2);
e.Graphics.DrawPath(myPen, myPath);
}
Just wondering how I would go about actually selecting the rectangle I draw and then being able to move it around? I would also need to delete it but I can work that part out. Just a bit lost as to what I need to do. Pseudo Code or actual code is fine, just a stepping stone would be appreciated.
Also, if anyone else knows how to actually show the rectangles X and Y in a listBox that would be great as well. But at the moment, the top question is priority.
This is how I draw my rectangles:
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
if (buttonDown)
{
if (pictureBox1.Image != null)
{
draw = true;
Graphics g = Graphics.FromImage(imageFile);
Pen pen1 = new Pen(color, 4);
g.DrawRectangle(pen1, e.X - 5, e.Y - 5, 5, 5);
g.Save();
pictureBox1.Image = imageFile;
}
}
}
In order to achieve such behavior, you will have to store information about the rectangles. E.g. in a class like this:
class Rect
{
int X { get; set; }
int Y { get; set; }
int Width { get; set; }
int Height { get; set; }
}
Note that there is already a Rectangle type. You can use this as well, but since it is a structure, you have to handle selection differently.
Now you can create a list of rectangles and the selection:
List<Rect> rects = new List<Rect>();
Rect selectedRect = null;
Displaying these rects in a listbox is pretty easy. Just bind the listbox's items to the list. You may need to override ToString() in order to get a user-friendly output.
Drawing the rectangles could look like the following:
foreach(var r in rects)
{
g.DrawRectangle(pen1, r.X, r.Y, r.Width, r.Height);
...
}
You can highlight the selected rect. E.g. with a different pen or another rect around it:
if(selectedRect == r)
g.DrawRectangle(pen1, r.x - 5, r.Y - 5, r.Width + 10, r.Height + 10);
In the MouseDown event or another appropriate event you can set the selection to the clicked rect:
foreach(var r in rects)
if(e.X >= r.X && e.X <= r.X + r.Width && e.Y >= r.Y && e.Y <= r.Y + r.Height)
selectedRect = r;
To move the rect, you would save the point of the rect that has been clicked (distance from top left corner). In the mouse move event, check if the left button is down and reposition the selectedRect accordingly.