Redrawing during drag and drop - c#

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.

Related

How can I draw a big image in WinForms?

I'm trying to make a floor for a top down style game, where I used to use pictureboxes
I was told that instead of using a Picturebox, I should be using the Graphics.DrawImage(); method, which seems to slightly help but still is very laggy.
My paint function looks like this to draw the background looks like this:
How should I make the drawing less laggy? The image is 2256 by 1504
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.DrawImage(PeanutToTheDungeon.Properties.Resources.tutorialFloor, 0, 0);
}
There are two points that I would make here.
Firstly, DO NOT use Properties.Resources like that. Every time you use a property of that object, the data for that resource gets extracted from the assembly. That means that you are creating a new Image object every time the Paint event is raised, which is often. You should use that resource property once and once only, assign the value to a field and then use that field repeatedly. That means that you will save the time of extracting that data and creating the Image object every time.
The second point may or may not be applicable but, if you are forcing the Paint event to be raised by calling Invalidate or Refresh then you need to make sure that you are invalidating the minimum area possible. If you call Refresh or Invalidate with no argument then you are invalidating the whole for, which means that the whole form will be repainted. The repainting of pixels on screen is actually the slow part of the process, so that's the part that you need to keep to a minimum. That means that, when something changes on your game board, you need to calculate the smallest area possible that will contain all the possible changes and specify that when calling Invalidate. That will reduce the amount of repainting and speed up the process.
Note that you don't need to change your drawing code. You always DRAW everything, then the system will PAINT the part of the form that you previously invalidated.

Invalidate without OnPaint

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.

Custom control border doesn't show if Update() is called, but shows if Refresh() is called

I've drawn a border around a Custom Control, but now when I try to draw a small rectangle inside of the Custom Control after (or even before) drawing the initial border, it does not draw/get displayed on the Control when the mouse is moving over the Control.
I put a Console.WriteLine(...) in the OnPaint method to see if it's being hit, but nothing is output to the Console/Output Pane. The only time it output anything was when the Control first loaded. But then if the else clause is being hit, why doesn't the small rectangle get drawn when the mouse is moving over the Control?
You can safely ignore anything inside the Borders region, thus the code is not shown.
namespace JTS.Controls
{
public partial class ListBox : Control
{
public enum ListBoxBorders
{
Top,
Bottom,
Left,
Right,
All,
None
}
ListBoxBorders SelectedListBoxBorder = ListBoxBorders.All;
[Browsable(true)]
[Description("Defines which borders are drawn onto the ListBox."), Category("Appearance")]
public ListBoxBorders ListBoxBorder
{
get { return SelectedListBoxBorder; }
set { SelectedListBoxBorder = value; }
}
public enum BorderStyles
{
Top,
Bottom,
Left,
Right,
All,
None
}
Graphics graphics;
protected bool mouseMoving;
public ListBox()
{
InitializeComponent();
}
private void ListBox_MouseMove(object sender, MouseEventArgs e)
{
Console.WriteLine("Handled.");
mouseMoving = true;
this.Update(); // it works if Refresh is called
}
protected override void OnPaint(PaintEventArgs pe)
{
graphics = pe.Graphics;
#region Borders
// logic to draw right-edge border.
#endregion
if (mouseMoving)
{
// ************************************
// THIS PART NEVER GETS EXECUTED.
// ************************************
Console.WriteLine(mouseMoving.ToString());
graphics.DrawRectangle(new Pen(
Color.Green, 1), new Rectangle(
5, 5, 50, 50));
}
else
{
Console.WriteLine(mouseMoving.ToString());
}
}
}
}
Edit:
After I change this.Update to this.Refresh, everything works. Can somebody explain why?
You need to understand what's the difference and relationship among Invalidate, Update and Refresh. Here is a very good blog about it, you can read my summary if you are lazy :)
In short: Invaidate only invalidates the client area, but never update it; Update will update the invalidated client area to the new look; Refresh essentially ends up with calling Invalidate followed by Update.
Now let's discuss things in details.
Before we look into your issue, let's look into how winforms control get painted.
All controls paint actually responses to WM_PAINT messages. As a short summary, this message is sent in the following conditions:
UpdateWindow or RedrawWindow is called, or
DispatchMessage dispatches a WM_PAINT from the message queue.
When a control gets a WM_PAINT message, it paints its background followed by the foreground if necessary, and then the OnPaint event is fired to perform user-defined custom painting.
With the background, let's talk about Invalidate, Update and Refresh.
Invalidate ... essentially ends up calling one of the RedrawWindow, InvaliateRect or InvalidateRgn functions. If RedrawWindow is called then this may result in a WM_PAINT message being posted to the application message queue (to invalidate the child controls).
But noted here, the function only "invalidate" or "dirty" the client area by adding it to the current update region of the window of the control. This invalidated region, along with all other areas in the update region, is marked for painting when the next WM_PAINT message is received. As a result you may not see your control refreshing (and showing the invalidation) immediately (or synchronously).
Update calls UpdateWindow function which updates the client area of the control by sending WM_PAINT message to the window (of the control) if the window's update region is not empty. This function sends a WM_PAINT directly to WndProc() bypassing the application message queue.
Thus, if the window update region is previously “invalidated” then calling “update” would immediately "update" (and cause repaint) the invalidation.
Finally, Refresh chained functions above together: it calls Invalidate to invalidate the area, then calls Update to force a refresh.
Well, after posting my question, I thought to myself, "It may have something to do with calling this.Update();. I don't know why, but I changed it to this.Refresh(); and it is now drawing the smaller rectangle as and when expected.

C# - Windows forms - Force repaint as though the off-screen window has moved

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
}

Paint event handler stops executing after a few iterations

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

Categories

Resources