Error when drawing line in Graphics.DrawLine C# Visual Studio - c#

private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics l = e.Graphics;
Pen p = new Pen(Color.Black, 1);
float angle = 0;
float len = 100;
PointF ori = new PointF(Width/2, 0);
PointF bob = new PointF(Width/2, len);
while(true)
{
bob.X = ori.X + len * (float)Math.Sin(angle);
bob.Y = ori.Y + len * (float)Math.Cos(angle);
angle += 0.001F;
l.DrawLine(p, ori.X, ori.Y, bob.X, bob.Y);
l.DrawEllipse(p, bob.X - 15, bob.Y, 30, 30);
if(angle == 360)
{
break;
}
l.Dispose();
}
}
The error line is l.DrawLine(p, ori.X, ori.Y, bob.X, bob.Y).
Error type: System.ArgumentException. Error Message: Parameter is not valid.
At first I thought the issue was with the floats but the DrawLine allows for such datatypes. It loops through once the error seems to occur when angle>0. Its magnitude doesn't seem to be the issue. Any help would be much appreciated. Thanks in advance. [UPDATE] Error seems to be with the l.Dispose

The problem is in the wrong Dispose call:
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics l = e.Graphics;
// Pen is IDisposable, that's why why wrap it into "using": it's you who created it
using (Pen p = new Pen(Color.Black, 1)) {
float angle = 0;
float len = 100;
PointF ori = new PointF(Width/2, 0);
PointF bob = new PointF(Width/2, len);
while(true)
{
bob.X = ori.X + len * (float)Math.Sin(angle);
bob.Y = ori.Y + len * (float)Math.Cos(angle);
angle += 0.001F;
l.DrawLine(p, ori.X, ori.Y, bob.X, bob.Y);
l.DrawEllipse(p, bob.X - 15, bob.Y, 30, 30);
// angle is float, that's why == is not recommended:
// (you can well have 359.99999999999999) and thus == will never be true
if (angle >= 360)
break;
// l.Dispose(); // <- Wrong: it's not you who've created it
// (let system Dispose it)
}
}
}

As an enhancement to #Dmitry's answer, I can offer this advice:
As a general rule, you shouldn't call Dispose on an object that you do not control the lifetime of. You are getting an existing Graphics instance from the Paint event. The control that raised the Paint event is what created that Graphics object, so it is responsible for calling Dispose on it when it is done with it, not you.
When you call Dispose on an object that you don't control, you effectively "rip the rug out from under" that code. This is bad, because that code could be expecting the instance to still be alive so that it can perform other operations on it. By disposing it, you don't give it that chance.
You are doing the correct thing with your Pen instance, p, though. You create it during the using statement, so you are responsible for it. The Using handles that by automatically calling Dispose when execution leave the block.
If you had created the Graphicsinstance yourself, with something likeGraphics.FromImage`, then you would be responsible for cleaning it up.

Related

System.ComponentModel.Win32Exception: 'Error creating window handle.'

My problem is:
System.ComponentModel.Win32Exception: 'Error creating window handle'.
I know I can solve this problem with Dispose(), but when I use it in the program, I'm displaying another error:
System.ObjectDisposedException: 'Can not access a disposed object.
Object name: 'PictureBox'. '
I use the following code:
private void SetUpPuzzle_Click(int parts)
{
Panel P = new Panel
{
Size = new Size(200, 200),
Location = new Point(394, 62),
};
Controls.Add(P);
Control board = P;
int total = parts * parts;
var PB = new PictureBox[total];
var imgarray = new Image[total];
var img = User_Image.Image;
int W = img.Width / (int.Parse(Math.Sqrt(double.Parse(parts.ToString())).ToString()));
int H = img.Height / (int.Parse(Math.Sqrt(double.Parse(parts.ToString())).ToString()));
int size = 200 / (int.Parse(Math.Sqrt(double.Parse(parts.ToString())).ToString()));
for (int x = 0; x < parts; x++)
{
for (int y = 0; y < parts; y++)
{
var index = x * parts + y;
imgarray[index] = new Bitmap(W, H);
using (Graphics graphics = Graphics.FromImage(imgarray[index]))
graphics.DrawImage(img, new Rectangle(0, 0, W, H),
new Rectangle(x * W, y * H, W, H), GraphicsUnit.Pixel);
PB[index] = new PictureBox
{
Name = "P" + index,
Size = new Size(size, size),
Location = new Point(x * size, y * size),
Image = imgarray[index],
SizeMode = PictureBoxSizeMode.StretchImage
};
PB[index].MouseEnter += Images_M_E;
PB[index].MouseLeave += Images_M_L;
PB[index].MouseClick += Form_MouseClick;
*PB[index].Dispose();
*board.Controls.Add(PB[index]);
}
}
}
When I want to create 10,000 objects
This error is displayed.
My problem is:
System.ComponentModel.Win32Exception: 'Error creating window handle'.
Indeed. You are creating way too many controls for a Winforms application.
And disposing of them doesn't really help because you can't use a disposed object any longer..
To have this kind of large puzzle (10k pieces) you need to change from using PictureBoxes (or any other Controls) to display the puzzle pieces to a different approach. This has been suggested in the original question but then you only wanted to have 100 pieces, remember?
The most common approach is this: Keep a list of images (when they are <= 256x256 pixels do put them into an ImageList!) and draw them in the board's Paint event. This will get rid of all the overhead involved with PictureBoxes.
(Aside: One may think this will not be performant with all the DrawImage calls. But all those PictureBoxes also need to draw all the pixels on all their surfaces, so that is no issue. But they also have to carry the overhead of being (under the hood) fully functional windows (see the error message!), which is why the system can only have a limited number of them; always try to keep the number of controls < 1k!)
You will have to move the placement logic to the board's Paint event and will also have to change the event model..:
Instead of having each PictureBox respond to its own events you will have to find a way to do all the work in the board's events. This will have to be diffenrent, depending on the event.
Since we don't know which event you have and what they do and which data they need for their work, it is hard to give all the necessary details, so I'll just point out a few things..:
There will not be a Enter or Leave event you can use. Instead you need to detect entering an area of a piece by testing for it in the MouseMove event. If you keep a List<Rectangle> you can use Rectangle.Contains(e.Location) for this test.
You can detect a MouseClick but then will have to find out which area was clicked. If your Enter and Leave logic from the MouseMove is working you can use its result to know where the Click went.
Similar ideas can be used for all other events; some are simple, some need a little calculation but they will all be fast and pretty easy to implement..
To optimize performance try to make the image n the right size and use Format32bppPArgb as the pixel format, because it is faster to display.
Another option is to pull the pixel data right from the original image in the Paint event with the same calculations you use now to create them. (There is a DrawImage overlay that uses two Rectangles, one to determine the target and one for the source area..) This saves GDI handles, at least if you can't use an ImageList.
Always plan for growth! For a better implementation do create a Piece class. It should hold a Rectangle and an integer index into the ImageList's Images collection. It could also have a method Switch(Piece otherPiece) which would either switch the Rectangles or the indices.
Good luck :-)
I met this exception because endless loop creating new UI control and set its properties. After looped many times, this excption was thrown when change control visible property. I found both User Object and GDI Object (From Task Manager) are quite large.
I guess your issue is similar reason that system resources are exhaust by those UI controls.
I comment PB[index].Dispose(); and it's work.
private void SetUpPuzzle(int parts)
{
// Comment ***********
//Panel P = new Panel
//{
// Size = new Size(200, 200),
// Location = new Point(394, 62),
//};
//Controls.Add(P);
//Control board = P; ***********
int total = parts * parts;
var PB = new PictureBox[total];
var imgarray = new Image[total];
var img = User_Image.Image;
int W =Convert.ToInt32(img.Width / Math.Sqrt(parts));
int H = Convert.ToInt32(img.Height / Math.Sqrt(parts));
int size = Convert.ToInt32(200 / Math.Sqrt(parts));
for (int x = 0; x < parts; x++)
{
for (int y = 0; y < parts; y++)
{
var index = x * parts + y;
imgarray[index] = new Bitmap(W, H);
using (Graphics graphics = Graphics.FromImage(imgarray[index]))
graphics.DrawImage(img, new Rectangle(0, 0, W, H),
new Rectangle(x * W, y * H, W, H), GraphicsUnit.Pixel);
PB[index] = new PictureBox
{
Name = "P" + index,
Size = new Size(size, size),
Location = new Point(x * size, y * size),
Image = imgarray[index],
SizeMode = PictureBoxSizeMode.StretchImage
};
PB[index].MouseEnter += Form1_MouseEnter;
PB[index].MouseLeave += Form1_MouseLeave;
PB[index].MouseClick += Form1_MouseClick;
//Comment
//PB[index].Dispose(); < -----------------
// Add PB in Panel in form
panel1.Controls.Add(PB[index]);
}
}
// after add all refresh panel
panel1.Refresh();
}
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
throw new NotImplementedException();
}
private void Form1_MouseLeave(object sender, EventArgs e)
{
throw new NotImplementedException();
}
private void Form1_MouseEnter(object sender, EventArgs e)
{
throw new NotImplementedException();
}
Then Call the SetUpPuzzle method in your button like :
private void button1_Click(object sender, EventArgs e)
{
SetUpPuzzle(10);
}

How to display only one clock pointer in c#

I'm trying to create an analog clock from scratch using C#, and I'm a little stuck at this point.
I have this code:
private void timer1_Tick(object sender, EventArgs e)
{
int seconde = DateTime.Now.Second;
spx = cx + (int)(length * Math.Sin(Math.PI * seconde / 30));
spy = cy - (int)(length * Math.Cos(Math.PI * seconde / 30));
a = new Point(spx, spy);
g.DrawLine(pen, m, a);
}
There are more variables in the form load but this is general how i draw the line for the second pointer on the clock. My problem is that it does exactly what i want. But after 60 seconds, I have 60 lines. How do I just display the good pointer line and/or delete the old lines.
Sorry if the answer to my problem is just easy. But I cant really find anything I understand as an answer.
You need to repaint the background of the clock every second before painting the new line, to remove the old line.
Of course, that would mean that for every paint of the seconds hand (or pointer) you will need to also paint the minutes hand and the hours hand, since both of them will also be deleted.
This is because winforms does not have the concept of painting in layers - everything gets painted on a single surface - so you have to first delete the old painting before you can paint a new one.
I would do it like this:
public partial class Form1 : Form
{
private int spx, spy, length;
private Pen pen = new Pen(new SolidBrush(Color.Red), 0.5f);
private Point a,m;
public Form1()
{
InitializeComponent();
}
private void timer1_Tick(object sender, EventArgs e)
{
this.Refresh(); // force redraw
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
length = Math.Min(e.ClipRectangle.Height, e.ClipRectangle.Width) / 2;
if (length != 0) // can't draw when there's no space
{
m = new Point(e.ClipRectangle.Width / 2, e.ClipRectangle.Height / 2);
int seconde = DateTime.Now.Second;
spx = m.X + (int)(length * Math.Sin(Math.PI * seconde / 30));
spy = m.Y - (int)(length * Math.Cos(Math.PI * seconde / 30));
a = new Point(spx, spy);
e.Graphics.DrawLine(pen, m, a);
}
}
}
I was taught to draw in OnPaint of Winforms controls.
Guess there are many different approaches.
By doing it this way you don't have to clear your graphics manually. This happens when the control is invalidated.

Dispose not being called correctly on disposable object

I have a function which draws a list of objects onto a bitmap to create a crude map. Code analysis throws a warning (CA2000) which says the object 'drawPen' isn't disposed along all exception paths. As far as I can see it is disposed at the end of the function and there is no inaccessible code where it could be missed.
Does anyone know why the compiler thinks it is not being disposed of properly?
public void drawUpdates(List<areaObjects> objectLocations)
{
Rectangle areaToClone = new Rectangle(0, 0, writeOnceMap.Width, writeOnceMap.Height);
var pixelFormat = writeOnceMap.PixelFormat;
areaBitMap = writeOnceMap.Clone(areaToClone, pixelFormat);
Pen drawPen = new Pen(Color.Black, 2);
drawPen.Width = 2;
foreach(areaObjectsop2d in objectLocations)
{
int xPosition = (int)(op2d.XYZ.xPos * mapScale);
int yPosition = (int)(op2d.XYZ.yPos * mapScale);
Point[] crossMarker = getCrossShape(xPosition, yPosition);
using (var graphics = Graphics.FromImage(areaBitMap))
{
graphics.DrawPolygon(drawPen, crossMarker);
}
}
drawPen.Dispose();
}
You get the warning because in the case of an exception in your function the drawPen will not be disposed.
You can wrapp your code in a try finally and in the finally call the .Dispose() or better - use the using which does precisely that.
public void drawUpdates(List<areaObjects> objectLocations)
{
Rectangle areaToClone = new Rectangle(0, 0, writeOnceMap.Width, writeOnceMap.Height);
var pixelFormat = writeOnceMap.PixelFormat;
areaBitMap = writeOnceMap.Clone(areaToClone, pixelFormat);
using(Pen drawPen = new Pen(Color.Black, 2))
{
foreach(areaObjectsop2d in objectLocations)
{
int xPosition = (int)(op2d.XYZ.xPos * mapScale);
int yPosition = (int)(op2d.XYZ.yPos * mapScale);
Point[] crossMarker = getCrossShape(xPosition, yPosition);
using (var graphics = Graphics.FromImage(areaBitMap))
{
graphics.DrawPolygon(drawPen, crossMarker);
}
}
}
}
The above is equivalent to:
Pen drawPen = new Pen(Color.Black, 2);
try
{
/*Your code*/
}
finally
{
drawPen.Dispose();
}
Imagine what happens if the code between creating the pen and disposing it throws an exception. The pen will not be disposed. The compiler warns you to make sure that the pen will get disposed even when an exception occurs. You have two ways to do so: using and try...finally (which is in fact the implementation of using).
using (Pen drawPen = ...)
{
} // now the code makes sure it gets disposed

Draw fractal Fit inside panel using Winform?

i am trying to build a windows application in .net which draw fractal image inside the panel.It take end points of line as starting point of next line.But problem is, diagram is going outside of the panel.How do i fix drawing inside the panel
static int start_x, start_Y;
static int end_x, end_Y;
static int my_angle = 0;
static int my_length = 0;
private void Canvas_Paint(object sender, PaintEventArgs e)
{
start_x = Canvas.Width / 2;
start_Y = Canvas.Height / 2;
for (int i = 0; i < 400; i++)
{
draw_T();
}
}
public void draw_T()
{
Pen mypen = new Pen(Color.Green, 2F);
my_angle = my_angle + (45);
my_length = 100 + (1);
end_x = (int)(start_x + Math.Cos(my_angle * .0174539676) * my_length);
end_Y = (int)(start_Y + Math.Sin(my_angle * .0174539676) * my_length);
Point[] points =
{
new Point (start_x,start_Y),
new Point (end_x,end_Y)
};
Point[] points1 =
{
new Point ((end_x+start_x)/2,(end_Y+start_Y)/2),
new Point (end_x+50,end_Y-100)
};
start_x = end_x;
start_Y = end_Y;
Graphics g = Canvas.CreateGraphics();
g.DrawLines(mypen, points);
g.DrawLines(mypen, points1);
}
I'm not sure how you graphic is supposed to look but I can give you a couple of hints.
At general one first: Do make use of e.Graphics parameter! Change
public void draw_T()
To
public void draw_T(Graphics g)
and delete the line.
Graphics g = Canvas.CreateGraphics();
Change the call to
draw_T(e.Graphics);
You are leaking GDI resource by creating all those Graphcs with disposing of them and and lose time by creating them when you already have the one from the Paint event.
Next you should add a NumericUpDown for testing your algorithm and script it like this:
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
Canvas.Invalidate();
}
To work you now change the loop to
for (int i = 0; i < numericUpDown1.Value; i++)
And watch your graphics develop.
Another test could be to introduce a second pen color for the second series of point.
To play around further you could add another NumericUpDown and tie my_lengthto it..
In the end you'll see that length needs to be smaller than 101 or the Canvas needs to be as large as 700 pixels.
BTW: Neither my_angle nor my_length need to be declared at class level since they are always set in the method and used nowhere else and no other variable needs to static either, at least from what you show us..

.Net DrawString font reference changes after invoking

Given the following code. Is there any potential for the first DrawString method to draw in Arial rather than Times New Roman?
protected override void OnPaint(PaintEventArgs pe)
{
Font f = new Font("Times New Roman", this.TextSize);
pe.Graphics.DrawString("Test 1", f, Brushes.Black, loc);
f = new Font("Arial", this.TextSize);
pe.Graphics.DrawString("Test 2", f, Brushes.Black, loc);
}
I have an issue where essentially this code is intermittently drawing the first string in the wrong font. I've changed the code to have two static font references now, but as I was unable to reproduce the code I can't be sure if it's fixed the problem or not.
Note: loc is a position that would be changed by the actual code, I've stripped out some code here to simplify
Here is the whole method with my fix in it. If you can't see anything wrong with it - I'll go blame some cosmic rays or something...
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
if (bcf == null)
{
FontFamily[] families = pfc.Families;
foreach (FontFamily ff in families)
{
if (ff.Name.Equals("Free 3 of 9"))
{
bcf = ff;
}
}
}
if (bcf != null)
{
Font f = new Font(bcf, this.BarcodeSize);
SizeF s = TextRenderer.MeasureText(barcodeValue, f);
Rectangle r = pe.ClipRectangle;
Point loc = new Point(0, 0);
if (s.Width < r.Width)
{
loc.X = (int)Math.Ceiling((r.Width - s.Width) / 2);
}
pe.Graphics.DrawString(barcodeValue, f, Brushes.Black, loc);
float barcodeBottom = s.Height + 5;
Font fp = new Font("Arial", this.TextSize);
s = TextRenderer.MeasureText(barcodeValue, fp);
r = pe.ClipRectangle;
loc = new Point(0, (int)Math.Ceiling(barcodeBottom));
if (s.Width < r.Width)
{
loc.X = (int)Math.Ceiling((r.Width - s.Width) / 2);
}
if (s.Height + loc.Y > r.Height)
{
loc.Y = (int)Math.Ceiling(r.Height - (s.Height));
}
pe.Graphics.FillRectangle(Brushes.White, new Rectangle(loc, new Size((int)Math.Ceiling(s.Width), (int)Math.Ceiling(s.Height))));
pe.Graphics.DrawString(barcodeValue, fp, Brushes.Black, loc);
}
}
The fixed code now looks like the following. Many fewer GDI calls now:
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
if (bcf != null)
{
Rectangle r = pe.ClipRectangle;
Point loc = new Point(0, 0);
if (barcodeDimensions.Width < r.Width)
{
loc.X = (int)Math.Ceiling((r.Width - barcodeDimensions.Width) / 2);
}
pe.Graphics.DrawString(barcodeValue, barcodeFont, Brushes.Black, loc);
float barcodeBottom = barcodeDimensions.Height + 5;
r = pe.ClipRectangle;
loc = new Point(0, (int)Math.Ceiling(barcodeBottom));
if (plaintextDimensions.Width < r.Width)
{
loc.X = (int)Math.Ceiling((r.Width - plaintextDimensions.Width) / 2);
}
if (plaintextDimensions.Height + loc.Y > r.Height)
{
loc.Y = (int)Math.Ceiling(r.Height - (plaintextDimensions.Height));
}
pe.Graphics.FillRectangle(Brushes.White, new Rectangle(loc, new Size((int)Math.Ceiling(plaintextDimensions.Width), (int)Math.Ceiling(plaintextDimensions.Height))));
pe.Graphics.DrawString(barcodeValue, plaintextFont, Brushes.Black, loc);
}
}
If I was planning on making this even more optimal I'd put the rectangle measuring parts in an override of OnResize, but I think this will do for now...
Yes, strange things start to happen when your program is close to consuming 10,000 GDI handles. It almost certainly affects the Windows font mapper without necessarily throwing an exception. Your program is playing Russian roulette with this potential problem because you are not calling Dispose() on the fonts you use. If the garbage collector doesn't run frequently enough then you may well get that gun to go off. You need to write it like this:
using (Font fp = new Font("Arial", this.TextSize)) {
// etc..
}
Also note another bug in your code, you are using TextRenderer.MeasureText but drawing with Graphics.DrawString. The measurement is not identical. You must use Graphics.MeasureString.
No, I can't see how that would possibly happen - it's not like the first call knows about the variable f - it only knows about its value at the time DrawString was called. The argument (a Font reference) is passed by value, not by reference.
The only way I could imagine this causing a problem is if the Graphics object remembers its "current" font (and resets it in the call to DrawString) but defers the actual drawing. That would have all kinds of nasty effects - I can't see it happening.
Basically, as far as the DrawString calls are concerned, it's as if you were using two different variables.
Skeet covered the base case. I've done a lot of GDI+ and I've never seen the behavior you describe. Most likely it is due to some other effect not shown by this code.
Can't see any problems with the code provided. Is it possible in your full code that the drawing locations are incorrect?
Are you 100% sure that the string value that is being rendered in Arial could only possibly be rendered by the call to DrawString using the TNR font?

Categories

Resources