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

我正在使用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); 

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

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

View.DrawWorldBox:

 public void DrawWorldBox(PictureBox worldBox, Panel canvas, ICollection controllers, ICollection selectedGameObjects, IDictionary 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> layers = LevelEditorUtils.LayersOfControllers(controllers); IOrderedEnumerable<KeyValuePair<float, ICollection>> sortedLayers = from item in layers orderby item.Key descending select item; using (Graphics g = Graphics.FromImage(worldBox.Image)) { foreach (KeyValuePair<float, ICollection> 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(); } 

我在这里做错了什么?

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

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

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

有一种方法通过发出WM_PAINT消息强制立即绘制,即Control.Update 。 因此,如果您想强制立即重绘,请使用:

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

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

Invalidate()仅“使控件或表单无效”(将其标记为重新绘制),但不强制重绘。 当应用程序在消息队列中没有其他消息要处理时再次重新绘制时,它将重新绘制。 如果要强制重绘,可以使用Refresh()

在这种情况下, InvalidateRefresh将执行相同的操作,并强制重绘(最终)。 如果你没有看到任何重绘(永远),那么这意味着在DrawWorldBox根本没有绘制任何东西,或者从PictureBox图像的可见部分绘制出来的任何东西。

确保(正如您所希望的那样使用断点或记录或单步执行代码)正在将某些内容添加到selectionRectangles ,并且这些矩形中的至少一个覆盖了PictureBox的可见部分。 还要确保您正在绘制的笔与背景颜色不同。