使用CreateGraphics而不是Paint事件处理程序进行自定义绘图时绘制毛刺

adv*_*v12 6 c# winforms

我写了一个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

TL; DR:

不要为了响应一次性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)

但为什么?我做错了什么,这是如何解决的?

看一下Control.CreateGraphics文档:

在处理当前Windows消息后,通常不应保留通过CreateGraphics方法检索的Graphics对象,因为使用下一个WM_PAINT消息将删除使用该对象绘制的任何内容.

Windows不保留您绘制的图形Control.相反,它确定了控件需要重新绘制的情况,并通过WM_PAINT消息通知它.然后由你的控制重新绘制自己.这种情况发生在OnPaint方法中,如果您是子类Control或其子类之一,则可以覆盖该方法.如果您不是子类,您仍然可以通过处理公共Paint事件来执行自定义绘制,控件将在其OnPaint方法结束时触发.这是你想要挂钩的地方,以确保每次Control告诉重绘时你的图形都会重新绘制.否则,部分或全部控件将被涂到控件的默认外观上.

当全部或部分控件无效时,重新发生.您可以通过调用使整个控件无效,请求完全重绘Control.Invalidate().其他情况可能只需要部分重绘.如果Windows确定只Control需要重新绘制部分内容,则PaintEventArgs您收到的内容将为非空ClipRegion.在这种情况下,ClipRegion即使您尝试绘制到该区域之外的区域,您的绘图也只会影响该区域.这就是drawPanel.Invalidate()上面例子中需要调用的原因.由于在drawPanel扩展窗口时需要随控件大小而改变的外观以及控件的新部分无效,因此需要对每个调整大小请求完全重新绘制.