Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C#: Windows Forms: What could cause Invalidate() to not redraw?

Tags:

c#

winforms

I'm using Windows Forms. For a long time, pictureBox.Invalidate(); worked to make the screen be redrawn. However, it now doesn't work and I'm not sure why.

this.worldBox = new System.Windows.Forms.PictureBox();
this.worldBox.BackColor = System.Drawing.SystemColors.Control;
this.worldBox.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.worldBox.Location = new System.Drawing.Point(170, 82);
this.worldBox.Name = "worldBox";
this.worldBox.Size = new System.Drawing.Size(261, 250);
this.worldBox.TabIndex = 0;
this.worldBox.TabStop = false;
this.worldBox.MouseMove += new 
    System.Windows.Forms.MouseEventHandler(this.worldBox_MouseMove);
this.worldBox.MouseDown += new 
    System.Windows.Forms.MouseEventHandler(this.worldBox_MouseDown);
this.worldBox.MouseUp += new 
    System.Windows.Forms.MouseEventHandler(this.worldBox_MouseUp);

Called in my code to draw the world appropriately:

view.DrawWorldBox(worldBox, canvas, gameEngine.GameObjectManager.Controllers, 
    selectedGameObjects, LevelEditorUtils.PREVIEWS);

View.DrawWorldBox:

public void DrawWorldBox(PictureBox worldBox,
    Panel canvas,
    ICollection<IGameObjectController> controllers,
    ICollection<IGameObjectController> selectedGameObjects,
    IDictionary<string, Image> previews)
{
    int left = Math.Abs(worldBox.Location.X);
    int top = Math.Abs(worldBox.Location.Y);
    Rectangle screenRect = new Rectangle(left, top, canvas.Width, 
        canvas.Height);

    IDictionary<float, ICollection<IGameObjectController>> layers = 
        LevelEditorUtils.LayersOfControllers(controllers);
    IOrderedEnumerable<KeyValuePair<float, 
        ICollection<IGameObjectController>>> sortedLayers 
            = from item in layers
              orderby item.Key descending
              select item;

    using (Graphics g = Graphics.FromImage(worldBox.Image))
    {
        foreach (KeyValuePair<float, ICollection<IGameObjectController>> 
        kv in sortedLayers)
        {
            foreach (IGameObjectController controller in kv.Value)
            {
                // ...

                float scale = controller.View.Scale;
                float width = controller.View.Width;
                float height = controller.View.Height;
                Rectangle controllerRect = new 
                    Rectangle((int)controller.Model.Position.X,
                    (int)controller.Model.Position.Y,
                    (int)(width * scale),
                    (int)(height * scale));

                // cull objects that aren't intersecting with the canvas
                if (controllerRect.IntersectsWith(screenRect))
                {
                    Image img = previews[controller.Model.HumanReadableName];
                    g.DrawImage(img, controllerRect);
                }

                if (selectedGameObjects.Contains(controller))
                {
                    selectionRectangles.Add(controllerRect);
                }
            }
        }
        foreach (Rectangle rect in selectionRectangles)
        {
            g.DrawRectangle(drawingPen, rect);
        }
        selectionRectangles.Clear();
    }
    worldBox.Invalidate();
}

What could I be doing wrong here?

like image 475
Nick Heiner Avatar asked Apr 23 '10 01:04

Nick Heiner


1 Answers

To understand this you have to have some understanding of the way this works at the OS level.

Windows controls are drawn in response to a WM_PAINT message. When they receive this message, they draw whichever part of themselves has been invalidated. Specific controls can be invalidated, and specific regions of controls can be invalidated, this is all done to minimize the amount of repainting that's done.

Eventually, Windows will see that some controls need repainting and issue WM_PAINT messages to them. But it only does this after all other messages have been processed, which means that Invalidate does not force an immediate redraw. Refresh technically should, but isn't always reliable. (UPDATE: This is because Refresh is virtual and there are certain controls in the wild that override this method with an incorrect implementation.)

There is one method that does force an immediate paint by issuing a WM_PAINT message, and that is Control.Update. So if you want to force an immediate redraw, you use:

control.Invalidate();
control.Update();

This will always redraw the control, no matter what else is happening, even if the UI is still processing messages. Literally, I believe it uses the SendMessage API instead of PostMessage which forces painting to be done synchronously instead of tossing it at the end of a long message queue.

like image 97
Aaronaught Avatar answered Oct 04 '22 15:10

Aaronaught