我写了一个Windows窗体应用程序,我在其中Panel使用自定义绘图Control.CreateGraphics().这是我Form在创业时的样子:

自定义绘图Click在"绘图!" 的事件处理程序的顶部面板上执行.按钮.这是我的按钮点击处理程序:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
单击后drawButton,表单如下所示:

成功!
但是当我通过拖动角落缩小形状时......

...并将其扩展回原始大小,

我画的部分内容已经消失了!
当我将部分窗口拖到屏幕外时也会发生这种情况......

...然后将其拖回屏幕:

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

是什么造成的?我怎样才能使我绘制的图形持久存在?
注意:我已经创建了这个自我回答的问题,所以我有一个规范的Q/A来引导用户,因为如果您还不知道问题的原因,这是一个很难搜索的常见场景.
adv*_*v12 10
不要为了响应一次性UI事件而绘图Control.CreateGraphics.相反,Paint为要绘制的控件注册一个事件处理程序,并使用Graphics通过该对象传递的对象进行绘制PaintEventArgs.
如果您只想在按钮单击(例如)之后进行绘制,请在Click处理程序中设置一个布尔标志,指示该按钮已被单击,然后调用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();
}
Run Code Online (Sandbox Code Playgroud)
在处理当前Windows消息后,通常不应保留通过CreateGraphics方法检索的Graphics对象,因为使用下一个WM_PAINT消息将删除使用该对象绘制的任何内容.
Windows不保留您绘制的图形Control.相反,它确定了控件需要重新绘制的情况,并通过WM_PAINT消息通知它.然后由你的控制重新绘制自己.这种情况发生在OnPaint方法中,如果您是子类Control或其子类之一,则可以覆盖该方法.如果您不是子类,您仍然可以通过处理公共Paint事件来执行自定义绘制,控件将在其OnPaint方法结束时触发.这是你想要挂钩的地方,以确保每次Control告诉重绘时你的图形都会重新绘制.否则,部分或全部控件将被涂到控件的默认外观上.
当全部或部分控件无效时,重新发生.您可以通过调用使整个控件无效,请求完全重绘Control.Invalidate().其他情况可能只需要部分重绘.如果Windows确定只Control需要重新绘制部分内容,则PaintEventArgs您收到的内容将为非空ClipRegion.在这种情况下,ClipRegion即使您尝试绘制到该区域之外的区域,您的绘图也只会影响该区域.这就是drawPanel.Invalidate()上面例子中需要调用的原因.由于在drawPanel扩展窗口时需要随控件大小而改变的外观以及控件的新部分无效,因此需要对每个调整大小请求完全重新绘制.