I'm trying to make an animation in Win Forms, and I have tried a different approach than calling Invalidate() in the timer_elapsed handler and then doing things in the OnPaintHandler.
I just directly draw an image on a pictureBox, then when the timer_ticks, I call Invalidate, and then I manually redraw the image on updated position.
static void tmrMoving_Elapsed(object sender, EventArgs e, Train t)
{
MainForm.playBox.Invalidate();
g.DrawImage(t.components[i], new Rectangle(
new Point(t.nextVagoonPositionX * 20, t.nextVagoonPositionY * 20),
new Size(20, 20)));
t.nextVagoonPositionX += 1;
t.nextPositionX = -1;
}
I have a problem, that when the timer ticks, the desired image sometimes get drawn, and sometimes not, like 30% chance to not draw(I have 1sec timer intervals so I can see it), even if the object is moving or just being in the same location.
Things I have tried:
1.Setting a region in Invalidate() args -> the image got drawn many times next to the older images (So it wasn't repaint at the desired location as the old images remained in their locations)
2.Calling Update() or Refresh() or both after the invalidate-> No image was ever shown
3.Combining Update() or Refresh() or both with Invalidate(rectangulous region) -> no image ever shown
4.Setting DoubleBuffering true -> no effect
5.Fiddling with this.SetStyle(ControlStyles.UserPaint, true); -> no effect (not even sure what it does btw.)
You're not supposed to directly paint UI controls outside of the OnPaint handler. In "lower level" WinAPI, you'd only painting to the screen on WM_PAINT, which is what raises the OnPaint event.
Move your drawing code (g.DrawImage(...)) out of the timer handler and into the OnPaint handler.
Related
So I'm trying to automate drawing a bunch of rectangles over an image. I've mostly figured out how to draw them, but the problem I'm running into is that I only seem to be able to get them to actually be drawn when I interact with the picture box.
namespace myGUI
{
public partial class formPicture : Form
{
public formPicture()
{
InitializeComponent();
pbImage.Image = Image.FromFile(#"../../Images/myImage.bmp");
// Doesn't Work
var g = pbImage.CreateGraphics();
g.DrawRectangle(new Pen(Color.Red, 5), 500, 200, 20, 20);
}
private void formPicture_Shown(object sender, EventArgs e)
{
// Doesn't Work
var g = pbImage.CreateGraphics();
g.DrawRectangle(new Pen(Color.Red, 5), 500, 200, 20, 20);
}
private void pbImage_Click(object sender, EventArgs e)
{
// Works
var g = pbImage.CreateGraphics();
g.DrawRectangle(new Pen(Color.Red, 5), 500, 200, 20, 20);
}
}
}
The pbImage_Click() call successfully draws the rectangle on my picture box. The other two instances of trying to draw the rectangle don't seem to result in anything.
I have no idea why it would work in one case, but not the others? I've tried invalidating the graphics, that didn't work. I've tried adding it to the Paint event, that didn't work. I've tried adding it to the validated event, the visible event, the loaded event, pretty much any other event that I can think of such that once the GUI was active it might actually result in the rectangle being drawn.
The only time it seems to work is Click or MouseClick (I haven't tried other interactive events, such as MouseUp or MouseDown, but I presume those would work as well. Besides, the whole point is that I want it to show up automatically.)
NEVER call CreateGraphics. If you want to draw on a control, handle the Paint event of that control and use the e.Graphics property provided. If the drawing needs to change, store the data that describes the drawing in one or more fields, then read those fields in the Paint event handler. If you need to force the drawing to change, modify those fields and then call Invalidate on the control. Ideally, pass an argument to that Invalidate method that describes the smallest area that has or might have changed. Here's one I and a friend prepared earlier.
Note that WinForms controls are painted and repainted quite often, so any drawing done outside the Paint event handler is likely to be wiped. That's exactly what's happening in the cases that you say are not working. The drawing is being done but is then wiped. By doing your drawing in the Paint event handler, you ensure that it is reinstated every time it is wiped, so it appears permanent.
In my code I am drawing a rectangle as a "frame" of a Panel. I am getting the required color from XML file as a string (like "red", "blue" etc.). While creating the panel, I am painting it using this code:
Strip.Paint += (sender, e) =>
{
//MessageBox.Show(clr.ToString());
Pen p = new Pen(Color.FromName(color), 2); // color is the string with name of the color
Rectangle r = new Rectangle(1, 1, 286, 36);
e.Graphics.DrawRectangle(p, r);
p.Dispose();
e.Dispose();
};
In the method that is supposed to refresh the rectangle, I add this line
Strip.Refresh();
This works fine. But, using Timer every 30 seconds I check if the color has changed, and
if it did, redraw the rectangle with requested color. The first rectangle draws correctly. But when the Timer reaches 30, it just... Well I am not sure even how to describe it, here is the picture what it does after "refreshing":
The "Red Cross" is what happens when an exception is thrown inside a OnPaint method. It means that you have a bug in your code inside the Paint lambda.
Once an exception is thrown, an internal flag is set and the control will no longer attempt to repaint itself. This is reset only when the application is run again, or when this trick is performed.
I suspect know the problem in your case is that you are not supposed to Dispose() the PaintEventArgs object in a Paint event.
In general, you should not have to dispose of objects you did not create yourself.
I have a PictureBox it serves as a canvas.
A List<RectangleObj> the array size of aprox 8000.
The "RectangleObj" is a simple rectangle class, once you invoke its Draw(Graphic g) method it will draw
the border using
g.DrawRectangle(...) and,
fill the rectangle with an alpha
transparency using
g.FillRectangle(...)
In the application Form.cs, i use the pictureBox1_Paint(...) to loop the array of RectangleObj and invoke the Draw method of that class.
Like this.
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
for (int i = 0; i < RectList.Count(); i++) //List<RectangleObj> count = 8000
RectList[i].Draw(e.Graphics);
}
Every time the mouse clicks upon a RectangleObj and drags changing its location (mouse move event) the paint event is called. since the array is large in number, the paint event does not have enough time to finish its loop and gets flooded with mouse movements. so, this makes the Paint event slow.
Can somebody advice me how to optimize this procedure.
Have you considered drawing to an in memory bitmap, then blitting that to the screen?
There are a couple of things here:
don't draw while moving. Calculate an outline for the selected elements and XOR only that outline when the mouse moves. Repaint on MouseUp.
don't draw stuff you don't need. You can run an algorithm to detect which rectangles are totally obscured and ignore them.
Make sure you have DoubleBuffered=true for your Picturebox
Work out which rectangles need to be redrawn based on the location of the moving rectangle and its prior location and only redraw those. Clip the painting to only those parts that have been invalidated.
I am trying to create this simple application in c#: when the user double clicks on specific location in the form, a little circle will be drawn. By one click, if the current location is marked by a circle - the circle will be removed.
I am trying to do this by simply register the MouseDoubleClick and MouseClick events, and to draw the circle from a .bmp file the following way:
private void MouseDoubleClick (object sender, MouseEventArgs e)
{
Graphics g = this.CreateGraphics();
Bitmap myImage = (Bitmap)Bitmap.FromFile("Circle.bmp");
g.DrawImage(myImage, e.X, e.Y);
}
My problem is that I dont know how to make the circle unvisible when the user clicks its location: I know how to check if the selected location contains a circle (by managing a list of all the locations containig circles...), but I dont know how exactly to delete it.
Another question: should I call the method this.CreateGraphics() everytime the user double-clicks a location, as I wrote in my code snippet, or should I call it once on initialization?
My personal preference is to put my images in instances of the Picturebox class. Reason being, I can simply call each Picturebox's Hide() function (or set 'Visible` to false).
What you're doing is drawing directly onto the window's client area, which technically isn't wrong but normally should be done in the form's Paint handler. If at some point you decide you don't want your circle to be visible anymore, you can call the form's Invalidate() method which triggers the Paint event. There, you explicitly do not draw your circle, and so to the user, the circle disappears.
The nice thing about a Picturebox is that it's persistent - you put your image into it and optionally draw on that image, but you only need to draw once. If you use the Paint handler technique, your drawing code gets called each time the form needs to redraw itself.
Edit:
Here's some code that illustrates my Paint handler information:
private void Form_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear(); // clear any and all circles being drawn
if (CircleIsVisible)
{
e.Graphics.DrawEllipse( ... ); // OR, DrawImage( ) as in your example
}
}
private void MouseDoubleClick (object sender, MouseEventArgs e)
{
CircleIsVisible = true;
Invalidate(); // triggers Paint event
}
If you're drawing bitmaps, I would load the bitmap once and store it as a class variable. This way you don't need to hit the hard drive each time you want to draw. Dispose of the bitmap when you dispose of your class (in this case, your window).
I thinks you should clear all of the image you draw before your next double click.
Such as Graphics.Clear().
On the other hand, you should not to create Graphics object or dispose it every time.
If you have simple background color you could use Graphics.DrawEllipse to draw Circles and then just change circle color to the background color. Also you need to have a Collection of all circles you draw so you can access any circle that you've drawn.
I would like trigger the drag target control to redraw itself (via either invalidate or refresh) during the DragEnter and DragLeave events. The code looks something like:
protected override void OnDragEnter (DragEventArgs drgargs)
{
//-- Set a property that affects drawing of control, then redraw
this.MyProperty = true;
this.Refresh(); //-- Does nothing???
}
protected override void OnDragLeave (EventArgs e)
{
//-- Set a property that affects drawing of control, then redraw
this.MyProperty = false;
this.Refresh(); //-- Does nothing???
}
It doesn't actually redraw the control, the OnPaint method is not called by the Refresh() within these events. Is there any way to do this? I must not be understanding something here.
UPDATE: the answer provided by jasonh doesn't actually work. When using Invalidate() or Invalidate(rect) the control does not actually update. This is being calls during a drag and drop action. Any other ideas? Can you trigger a redraw of a control during a drag and drop? Thanks!
UPDATE 2: I created a sample project and could not get this to not work. Sigh... I finally tracked it down to some code in the OnPaint that was causing the problem. So, this turned out to be more of me not understanding how the debugger worked (it never hit break points in the OnPaint...still don't know why). Invalidate(), Refresh() both work. JasonH gets the answer as it was ultimately correct and also showed how to invalidate just a portion of a control...I didn't know about that.
Thanks for all your help!
Call this.Invalidate() to get the Form/Control to redraw itself. If you know the specific region, then call one of the overloaded methods to specify what to invalidate. For example:
Rectangle toInvalidate = new Rectangle(drgargs.X - 50, drgargs.Y - 50, 50, 50);
this.Invalidate(toInvalidate);
That would invalidate an area 50 pixels around the area the drag target is.
There are three seemingly applicable methods here:
Control.Invalidate() - marks the control (region, or rectangle) as in need of repainting, but
doesn't force repainting, the repaint is triggered when everything else has
been taken care of and the app becomes idle.
Control.Update() - causes the control to immediately repaint if any portions have been
invalidated.
Control.Refresh() - causes the control to invalidate, and then update (immediately
repaint itself).
So, Refresh() is the right approach. What I would do is set a breakpoint on the refresh method call and see if/when it's being hit.