使用 CreateGraphics 而不是 Paint 事件处理程序进行自定义绘图时的绘图故障 [英] Drawing glitches when using CreateGraphics rather than Paint event handler for custom drawing

查看:18
本文介绍了使用 CreateGraphics 而不是 Paint 事件处理程序进行自定义绘图时的绘图故障的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

自定义绘图在Draw!"的Click 事件处理程序的顶部面板上执行按钮.这是我的按钮点击处理程序:

private void drawButton_Click(object sender, EventArgs e){使用(图形 g = drawPanel.CreateGraphics()){g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;g.Clear(颜色.白色);大小大小 = drawPanel.ClientSize;矩形边界 = drawPanel.ClientRectangle;bounds.Inflate(-10, -10);g.FillEllipse(Brushes.LightGreen, bounds);g.DrawEllipse(Pens.Black, bounds);}}

点击drawButton后,表单如下所示:

成功!

但是当我通过拖动一个角来缩小表单时...

...并将其扩展回原来的大小,

我画的部分不见了!

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

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

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

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

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

解决方案

TL;DR:

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

如果您只想在单击按钮后进行绘制(例如),请在您的 Click 处理程序中设置一个布尔标志,指示该按钮已被单击,然后调用 Control.Invalidate().然后在 Paint 处理程序中有条件地进行渲染.

最后,如果你的控件的内容应该随着控件的大小而改变,注册一个 Resize 事件处理程序并在那里调用 Invalidate().

示例代码:

private bool _doCustomDrawing = false;私有无效 drawPanel_Paint(对象发送者,PaintEventArgs e){如果(_doCustomDrawing){图形 g = e.Graphics;g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;g.Clear(颜色.白色);大小大小 = drawPanel.ClientSize;矩形边界 = 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 = 真;drawPanel.Invalidate();}私有无效 drawPanel_Resize(对象发送者,EventArgs e){drawPanel.Invalidate();}

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

查看 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的外观需要随着控件的大小而变化,并且在窗口展开时只有控件的新部分失效,因此每次调整大小都需要请求完整重绘.>

I've written a Windows Forms app where I do custom drawing on a Panel using Control.CreateGraphics(). Here's what my Form looks like at startup:

The custom drawing is performed on the top panel in the Click event handler of the "Draw!" button. Here's my button click handler:

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);
    }
}

After a click on drawButton, the form looks like this:

Success!

But when I shrink the form by dragging a corner...

...and expand it back to its original size,

part of what I drew is gone!

This also happens when I drag part of the window offscreen...

...and drag it back onscreen:

If I minimize the window and restore it, the whole image is erased:

What is causing this? How can I make it so the graphics I draw are persistent?

Note: I've created this self-answered question so I have a canonical Q/A to direct users to, as this is a common scenario that's hard to search for if you don't already know the cause of the problem.

解决方案

TL;DR:

Don't do your drawing in response to a one-time UI event with Control.CreateGraphics. Instead, register a Paint event handler for the control on which you want to paint, and do your drawing with the Graphics object passed via the PaintEventArgs.

If you want to paint only after a button click (for example), in your Click handler, set a boolean flag indicating that the button has been clicked and then call Control.Invalidate(). Then do your rendering conditionally in the Paint handler.

Finally, if your control's contents should change with the size of the control, register a Resize event handler and call Invalidate() there too.

Example code:

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();
}

But why? What was I doing wrong, and how does this fix it?

Take a look at the documentation for Control.CreateGraphics:

The Graphics object that you retrieve through the CreateGraphics method should not normally be retained after the current Windows message has been processed, because anything painted with that object will be erased with the next WM_PAINT message.

Windows doesn't take responsibility for retaining the graphics you draw to your Control. Rather, it identifies situations in which your control will require a repaint and informs it with a WM_PAINT message. Then it's up to your control to repaint itself. This happens in the OnPaint method, which you can override if you subclass Control or one of its subclasses. If you're not subclassing, you can still do custom drawing by handling the public Paint event, which a control will fire near the end of its OnPaint method. This is where you want to hook in, to make sure your graphics get redrawn every time the Control is told to repaint. Otherwise, part or all of your control will be painted over to the control's default appearance.

Repainting happens when all or part of a control is invalidated. You can invalidate the entire control, requesting a full repaint, by calling Control.Invalidate(). Other situations may require only a partial repaint. If Windows determines that only part of a Control needs to be repainted, the PaintEventArgs you receive will have a non-empty ClipRegion. In this situation, your drawing will only affect the area in the ClipRegion, even if you try to draw to areas outside that region. This is why the call to drawPanel.Invalidate() was required in the above example. Because the appearance of drawPanel needs to change with the size of the control and only the new parts of the control are invalidated when the window is expanded, it's necessary to request a full repaint with each resize.

这篇关于使用 CreateGraphics 而不是 Paint 事件处理程序进行自定义绘图时的绘图故障的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆