C# 中GDI绘制后不显示问题原因分析与解决,与控件重绘相关的方法区别

79 篇文章 22 订阅
18 篇文章 2 订阅

基本上都是因为系统自动对画板控件发送了重绘消息导致绘制方法结束后又执行了画板的重绘事件,导致了绘制失效。这种情况下要么禁止画板重绘,但是这种办法较为复杂且不一定能实现想要的绘制效果,而且系统自动重绘的机制和原因分析出来也不容易。另一种就是将绘制方法放到Paint事件里,这中情况下注意绘制方法的内容不能嵌套绘制,否则会导致无限循环递归绘制。

下面是一些参考实例

刚学GDI+画图的时候,画线就是画不出来,代码如下:

public Form1()
 
{
 
InitializeComponent();
 
Graphics g = this.CreateGraphics();
 
Pen p = new Pen(Color.Black);
 
g.DrawLine(p, 10, 10, 20, 20);
 
}
 
应改为
public Form1()
 
{
 
InitializeComponent();
 
Graphics g = this.CreateGraphics();
 
this.Show();
 
Pen p = new Pen(Color.Black);
 
g.DrawLine(p, 10, 10, 20, 20);
 
}
 
Show()方法显示窗口空间。必须让窗口立即显示,因为在其显示之前不能作任何工作。即在其显示之前画什么都是无用的。
上面程序窗体如果最小化再恢复,绘制好的图形就不见了。如果在该窗体上拖动另一个窗口,使之只遮挡一部分图形,再把该窗口拖离这个窗体,临时被遮挡的部分就消失了!原因是:如果窗体的一部分被隐藏了,Windows通常会立即删除与其中显示的内容相关的所有信息。在窗口的某一部分消失时,那些像素也就丢失了(即Windows释放了保存这些像素的内存)。
但要注意窗口的一部分被隐藏了,当它检测到窗口不再被隐藏时,就请求拥有该窗口的应用程序重新绘制其内容。这个规则有一些例外----窗口的一小部分被挡住的时间比较短(显示菜单时)。一般情况下应用程序就需要在以后重新绘制它。
由于本示例把绘图代码放在Form1的构造函数中,故不能在启动后再次调用该构造函数进行重新绘制。

使用OnPaint()绘制图形
Windows会利用Paint事件通知应用程序完成重新绘制的要求。Paint事件的Form1处理程序处理虚方法OnPaint()的调用,同时传给他一个参数PaintEventArgs。也就是说只要重写OnPaint()执行画图操作。
下面创建一个Windows应用程序DrawShapes来完成这个操作。
protected override void OnPaint(PaintEventarges e)
{
base.OnPaint(e);
Graphics dc = e.Graphics;
Pen bluePen = new Pen(Color.Blue,3);
dc.DrawRectangle(bluePen,0,0,50,50);
Pen redpen = new Pen(Color.Red,2);
dc.DrawEllipse(redPen,0,50,80.60);
}
PaintEventArgs是一个派生自EventArgs的类,一般用于传送有关事件的信息。PaintEventArgs有另外两个属性,其中一个比较重要的是Graphics实例,它们主要用于优化绘制窗口中需要绘制的部分。这样就不必调用CreateGraphics(),在OnPaint()方法中获取DC。
在完成我们的绘图后,还要调用基类OnPaint()方法,因为Windows在绘图过程中可能会执行一些他自己的工作。
这段代码的结果与前面的示例结果相同,但当最小化或隐藏它时,应用程序会正确执行。
如何是GDI+画的图最小化之后不消失呢,在bitmap中画,然后在pictureBox中显示

public Form1()
 
{
 
InitializeComponent();
 
Bitmap b = new Bitmap(50, 50);
 
Graphics g = Graphics.FromImage(b);
 
Pen p = new Pen(Color.Black);
 
g.DrawLine(p, 10, 10, 20, 20);
 
pictureBox1.Image = b;
 
g.DrawLine(p, 10, 10, 20, 200);
 
}
 
这样,图就不会消失了。

/***************C# 禁止控件重绘(绘制)

完整代码:


[DllImport("user32")]  
private static extern int SendMessage(IntPtr hwnd, int wMsg, int wParam, IntPtr lParam);  
private const int WM_SETREDRAW = 0xB;   
  
  
    //禁止pnl重绘  
    //SendMessage(SelfInfo_pnlContact1.Handle, WM_SETREDRAW, 0, IntPtr.Zero);  
  
    //允许重绘pnl  
    //SendMessage(SelfInfo_pnlContact1.Handle, WM_SETREDRAW, 1, IntPtr.Zero);  
  
参数是控件

/****************************************

C# Control.Refresh

Control. Refresh 强制控件使其工作区无效并立即重绘自己和任何子控件。

什么意思呢,我们用实例看会很快明白:

private void button1_Click(object sender, EventArgs e)
        {
            System.Threading.Thread.CurrentThread.Priority = System.Threading.ThreadPriority.Highest;
            Image myImage2;
            openFileDialog1.Filter = "*.jpg,*.jpeg,*.bmp|*.jpg;*.jpeg;*.bmp";
            if (openFileDialog1.ShowDialog() == DialogResult.OK)
            {
                myImage2 = System.Drawing.Image.FromFile(openFileDialog1.FileName);

                this.BackgroundImage = myImage2;//第一张图片
                this.Refresh();//注释reflesh()和不注释reflesh()的效果差异,运行程序就可以看出了
            }
            System.Threading.Thread.Sleep(1000);
            BackgroundImage = Image.FromFile(@"D:/abc.png");//第二张图片
        }

我们发现:

1不注释reflesh(),运行后,第一张图片会显示出来,等待1秒后第二张图片又显示了出来。

2注释reflesh(),运行后,第一张图片没有显示出来,等待一会儿第二张图片显示了出来。

也就是说:

没有Refresh,方法体执行期间界面保持原样,方法体执行完后才绘制方法体中所做的更改。

有Refresh,会强制立即绘制刚才所做的更改,无论方法体是否执行完。

/******************************

C# WinForm窗体 控件Control 的 Invalidate、Update、Refresh的区别

Control.Refresh - does an Control.Invalidate followed by Control.Update.
Refresh: 强制控件使其工作区无效并立即重绘自己和任何子控件。== Invalidate Update

Control.Invalidate - invalidates a specific region of the Control (defaults to entire client area) and causes a paint message to be sent to the control.Invalidate marks the control (region, or rect) as in need of repainting, but doesn't immediately repaint (the repaint is triggered when everything else has been taken care of and the app becomes idle).
Invalidate: 使控件的特定区域(可以自己设置区域,从而提高性能)无效并向控件发送绘制消息。
                将控件标记为需要重绘,但是不会立即执行刷新重绘,等到系统空闲时进行重绘。


Control.Update - causes the Paint event to occur immediately (Windows will normally wait until there are no other messages for the window to process, before raising the Paint event).Update causes the control to immediately repaint if any portions have been invalidated.
Update: 使控件重绘其工作区内的无效区域,立即调用Paint事件。若有无效区域,Update将立即触发重绘。

The paint event of course is where all the drawing of your form occurs. Note there is only one pending Paint event, if you call Invalidate 3 times, you will still only receive one Paint event.
Paint: 无处不在。如果你调用3次Invalidate,但是系统将只触发一次Paint事件。

Most of the time Invalidate is sufficient, and advisable as you can do a bunch of invalidations (either explicit or implicit) and then let the control repaint itself when the app is idle. It is advisable to use Update or Refresh when you want the control to immediately repaint because the app will not be idle for a user-noticable period of time.

大多数时候Invalidate已经足够了,当系统要集中进行大量的刷新重绘时,建议使用Invalidate,因为这样系统最终只进行一次刷新,提高了系统性能。如果你想立即执行刷新的时候,建议使用Refresh方法。

/**********************控件的refresh,invalidate和update函数的区别

在图形用户界面(GUI)编程中,refresh,invalidate和update是常见的控件刷新(重绘)操作。它们的作用和使用场景有一些区别:

Refresh:
Refresh 方法用于强制控件重绘。
调用 Refresh 会触发 Paint 事件,导致控件重新绘制自身。
常用于需要立即更新控件外观的情况,例如改变控件属性后需要立即重绘。
可能会引起多次重绘,因为它会触发多次 Paint 事件。
Invalidate:
Invalidate 方法用于标记控件或控件的特定区域无效,需要重新绘制。
通过调用 Invalidate,告诉 GUI 系统控件的某个部分需要重新绘制,但不会立即执行重绘操作。
可以选择性地传递一个矩形区域参数,表示只有该区域需要重绘。
适用于需要延迟重绘的情况,例如在一个循环中更新控件属性后,最后一次性重绘。
Update:
Update 方法用于强制立即执行挂起的重绘操作。
调用 Update 会导致控件立即进行重绘,清除了之前标记为无效的区域。
常用于需要立即更新控件外观且避免多次重绘的情况,例如在一连串属性更改后立即重绘一次。
综上所述,Refresh、Invalidate 和 Update 都涉及控件的重绘,但在使用时需要根据不同的需求和场景来选择。通常情况下,可以使用以下原则:

如果需要立即重绘控件并且在不同属性更改后需要重绘多次,可以使用 Refresh。
如果需要标记控件无效并延迟重绘,可以使用 Invalidate。
如果需要立即执行挂起的重绘操作,可以使用 Update。
需要注意的是,这些方法在不同的 GUI 框架和编程语言中的具体行为可能会有细微的差异。

/**************************C#重绘OnPaint()事件调用机制

C#重绘OnPaint()事件调用机制

与绘制有关的函数
Paint()
OnPaint()
Invalidate()
前两个函数的执行顺序,如果在OnPaint()中有base.OnPaint(e)函数,就相当于执行外部的Paint()函数。 
Invalidate()运行后,会通知系统要重绘控件,但是不是立即重绘,而是运行完所有程序之后再重绘。

一个控件应该什么时候绘制主要有两个 :
一个是 系统来确定 这个控件需要绘制 比如 最小化后在最大化 那么此时 系统会发出WM_PAINT 消息来告知控件 你需要重绘了
另一个就是 人为的调用this.Invalidate()或者this.Invalidate(Rectangle)来自己触发一次重绘 
[引用]
对于第一种 那是系统自身的一种行为 不管你是什么控件系统自带的还是自己写的 对于一些操作必然是要重绘界面的 比如最小化后 在最大化 控件隐藏后 在显示 得到焦点 失去焦点 之类的 这种是必然要重绘的

实例
目标:控件改动一个属性后希望重新绘制控件达到刷新的效果。
修改版半径后在设计器中显示出来:

public int Radius
        {
            get { return _radius; }
            set
            {
                _radius = value;
                this.Invalidate();
            }
        }

选中后刷新显示:

 public bool IsSelected
        {
            get { return this._isSelected; }
            set
            {
                this._isSelected = value;
                this.Invalidate();
            }
        }

/*********************在什么情况下控件需要重新绘制 C# 也就是在什么情况下,触发Paint事件

系统会在多个不同的时机发送WM_PAINT消息:当第一次创建一个窗口时,当改变窗口的大小时,当把窗口从另一个窗口背后移出时,当最大化或最小化窗口时,等等,这些动作都是由 系统管理的,应用只是被动地接收该消息,在消息处理函数中进行绘制操作;大多数的时候应用也需要能够主动引发窗口中的绘制操作,比如当窗口显示的数据改变的时候,这一般是通过InvalidateRect和 InvalidateRgn函数来完成的。InvalidateRect和InvalidateRgn把指定的区域加到窗口的Update Region中,当应用的消息队列没有其他消息时,如果窗口的Update Region不为空时,系统就会自动产生WM_PAINT消息。
系统会在多个不同的时机发送WM_PAINT消息:当第一次创建一个窗口时,当改变窗口的大小时,当把窗口从另一个窗口背后移出时,当最大化或最小化窗口时,等等,这些动作都是由 系统管理的,应用只是被动地接收该消息,在消息处理函数中进行绘制操作;大多数的时候应用也需要能够主动引发窗口中的绘制操作,比如当窗口显示的数据改变的时候,这一般是通过InvalidateRect和 InvalidateRgn函数来完成的。InvalidateRect和InvalidateRgn把指定的区域加到窗口的Update Region中,当应用的消息队列没有其他消息时,如果窗口的Update Region不为空时,系统就会自动产生WM_PAINT消息。
系统为什么不在调用Invalidate时发送WM_PAINT消息呢?又为什么非要等应用消息队列为空时才发送WM_PAINT消息呢?这是因为系统把在窗口中的绘制操作当作一种低优先级的操作,于是尽 可能地推后做。不过这样也有利于提高绘制的效率:两个WM_PAINT消息之间通过InvalidateRect和InvaliateRgn使之失效的区域就会被累加起来,然后在一个WM_PAINT消息中一次得到 更新,不仅能避免多次重复地更新同一区域,也优化了应用的更新操作。像这种通过InvalidateRect和InvalidateRgn来使窗口区域无效,依赖于系统在合适的时机发送WM_PAINT消息的机 制实际上是一种异步工作方式,也就是说,在无效化窗口区域和发送WM_PAINT消息之间是有延迟的;有时候这种延迟并不是我们希望的,这时我们当然可以在无效化窗口区域后利用SendMessage 发送一条WM_PAINT消息来强制立即重画,但不如使用Windows GDI为我们提供的更方便和强大的函数:UpdateWindow和RedrawWindow。UpdateWindow会检查窗口的Update Region,当其不为空时才发送WM_PAINT消息;RedrawWindow则给我们更多的控制:是否重画非客户区和背景,是否总是发送WM_PAINT消息而不管Update Region是否为空等。 

  • 21
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值