如何调用绘图事件?

60
我的程序在面板上绘制文本,但如果我想删除文本,我必须重新绘制。如何手动调用(触发)绘画事件?
8个回答

107
在您的Form或Control的方法中,您有3个选择:
this.Invalidate();  // request a delayed Repaint by the normal MessageLoop system    
this.Update();      // forces Repaint of invalidated area 
this.Refresh();     // Combines Invalidate() and Update()

通常情况下,您只需要调用Invalidate(),然后让系统将其与其他屏幕更新合并。如果您很匆忙,可以调用Refresh(),但这样做可能会由于其他控件(特别是父控件)无效而使其连续重绘多次。

Windows(Win32和WinForms.Net)处理此问题的常规方式是等待消息队列为空,然后处理所有失效的屏幕区域。这样做是有效的,因为当某些变化发生时,通常会导致其他内容(控件)也发生变化。

Update()最常见的场景是在for循环中更改属性(比如label1.Text,它会使Label失效),而该循环会暂时阻塞消息循环。每当使用它时,您都应该问自己是否应该使用线程。但答案并不总是肯定的。


9
我来澄清一下。Update()会立即重新绘制任何已失效的控件部分,但您仍然需要先使其失效。Refresh()将失效并立即重绘整个控件。当我编写一个自行绘制的控件时,我会使用带有脏矩形的Invalidate()方法,然后为了响应性调用Update()方法,并避免使用Refresh()方法以提高性能。 - snarf
2
请注意,根据您使用的方法,CPU性能差异可能非常大。this.Invalidate()需要最少的CPU资源,因为您没有强制执行标准消息循环之外的操作,如果您可以调用this.Invalidate(false),那么只在主控件/窗体上调用OnPaint()而不是子控件,这一点尤其正确。对于我的一个用户控件,从this.Update()this.Invalidate(false)的转换将CPU负载降低了4倍。 - Special Sauce

51

6
我发现Invalidate()会造成太多的闪烁。这是我的情况:我正在开发一个自定义控件,通过处理Paint事件绘制了它的全部内容
this.Paint += this.OnPaint;

这个处理程序调用一个自定义例程来进行实际的绘制。
private void OnPaint(object sender, PaintEventArgs e)
{
    this.DrawFrame(e.Graphics);
}

为了模拟滚动,我希望在鼠标左键按下并移动时每次重绘控件。我的第一选择是使用Invalidate(),如下所示。
private void RedrawFrame()
{
    var r = new Rectangle(
        0, 0, this.Width, this.Height);

    this.Invalidate(r);
    this.Update();
}

控件可以滚动,但是闪烁超出了任何舒适的水平。所以我决定,在处理MouseMove事件后直接调用我的自定义DrawFrame()方法,而不是重新绘制控件。这样就可以实现平滑滚动,没有闪烁。
private void RedrawFrame()
{
    var g = Graphics.FromHwnd(this.Handle);
    this.DrawFrame(g);
}

这种方法可能并不适用于所有情况,但目前它很适合我。

1
小心,我认为FromHwnd()的结果应该再次被Dispose(将其放入using() {}中) - H H
6
使用双缓冲来消除闪烁。 - TheRealChx101
@TheRealChx101 我只能稍微减少闪烁。 - Abdel Aleem

4

我认为你也可以调用Refresh()方法。


2

也许这是一个老问题,这就是为什么这些答案对我没有用的原因...使用Visual Studio 2019,在进行一些调查后,我找到了以下解决方案:

this.InvokePaint(this, new PaintEventArgs(this.CreateGraphics(), this.DisplayRectangle));

1
调用control.invalidate,将会触发paint事件。

1
私有 void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e) { System.Drawing.Graphics graphics = this.CreateGraphics(); System.Drawing.Rectangle rectangle = new System.Drawing.Rectangle(0, 0, 50, 50); graphics.DrawEllipse(System.Drawing.Pens.Black, rectangle); graphics.DrawRectangle(System.Drawing.Pens.Red, rectangle); } 这个绘画方法是在什么时候被调用来重新绘制窗体的?它确实会重新绘制窗体,不是吗? - Doug Hauf

1

根据上下文,刷新可能会使代码更易读。


-2

使用 Control.InvokePaint,您还可以将其用于手动双缓冲。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接