我编写了一个Windows窗体应用程序,我使用Panel
在Control.CreateGraphics()
上自定义绘图。这是我的Form
在启动时的样子:
自定义绘图在“Draw!”的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);
}
}
点击drawButton
后,表单如下所示:
成功!
但是当我通过拖动角落缩小形状时......
...并将其展开回原来的尺寸,
我画的部分内容已经消失了!
当我将部分窗口拖到屏幕外时也会发生这种情况......
...然后将其拖回屏幕:
如果我最小化窗口并将其恢复,整个图像将被删除:
造成这种情况的原因是什么?我怎样才能使我绘制的图形持久?
注意:我已经创建了这个自我回答的问题,所以我有一个规范的Q / A来引导用户,因为如果您还不知道问题的原因,这是一个很难搜索的常见情况
答案 0 :(得分:9)
不要使用Control.CreateGraphics
来回应一次性UI事件。相反,为要绘制的控件注册一个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();
}
查看documentation for Control.CreateGraphics:
通过CreateGraphics方法检索的Graphics对象通常不会在处理完当前Windows消息后保留,因为用该对象绘制的任何内容都将被下一个WM_PAINT消息擦除。
Windows不会保留您绘制到Control
的图形。相反,它确定了控件需要重新绘制的情况,并通过WM_PAINT消息通知它。然后由你的控制重新绘制自己。这发生在OnPaint
方法中,如果您继承Control
或其子类之一,则可以覆盖该方法。如果您不是子类,您仍然可以通过处理公共Paint
事件来执行自定义绘制,控件将在其OnPaint
方法的末尾附近触发。这是您想要挂钩的地方,以确保每次Control
被告知重绘时都会重新绘制图形。否则,部分或全部控件将被涂到控件的默认外观上。
当控件的全部或部分无效时重新发生。您可以通过调用Control.Invalidate()
使整个控件无效,请求完整重绘。其他情况可能只需要部分重绘。如果Windows确定只需要重新绘制Control
的一部分,则您收到的PaintEventArgs
将具有非空ClipRegion
。在这种情况下,即使您尝试绘制到该区域之外的区域,您的绘图也只会影响ClipRegion
中的区域。这就是上述示例中需要调用drawPanel.Invalidate()
的原因。由于drawPanel
的外观需要随控件的大小而变化,并且在展开窗口时只有控件的新部分无效,因此需要对每个调整大小请求完全重新绘制。