C#:Windows窗体:什么可能导致Invalidate()不重绘?

Nic*_*ner 12 c# winforms

我正在使用Windows窗体.很长一段时间,pictureBox.Invalidate();努力重新绘制屏幕.但是,它现在不起作用,我不知道为什么.

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);
Run Code Online (Sandbox Code Playgroud)

在我的代码中调用以适当地绘制世界:

view.DrawWorldBox(worldBox, canvas, gameEngine.GameObjectManager.Controllers, 
    selectedGameObjects, LevelEditorUtils.PREVIEWS);
Run Code Online (Sandbox Code Playgroud)

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();
}
Run Code Online (Sandbox Code Playgroud)

我在这里做错了什么?

Aar*_*ght 19

要理解这一点,您必须对操作系统级别的工作方式有所了解.

绘制Windows控件以响应WM_PAINT消息.当他们收到此消息时,他们会绘制自己无效的部分.可以使特定控件无效,并且可以使控件的特定区域无效,这样做是为了最小化重新绘制的量.

最终,Windows将看到某些控件需要重新绘制并向其发出WM_PAINT消息.但它只在处理完所有其他消息后执行此操作,这意味着Invalidate不会强制立即重绘. Refresh技术上应该,但并不总是可靠的.(更新:这是因为Refreshvirtual和有在重写此方法的使用不正确的实施野生某些控件.)

有一个方法,通过发出强制立即油漆WM_PAINT的消息,那就是Control.Update.因此,如果您想强制立即重绘,请使用:

control.Invalidate();
control.Update();
Run Code Online (Sandbox Code Playgroud)

即使UI仍在处理消息,这也将始终重绘控件,无论发生什么.从字面上看,我相信它使用的是SendMessageAPI,而不是PostMessage将绘制的力同步完成,而不是在长消息队列的末尾抛出它.

  • @MusiGenesis:我运行了一个快速测试,看到了与`Refresh` vs.`Invalidate/Update`相同的行为.这让我感到困扰,因为我以前是*积极的*我之前发现了差异.所以我在Reflector中打开它,看到`Control.Refresh`实际上是一个`Invalidate`,然后是`Update`.然后它突然出现在我身上:`Refresh`方法是`virtual`.所以有时它不起作用,因为控制作者会覆盖它并将其搞砸.`Refresh`应该*大多数时间*,但如果没有,你通常可以用一个明确的`Invalidate/Update`来修复它. (5认同)