使用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的完整重绘。