tags:

views:

330

answers:

3

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?

+1  A: 

Invalidate() only "invalidates" the control or form (marks it for repainting), but does not force a redraw. It will be redrawn as soon as the application gets around to repainting again when there are no more messages to process in the message queue. If you want to force a repaint, you can use Refresh().

Zach Johnson
Right, but it's not getting redrawn ever.
Rosarch
A: 

Invalidate or Refresh will do the same thing in this case, and force a redraw (eventually). If you're not seeing anything redrawn (ever), then that means either nothing has been drawn at all in DrawWorldBox or whatever has been drawn has been drawn off the visible part of the PictureBox's image.

Make sure (using breakpoints or logging or stepping through the code, as you prefer) that something is being is being added to selectionRectangles, and that at least one of those rectangles covers the visible part of the PictureBox. Also make sure the pen you're drawing with doesn't happen to be the same color as the background.

MusiGenesis
+3  A: 

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.

Aaronaught
You've observed a difference between Refresh and Invalidate/Update? I've never even seen much difference between Refresh and plain ol' Invalidate. According to MSDN, Update sends WM_PAINT directly to the window, while Invalidate puts the message in the application queue. In practice I've never seen this make any observable difference, even in high-speed animation.
MusiGenesis
@MusiGenesis: I ran a quick test and saw the same behaviour with `Refresh` vs. `Invalidate/Update`. This bothered me because I was *positive* that I'd observed a difference before. So I opened it up in Reflector and saw that `Control.Refresh` is, literally, an `Invalidate` followed by an `Update`. Then it dawned on me: The `Refresh` method is `virtual`. So sometimes it doesn't work because control authors override it and screw it up. `Refresh` should work *most* of the time, but if it doesn't, you can usually fix it with an explicit `Invalidate/Update`.
Aaronaught
@MusiGenesis: By the way, if you want to observe the difference between `Invalidate` and `Refresh` (or `Invalidate/Update`), just throw a text box onto the form, and in an event handler, write `textBox1.BackColor = Color.Red` followed by `Invalidate` then `Thread.Sleep(1000)` - you'll see that the update doesn't happen until the event handler is finished executing. If you're in the middle of a long-running operation and haven't used a `BackgroundWorker` or worker thread, you need to "force" updates this way.
Aaronaught
@Aaronaught: I wrote a long response and then realized I was just being pointlessly annoying (as in my initial comment here). I'll just say that the guy's using a `PictureBox`, so chances are his problem isn't an incorrectly-implemented Refresh method. Also, I realized that the reason I didn't see Refresh vs. Invalidate differences in my animation is that I wasn't using either method (since `BitBlt` doesn't require it).
MusiGenesis
@MusiGenesis: I didn't find it annoying, if there's a technical error in one of my answers then I want to know; small details can often mean the difference between a working solution and continued confusion. In this case, you're right, it probably doesn't make a difference. Still, answers here may be read by many people, and the "delayed invalidate" is a fairly common question/problem; the additional context may help somebody else in the future.
Aaronaught
Well, it helped me. I wasn't aware there was any difference at all between Refresh and Invalidate. I think generally the difference would only be apparent in situations where the UI is locked up, and then you've got a sucky situation anyway.
MusiGenesis