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

时间:2015-05-24 02:25:38

标签: c# winforms

我编写了一个Windows窗体应用程序,我使用PanelControl.CreateGraphics()上自定义绘图。这是我的Form在启动时的样子:

blank form with Draw! button

自定义绘图在“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后,表单如下所示:

form with green filled ellipse

成功!

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

same form, shrunk to partially hide ellipse

...并将其展开回原来的尺寸,

form with missing graphics on right and bottom

我画的部分内容已经消失了!

当我将部分窗口拖到屏幕外时也会发生这种情况......

form partially offscreen

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

form back onscreen but with half of graphics missing

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

blank form like first image

造成这种情况的原因是什么?我怎样才能使我绘制的图形持久?

注意:我已经创建了这个自我回答的问题,所以我有一个规范的Q / A来引导用户,因为如果您还不知道问题的原因,这是一个很难搜索的常见情况

1 个答案:

答案 0 :(得分:9)

TL; DR:

不要使用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的外观需要随控件的大小而变化,并且在展开窗口时只有控件的新部分无效,因此需要对每个调整大小请求完全重新绘制。