使用CreateGraphics而不是Paint事件处理程序来绘制自定义绘图时的毛刺
我写了一个Windows窗体应用程序,我使用Control.CreateGraphics()在Panel上自定义绘图。 这是我的Form在启动时的样子:

自定义绘图是在“绘制!”的Click事件处理程序的顶部面板上执行的。 button。 这是我的button点击处理程序:
private void drawButton_Click(object sender, EventArgs e) { using (Graphics g = drawPanel.CreateGraphics()) { g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; g.Clear(Color.White); Size size = drawPanel.ClientSize; Rectangle bounds = drawPanel.ClientRectangle; bounds.Inflate(-10, -10); g.FillEllipse(Brushes.LightGreen, bounds); g.DrawEllipse(Pens.Black, bounds); } }
点击drawButton ,表单看起来像这样:

成功!
但是当我通过拖动一个angular来缩小表格的时候…

…并将其扩大回原来的大小,

我画的一部分没了!
当我将部分窗口拖离屏幕时,也会发生这种情况。

…并将其拖回屏幕上:

如果我最小化窗口并恢复它,整个图像被删除:

这是什么原因造成的? 我怎样才能做到这一点,所以我画的graphics是持久的?
注:我已经创build了这个自我回答的问题,所以我有一个规范的Q / A来引导用户,因为如果你不知道问题的原因,这是一个很难search的常见情况。
TL; DR:
不要使用Control.CreateGraphics来响应一次性的UI事件。 而是为要绘制的控件注册Paint事件处理程序,并使用通过PaintEventArgs传递的Graphics对象进行绘制。
如果只想在点击button(例如)之后绘制,请在Click处理程序中设置一个布尔型标志,指示该button已被单击,然后调用Control.Invalidate() 。 然后在Paint处理程序中有条件地执行渲染。
最后,如果你的控件的内容应该随着控件的大小而改变,那么注册一个Resize事件处理器并且调用Invalidate()。
示例代码:
private bool _doCustomDrawing = false; private void drawPanel_Paint(object sender, PaintEventArgs e) { if (_doCustomDrawing) { Graphics g = e.Graphics; g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; g.Clear(Color.White); Size size = drawPanel.ClientSize; Rectangle bounds = drawPanel.ClientRectangle; bounds.Inflate(-10, -10); g.FillEllipse(Brushes.LightGreen, bounds); g.DrawEllipse(Pens.Black, bounds); } } private void drawButton_Click(object sender, EventArgs e) { _doCustomDrawing = true; drawPanel.Invalidate(); } private void drawPanel_Resize(object sender, EventArgs e) { drawPanel.Invalidate(); }
但为什么? 我做错了什么,这是怎么解决的呢?
看看Control.CreateGraphics的文档 :
通过CreateGraphics方法检索的graphics对象通常不应当在当前的Windows消息处理后保留,因为用该对象绘制的任何内容都将被删除,并显示下一条WM_PAINT消息。
Windows不负责保留您绘制到您的Control的graphics。 而是确定您的控件需要重绘的情况,并用WM_PAINT消息通知它。 然后,您可以自行重新绘制。 这发生在OnPaint方法中,如果您inheritance了Control或其子类的子类,则可以覆盖该方法。 如果您不是inheritance类,则仍然可以通过处理公共Paint事件来执行自定义绘图,该事件在控件的OnPaint方法结束时触发。 这是你想要挂钩的地方,以确保每次重新绘制Control重新绘制graphics。 否则,部分或全部控件将被绘制到控件的默认外观上。
全部或部分控件失效时重新进行重绘 。 通过调用Control.Invalidate() ,可以使整个控件失效,请求完全重绘。 其他情况可能只需要部分重绘。 如果Windows确定只有部分Control需要重新绘制,则您收到的PaintEventArgs将具有非空的ClipRegion 。 在这种情况下,即使您尝试绘制区域以外的区域,您的绘图也只会影响ClipRegion中的区域。 这就是为什么在上面的例子中需要调用drawPanel.Invalidate() 。 由于drawPanel的外观需要随着控件的大小而改变,并且在窗口展开时只有控件的新部分是无效的,所以有必要请求每个resize的完整重绘。