I've got a Windows Form that circulates through images which are displayed on the form as a slideshow. The way I'm doing this is to have a Panel control the size of the form it resides in, and add an event handler that draws an Image object that exists in memory.
void panel_Paint(object sender, PaintEventArgs e)
{
if (_bShowImage)
{
Point leftCorner = new Point((this.Bounds.Width / 2) - (_image.Width / 2), (this.Bounds.Height / 2) - (_image.Height / 2));
e.Graphics.DrawImage(_image, leftCorner);
_bShowImage = false;
}
}
When a new Image is loaded and referenced by _image, I'm forcing the Panel to redraw:
_bShowImage = true;
_panel.Refresh();
Immediately afterwards, the image is disposed and the dereferenced from the global variable:
_image.Dispose();
_image = null;
I've seen that it works for a while, say 5 iterations, then the panel_Paint() handler is not being called. I'm using 2-3 JPG's for the display and I know they're not corrupted as they are shown fine for the first x times. I've put debug lines around the Refresh() method of the panel which execute fine. It's as if the call to the handler has been dropped. Has anyone encountered this problem before?
This is so completely backwards. Either you use a paint event handler like now. It's just fine (I say it's better than a picturebox) but then you need to drop that _bShowImage and _image.Dispose stuff. You should instead dispose the _image before you power it up with a new one. But not until that.
Or, if you absolutley must dispose the _image right after it's painted, then you should instead use Panel.CreateGraphics to get a Graphichs object you can use to immediately draw the _image and drop the event.
As it stands - it is just darn confusing. Also: .Invalidate() is what you almost always want -not .Refresh(). That's just something that got stuck in many minds since the VB6 era.
Wouldn't it be smarter to put your pictures in a picture box and loop through them in that way, so that you don't force a repaint on the whole window each time?
just a thought...
Tony
Related
This question is a little strange, but I'm going to try to explain everything as well as I can.
I've having trouble automating a specific occurrence of window repainting. Basically, I'm trying to to make the paint event fire as though the window had been moved while some of it's content was off-screen (because, for example, the window's dimensions are too large to fit on the screen).
I've tried using Invalidate(), Update() and Refresh(), as well as this.Invalidate(new Rectangle(0, 0, Width, Height));. however none produce the effects I'm expecting.
Currently, the case I'm testing for within the Paint event is when the PaintEventArgs ClipRectangle property has Width and Height greater both greater than zero. When the application is launched, and when all of the aforementioned methods are run, the subsequent PaintEventArgs ClipRectangle has all values of zero:
{ClipRectangle = {X=0,Y=0,Width=0,Height=0}}
However, when I manually drag the window (while some of it is off screen) and the paint event fires the ClipRectangle has these values:
{ClipRectangle = {X=0,Y=0,Width=1356,Height=1636}}
The shown Width and Height are the full dimensions of the window.
I'm assuming the difference in the two cases is that, in the latter, because some of the window is off-screen, all of it must be redrawn when moved to account for what was previously not on screen; though, admittedly, I'm not entirely sure of the exact cause of these different cases. Unfortunately, through all of my crazy, different attempts, my program only works when the Paint event is occurs in these specific circumstances. Is anyone aware of any way to force an event like this to occur (preferably regardless of whether the window is actually off-screen)? I don't care how hackey or weird a solution is as long as it works reliably.
Try adding this in the OnPaint function:
if (e.ClipRectangle.Width < 200 || e.ClipRectangle.Height<200)
{
this.Refresh();
}
else
{
//your paint code
}
I have a question about forms and controls. I want to add the ability to sort of make a part of my form only show when something is clicked. For example I have form1 and on the form i have a button and when that button is clicked the form grows or extends (slides out?) to show other controls that werent there before the button was clicked. I have no idea what this is called so I don't know what to look for but Ive seen it used in many other applications. Any information on this would be greatly appreciated.
You'd probably have to roll your own animation, increasing the size dimensions of your form (or panel, or whatever) on a timer, thereby exposing the previously hidden controls.
Timer T = new Timer();
T.Interval = 10;
T.Tick += (s, e) =>
{
myForm.Size = new System.Drawing.Size(myForm.Width + 10, myForm.Height);
if (myForm.Size.Width >= FormWidthThreashold)
T.Stop();
};
T.Start();
At the risk of stating the obvious, I don't suppose there's any way to switch the WPF? This stuff is built in, and quite easy for WPF. If not though, something like this should get you started.
I've done this before. Start by organising your form into logical sections. Don't leave all your controls on the form, place them inside panels. At Design-time you'll need to have the panels "fully expanded", but then at runtime you manipulate the panels' left, top, width, height, and maybe even the alignment and anchors properties, through code. You could use a timer as suggested by #Adam Rackis.. or you could change the increment value to alter the speed of the animation. The animation itself is just a loop that starts with x = x1 and ends with x = x2, where x = x + increment_value inside the loop. As the value of "x" changes, the component will be automatically redrawn. To get a smoother effect you might need to repaint the control (or the entire panel) on each iteration. If it runs too fast, you can either insert a delay or try to make the loop rely on a timer. I've had problems with timers for this kind of stuff, but admittedly I wasn't using C#.NET at the time (I did it in Delphi). It takes a lot of fiddling with the fine details to get this working nicely, so be patient, it's not Flash! Good luck.
In C# WinForms - I am drawing a line chart in real-time that is based on data received via serial port every 500 ms.
The e.Graphics.DrawLine logic is within the form's OnPaint handler.
Once I receive the data from the serial port, I need to call something that causes the form to redraw so that the OnPaint handler is invoked. I have tried this.Refresh and this.Invalidate, and what happens is that I lose whatever had been drawn previously on the form.
Is there another way to achieve this without losing what has been drawn on your form?
The point is that you should think about storing your drawing data somewhere. As already said, a buffer bitmap is a solution. However, if you have not too much to draw, sometimes it is easier and better to store your drawing data in a variable or an array and redraw everything in the OnPaint event.
Suppose you receive some point data that should be added to the chart. Firs of all you create a point List:
List<Point> points = new List<Point>();
Then each time you get a new point you add it to the list and refresh the form:
points.Add(newPoint);
this.Refresh();
In the OnPaint event put the following code:
private void Form_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawLines(Pens.Red, points);
}
This works quite fast up to somehow 100 000 points and uses much less memory than the buffer bitmap solution. But you should decide which way to use according to the drawing complexity.
As rerun said, you need to buffer your form (since it appears that you are discarding the data after you draw it).
This is basically how I would do it:
private Bitmap buffer;
// When drawing the data:
if (this.buffer == null)
{
this.buffer = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
}
// then draw on buffer
// then refresh the form
this.Refresh();
protected override void OnPaint(PaintEventArgs e)
{
if (this.buffer != null)
{
e.Graphics.DrawImage(this.buffer);
}
}
That said, you probably want to cache your data so you can change the size of the buffer when the form size changes and then redraw the old data on it.
The solution may be this.Invalidate()
the default way to handle this is to create a memory bitmap and draw on that then set the image property of the picture box to the memory bitmap.
You will need to store historical data somewhere and just repaint it.
That will be much easier than say caching and clipping bitmaps.
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.
happy holidays!
i have a tablelayoutpanel (10x10). within each cell i have a picturebox which are disabled (enabled = false).
i am trapping mouse move over the table to catch mouse movement. here is the code:
private void tableLayoutPanelTest_MouseMove(object sender, MouseEventArgs e)
{
if (!placeShip)
{
c = tableLayoutPanelTest.GetControlFromPosition(homeLastPosition.Column, homeLastPosition.Row);
if (c.GetType() == typeof(PictureBox))
{
PictureBox hover = new PictureBox();
hover = (PictureBox)(c);
hover.Image = Properties.Resources.water;
}
Point p = tableLayoutPanelTest.PointToClient(Control.MousePosition);
Control picControl = tableLayoutPanelTest.GetChildAtPoint(p);
if (picControl != null)
{
TableLayoutPanelCellPosition me = tableLayoutPanelTest.GetCellPosition(picControl);
if (picControl.GetType() == typeof(PictureBox))
{
PictureBox thisLocation = new PictureBox();
thisLocation = (PictureBox)(picControl);
thisLocation.Image = Properties.Resources.scan;
homeLastPosition = me;
}
}
}
toolTipApp.SetToolTip(tableLayoutPanelTest, tableLayoutPanelTest.GetCellPosition(c).ToString());
}
when i run this the tooTipApp starts consuming upto 56% of the CPU. so there is something wrong.
also the picturebox image changing code stops working for some reason.
any help is very welcome!
thank you.
A few thoughts:
You're creating another PictureBox called hover - why? This code doesn't seem to do anything and it's almost certainly going to slow the loop down. I think you meant to just declare hover and cast it from c, but you're actually creating a new PictureBox instance and just throwing it away.
You're also never disposing of hover, as far as I can tell - so you end up allocating tons of memory and window handles. In general you should avoid creating new objects at all inside a MouseMove handler (small ones like hit tests are sometimes OK). As with the previous point - you probably didn't mean to write the new PictureBox().
You use PointToClient(Control.MousePosition) when the MouseMove event already gives you the control-specific mouse position (e.X and e.Y). This is costing you more time than it should.
Probably the most important, you're invoking SetToolTip on every MouseMove. You should only be invoking this when the tooltip has actually changed. You need to set a flag on which cell or control the tooltip was last displayed for, check for changes, then call SetToolTip.
You will get a lot of performance back if you avoid setting the tooltip text when it hasn't changed.
Other than that I want to echo the comment. This is a lot of processing for a mouse handler.
You should be trying to do an early test to see if the mouse is still over the same thing it was on the last move, and skipping most of the code in that case.