【问题标题】:Drawing glitches when using CreateGraphics rather than Paint event handler for custom drawing使用 CreateGraphics 而不是 Paint 事件处理程序进行自定义绘图时的绘图故障
【发布时间】:2015-05-24 02:25:37
【问题描述】:

我编写了一个 Windows 窗体应用程序,在其中使用 Control.CreateGraphics()Panel 上进行自定义绘图。这是我的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 来指导用户,因为这是一个常见的场景,如果你还不知道问题的原因很难搜索到.

【问题讨论】:

  • 对于一个规范的问答,你不觉得所有的图像都有点多吗?问题本身可以在此处认真精简,将重点放在答案上。这是一个很容易描述的问题——我画了一些东西,但它消失了。
  • 好吧,我认为它们有助于说明常见症状,但您可以随意投反对票。
  • 拒绝投票并不能解决问题。我以前没有发现这个问题,但这是我需要开始使用的东西......经常。有很多个问题需要作为重复的问题关闭,但令我困扰的是这个问题太长了,使本来应该是一个简单问题的问题变得不必要地复杂化。很难获得人们想要的信息。我本来只是编辑它,但我注意到即使您打算将其作为规范的问答,您也没有将其设为社区 wiki,因此我不想强加于人。

标签: c# winforms


【解决方案1】:

TL;DR:

不要使用Control.CreateGraphics 响应一次性 UI 事件来进行绘图。相反,为您要绘制的控件注册一个Paint 事件处理程序,并使用通过PaintEventArgs 传递的Graphics 对象进行绘制。

如果您只想在单击按钮后进行绘制(例如),请在您的 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的外观需要随着控件的大小而改变,并且只有控件的新部分在窗口展开时才会失效,所以每次resize都需要请求一个full repaint。

【讨论】:

  • +1。这是我一直在寻找的好信息。我对CreateGraphics 有过怀疑,现在我更了解绘图在 Winforms 中的工作原理了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多