如何避免多控件窗体重新布局时闪烁
2009-09-08 21:59
316 查看
适用场景:
需要在某容器控件中动态装载多个子控件,而且该容器控件可能需要改变WindowFormState,即从Normal转变为Maxmized,或者是其他状态转换啦, what ever :)
如果没有应用任何特殊处理,你就会发现,当容器控件状态转换时,其上的子控件在经过一阵狂闪之后(可能背景控件颜色和自身相互交替出现),最终恢复至平静; 这种情形当然无论是程序员自己和客户都不愿意看到的
ok,废话一通之后,咱们开始解释原因,以及考虑解决方案
为什么会闪烁?
因为窗体控件状态转换时,windows需要负责"擦除"其背景,重新绘制,在一台性能并不优良的终端上(很大可能程度上客户端电脑都不是那么强劲吧) ,这个过程不是一时半会就能完成的,尤其对于很多个子控件的情况,因此就…
解决之道?
如果稍微写过WinForm程序的同学,肯定或多或少的用过ListView控件,简单易用嘛 :) 那么一定也知道该控件有2个比较有意思的方法:
BeginUpdate
Prevents the control from drawing until the EndUpdate method is called.
EndUpdate
Resumes drawing of the list view control after drawing is suspended by the BeginUpdate method.
从msdn的解释来看,这2个方法的应用能解决往ListView控件中分多次Add ListViewItem时闪烁的问题,ok,既然它能这么处理,咱们自己的容器控件为什么不能依葫芦画瓢呢?
btw. 其实我一开始也没任何好方法解决闪烁问题,后来偶尔想到ListView的此特性 :)
看看ListView.BeginUpdateInternal方法怎么写:
internal bool EndUpdateInternal(bool invalidate)
{
if (this.updateCount <= 0)
{
return false;
}
this.updateCount = (short) (this.updateCount - 1);
if (this.updateCount == 0)
{
this.SendMessage(11, -1, 0);
if (invalidate)
{
this.Invalidate();
}
}
return true;
}
同样有一行代码: this.SendMessage(11, –1, 0); 11还是同一个意思,此时告知windows可以重绘ListView控件了
ok,到这时候应该明白这2个方法含义了吧,也就是说对子控件的操作都是在一个“冻结”的状态中进行的,等到所有准备工作就绪,才对最终状态重新绘制,因此界面就不会出现闪烁状态.
如何依葫芦画瓢?
要知道不是每个控件类都有提供BeginUpdate和EndUpdate方法,所以需要自己亲自打造一个
1. SendMessage如何来? 从windows api interop而来,很简单,有个工具可以提供所有api函数到c#方法的转换: P/Invoke Interop Assistant
2.11这个定义以及类似的东东怎么找? 强大的google或者bing可以帮忙 :D
至于代码怎么写,就不需要偶来操刀啦 :D
后记:
这样处理之后,是不是发现闪烁从此就不再出现了?但是……还有问题
拿一个无边框窗体举例,当它从Normal状态变为Maxmized(顺便设置TopMost为true),你会很高兴看到该窗体包含的子控件真的不闪了,从Maxmized回到Normal时,也不闪了,但是很诡异的问题发生了: 任务栏不见了?取而代之的是当前窗体的背景色??? 难道任务栏没有重绘回来???
发生什么事情了? 不是只让窗体的重绘停止了吗,怎么会影响到任务栏窗口?
ok,解决办法是有的,发个消息给任务栏窗口让它强制重绘,怎么写?
同样,对于.net中的任何控件,都自带Invalidate方法,通过调用该方法,可以强制重绘控件的整体或某矩形部分, 又要画瓢啦
在Invalidate中可以看到这么一行
SafeNativeMethods.RedrawWindow(new HandleRef(this.window, this.Handle), (NativeMethods.COMRECT) null, NativeMethods.NullHandleRef, 0x85);
现在的问题是,如何获取任务栏窗口句柄? bing一把就会发现,很简单: FindWindow(“Shell_TrayWnd”, “”);
ok, 后面的事情就简单啦
再后记
不难发现,Control对象其实是自带BeginUpdateInternal方法的,但m$很恶毒的把它弄成internal的了… 而且只被少数几个控件享用: ListView, ComboBox, DataGrid, TreeView
需要在某容器控件中动态装载多个子控件,而且该容器控件可能需要改变WindowFormState,即从Normal转变为Maxmized,或者是其他状态转换啦, what ever :)
如果没有应用任何特殊处理,你就会发现,当容器控件状态转换时,其上的子控件在经过一阵狂闪之后(可能背景控件颜色和自身相互交替出现),最终恢复至平静; 这种情形当然无论是程序员自己和客户都不愿意看到的
ok,废话一通之后,咱们开始解释原因,以及考虑解决方案
为什么会闪烁?
因为窗体控件状态转换时,windows需要负责"擦除"其背景,重新绘制,在一台性能并不优良的终端上(很大可能程度上客户端电脑都不是那么强劲吧) ,这个过程不是一时半会就能完成的,尤其对于很多个子控件的情况,因此就…
解决之道?
如果稍微写过WinForm程序的同学,肯定或多或少的用过ListView控件,简单易用嘛 :) 那么一定也知道该控件有2个比较有意思的方法:
BeginUpdate
Prevents the control from drawing until the EndUpdate method is called.
EndUpdate
Resumes drawing of the list view control after drawing is suspended by the BeginUpdate method.
从msdn的解释来看,这2个方法的应用能解决往ListView控件中分多次Add ListViewItem时闪烁的问题,ok,既然它能这么处理,咱们自己的容器控件为什么不能依葫芦画瓢呢?
btw. 其实我一开始也没任何好方法解决闪烁问题,后来偶尔想到ListView的此特性 :)
看看ListView.BeginUpdateInternal方法怎么写:
internal bool EndUpdateInternal(bool invalidate)
{
if (this.updateCount <= 0)
{
return false;
}
this.updateCount = (short) (this.updateCount - 1);
if (this.updateCount == 0)
{
this.SendMessage(11, -1, 0);
if (invalidate)
{
this.Invalidate();
}
}
return true;
}
同样有一行代码: this.SendMessage(11, –1, 0); 11还是同一个意思,此时告知windows可以重绘ListView控件了
ok,到这时候应该明白这2个方法含义了吧,也就是说对子控件的操作都是在一个“冻结”的状态中进行的,等到所有准备工作就绪,才对最终状态重新绘制,因此界面就不会出现闪烁状态.
如何依葫芦画瓢?
要知道不是每个控件类都有提供BeginUpdate和EndUpdate方法,所以需要自己亲自打造一个
1. SendMessage如何来? 从windows api interop而来,很简单,有个工具可以提供所有api函数到c#方法的转换: P/Invoke Interop Assistant
2.11这个定义以及类似的东东怎么找? 强大的google或者bing可以帮忙 :D
至于代码怎么写,就不需要偶来操刀啦 :D
后记:
这样处理之后,是不是发现闪烁从此就不再出现了?但是……还有问题
拿一个无边框窗体举例,当它从Normal状态变为Maxmized(顺便设置TopMost为true),你会很高兴看到该窗体包含的子控件真的不闪了,从Maxmized回到Normal时,也不闪了,但是很诡异的问题发生了: 任务栏不见了?取而代之的是当前窗体的背景色??? 难道任务栏没有重绘回来???
发生什么事情了? 不是只让窗体的重绘停止了吗,怎么会影响到任务栏窗口?
ok,解决办法是有的,发个消息给任务栏窗口让它强制重绘,怎么写?
同样,对于.net中的任何控件,都自带Invalidate方法,通过调用该方法,可以强制重绘控件的整体或某矩形部分, 又要画瓢啦
在Invalidate中可以看到这么一行
SafeNativeMethods.RedrawWindow(new HandleRef(this.window, this.Handle), (NativeMethods.COMRECT) null, NativeMethods.NullHandleRef, 0x85);
现在的问题是,如何获取任务栏窗口句柄? bing一把就会发现,很简单: FindWindow(“Shell_TrayWnd”, “”);
ok, 后面的事情就简单啦
再后记
不难发现,Control对象其实是自带BeginUpdateInternal方法的,但m$很恶毒的把它弄成internal的了… 而且只被少数几个控件享用: ListView, ComboBox, DataGrid, TreeView
相关文章推荐
- 如何避免多控件窗体重新布局时闪烁
- 如何避免多控件窗体重新布局时闪烁
- C#中弹出式窗体如何避免闪烁?
- 使用双重缓冲,避免窗体中控件位置移动的时候闪烁
- [补充]避免多控件窗体闪烁demo
- 避免多控件窗体闪烁
- 如何防止拖动窗体大小时控件闪烁的问题
- 跨线程调用控件时关闭窗体时如何避免出现异常:Cannot access a disposed object.
- winform窗体加背景图片后,控件多了会闪烁,该如何处理
- 避免多控件窗体闪烁demo
- 如何避免treeview控件闪烁刷新
- 如何控制其他程序窗体上的窗口控件(下)
- c#如何跨线程调用窗体控件
- 关于如何操作其他窗体的控件或变量的方法
- C#里WinForm开发中如何实现控件随窗体大小的改变而自动适应其改变
- SDI窗体中使用mscom控件通信当窗体切换时如何使其通信不中断
- MDI窗体与子窗体的显示问题--(如何让主窗体是被控件挡住的子窗体显示)
- 如何:将数据绑定到 Windows 窗体 DataGridView 控件
- 主窗体和子窗体之间如何相互操作对方的控件
- C# winform中一个类中如何调用另一个窗体的控件或方法