在我的应用程序中,我不断地从一个控件移动到另一个控件。我创造了没有。用户控件,但在导航过程中我的控件闪烁。更新需要1或2秒。我试着设置这个
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
or
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.DoubleBuffer, true);
但它没有帮助......每个控件都有相同的背景图像和不同的控件。
那么它的解决方案是什么..
感谢。
答案 0 :(得分:293)
双缓冲可以解决的不是那种闪烁。也不是BeginUpdate或SuspendLayout。你有太多的控件,BackgroundImage可以让它很多更差。
它在UserControl绘制自身时启动。它绘制了BackgroundImage,在子控件窗口的位置留下了空洞。然后每个子控件都会收到一条消息来绘制自己,他们将用他们的窗口内容填充这个洞。当您有很多控件时,用户可以看到这些漏洞一段时间。它们通常是白色的,在黑暗时与BackgroundImage形成鲜明对比。或者如果表单设置了Opacity或TransparencyKey属性,它们可能是黑色的,与几乎任何东西形成鲜明对比。
这是Windows窗体的一个非常基本的限制,它与Windows呈现窗口的方式一致。由WPF btw修复,它不使用窗口进行子控件。您想要的是对整个表单进行双缓冲,包括子控件。这是可能的,请在this thread中查看我的代码以获得解决方案。它虽然有副作用,但实际上并没有提高绘画速度。代码很简单,将其粘贴到您的表单中(而不是用户控件):
protected override CreateParams CreateParams {
get {
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000; // Turn on WS_EX_COMPOSITED
return cp;
}
}
你可以做很多事情来提高绘画速度,以至于闪烁不再明显。首先解决BackgroundImage问题。当源图像很大并且需要缩小以适应控件时,它们可能非常昂贵。将BackgroundImageLayout属性更改为“Tile”。如果这样可以显着提高速度,请返回绘图程序并调整图像大小以更好地匹配典型的控件尺寸。或者在UC的OnResize()方法中编写代码以创建适当大小的图像副本,这样每次控件重绘时都不必调整大小。对该副本使用Format32bppPArgb像素格式,其渲染速度比任何其他像素格式快10倍。
接下来你要做的就是防止这些孔明显,并与图像形成鲜明对比。您可以turn off UC的WS_CLIPCHILDREN样式标志,该标志阻止UC在子控件所在的区域进行绘制。将此代码粘贴到UserControl的代码中:
protected override CreateParams CreateParams {
get {
var parms = base.CreateParams;
parms.Style &= ~0x02000000; // Turn off WS_CLIPCHILDREN
return parms;
}
}
现在,子控件将自己绘制在背景图像的顶部。你可能仍然会看到他们一个接一个地画画,但丑陋的中间白洞或黑洞将不可见。
最后但同样重要的是,减少子控件的数量始终是解决慢速绘画问题的好方法。覆盖UC的OnPaint()事件并绘制现在在孩子中显示的内容。特定的Label和PictureBox 非常浪费。方便点和点击但是它们的轻量级替代(绘制字符串或图像)在OnPaint()方法中只需要一行代码。
答案 1 :(得分:8)
这是一个真正的问题,Hans Passant给出的答案非常适合保存闪烁。然而,正如他所提到的那样,有副作用,而且它们可能很难看(UI难看)。如上所述,“您可以关闭UC的WS_CLIPCHILDREN样式标志”,但这只会为UC关闭它。主表单上的组件仍有问题。
示例,面板滚动条不会绘制,因为它在技术上位于子区域。但是子组件不会绘制滚动条,因此在鼠标悬停(或其他事件触发)之前它不会被绘制。
此外,动画图标(在等待循环中更改图标)不起作用。删除tabPage.ImageKey上的图标不会适当地调整/重新绘制其他tabPages。
所以我正在寻找一种方法来在初始绘画时关闭WS_CLIPCHILDREN,这样我的Form将加载很好的绘制,或者更好,但只有在使用大量组件调整表单时才打开它。
诀窍是让应用程序使用所需的WS_EX_COMPOSITED / WS_CLIPCHILDREN样式调用CreateParams?我在这里找到了一个黑客(http://www.angryhacker.com/blog/archive/2010/07/21/how-to-get-rid-of-flicker-on-windows-forms-applications.aspx)并且效果很好。谢谢AngryHacker!
我以ResizeBegin事件的形式调用TurnOnFormLevelDoubleBuffering()。 TurnOffFormLevelDoubleBuffering()以ResizeEnd事件的形式调用(或者在最初正确绘制之后将其保留为WS_CLIPCHILDREN。)
int originalExStyle = -1;
bool enableFormLevelDoubleBuffering = true;
protected override CreateParams CreateParams
{
get
{
if (originalExStyle == -1)
originalExStyle = base.CreateParams.ExStyle;
CreateParams cp = base.CreateParams;
if (enableFormLevelDoubleBuffering)
cp.ExStyle |= 0x02000000; // WS_EX_COMPOSITED
else
cp.ExStyle = originalExStyle;
return cp;
}
}
public void TurnOffFormLevelDoubleBuffering()
{
enableFormLevelDoubleBuffering = false;
this.MaximizeBox = true;
}
答案 2 :(得分:6)
如果您在控件中进行任何自定义绘制(即重写OnPaint),您可以自己尝试双缓冲。
Image image;
protected override OnPaint(...) {
if (image == null || needRepaint) {
image = new Bitmap(Width, Height);
using (Graphics g = Graphics.FromImage(image)) {
// do any painting in image instead of control
}
needRepaint = false;
}
e.Graphics.DrawImage(image, 0, 0);
}
使用属性NeedRepaint
否则SuspendLayout和ResumeLayout的上述答案可能就是您想要的。
答案 3 :(得分:3)
尝试使用BeginUpdate / EndUpdate或SuspendLayout / ResumeLayout方法。
见下文
How to fix nested winform control flicker issues
Flickering during updates to Controls in WinForms (e.g. DataGridView)
答案 4 :(得分:2)
在背景图片所在的主窗体或用户控件上,将BackgroundImageLayout
属性设置为Center
或Stretch
。当用户控件呈现时,您会注意到一个很大的区别。
答案 5 :(得分:2)
我尝试将其添加为评论,但我没有足够的积分。这是唯一能帮助我解决问题的事情,感谢汉斯的这篇文章。对于像我这样使用c ++ builder的人来说,这就是翻译
将CreateParams声明添加到应用程序的主窗体.h文件中,例如
class TYourMainFrom : public TForm
{
protected:
virtual void __fastcall CreateParams(TCreateParams &Params);
}
并将其添加到.cpp文件
void __fastcall TYourMainForm::CreateParams(TCreateParams &Params)
{
Params.ExStyle |= 0x02000000; // Turn on WS_EX_COMPOSITED
TForm::CreateParams(Params);
}
答案 6 :(得分:2)
将代码放在构造函数或OnLoad事件中,如果您使用某种具有子控件的自定义用户控件,则需要确保这些自定义控件也是双缓冲的(即使在MS文档中他们说它默认设置为true。
如果您正在制作自定义控件,则可能需要将此标记添加到您的ctor中:
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
您可以选择在表单/控件中使用此代码:
foreach (Control control in Controls)
{
typeof(Control).InvokeMember("DoubleBuffered",
BindingFlags.SetProperty | BindingFlags.Instance | BindingFlags.NonPublic,
null, control, new object[] { true });
}
我们遍历表单/控件中的所有控件并访问它们的DoubleBuffered
属性,然后我们将其更改为true,以便对表单上的每个控件进行双缓冲。我们在这里做反思的原因是因为想象你有一个控件,它具有无法访问的子控件,这样,即使他们是私人控件,我们仍然会将其属性更改为true。
有关双缓冲技术的更多信息,请参见here。
我通常会覆盖另一个属性来排序这个问题:
protected override CreateParams CreateParams
{
get
{
CreateParams parms = base.CreateParams;
parms.ExStyle |= 0x00000020; // WS_EX_COMPOSITED
return parms;
}
}
WS_EX_COMPOSITED
- 使用双缓冲以底部到顶部的绘画顺序绘制窗口的所有后代。
您可以找到更多这些样式标记here。
希望有所帮助!
答案 7 :(得分:1)
只是为了补充汉斯给出的答案:
(TLDR版本:透明度比您想象的要重,只使用纯色)
如果WS_EX_COMPOSITED,DoubleBuffered和WS_CLIPCHILDREN没有解决你的闪烁(对我来说WS_CLIPCHILDREN让它变得更糟),试试这个:浏览所有控件和所有代码,无论你有什么透明或BackColor的半透明, ForeColor或任何其他颜色,只需删除它,只使用纯色。在大多数情况下,如果您认为自己 使用透明度,则不会。重新设计代码和控件,并使用纯色。 我有可怕的,可怕的闪烁,程序运行缓慢。一旦我删除了透明度,它就显着加快了,并且闪烁了0次。
编辑:为了进一步增加,我刚刚发现WS_EX_COMPOSITED不必是窗口范围的,它可以仅应用于特定的控件!这给我带来了很多麻烦。只需从您需要的任何控件继承自定义控件,并粘贴已发布的WS_EX_COMPOSITED覆盖。这样你只能在这个控件上获得低级双缓冲,避免了应用程序其余部分的恶意副作用!答案 8 :(得分:0)
我知道这个问题已经很老了,但我想借鉴它的经验。
我在使用.NET 4.0的Windows 8中覆盖Tabcontrol
和/或OnPaint
的表单中OnPaintBackGround
闪烁时出现了很多问题。
唯一有效的想法是 NOT USE Graphics.DrawImage
中的OnPaint
方法覆盖,换句话说,直接绘制到PaintEventArgs
提供的图形时{1}},甚至画了所有的矩形,闪烁消失了。但是如果调用DrawImage
方法,甚至绘制一个剪切的位图(为双缓冲而创建),则会出现闪烁。
希望它有所帮助!
答案 9 :(得分:0)
我合并了this flicker fix和this font fix,然后我不得不添加一些我自己的代码来启动绘制时的计时器,以便在它离开屏幕和返回时使TabControl无效等。
所有三个人都这样做:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class TabControlEx:TabControl
{
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
private const int WM_PAINT = 0x0f;
private const int WM_SETFONT = 0x30;
private const int WM_FONTCHANGE = 0x1d;
private System.Drawing.Bitmap buffer;
private Timer timer = new Timer();
public TabControlEx()
{
timer.Interval = 1;
timer.Tick += timer_Tick;
this.SetStyle(ControlStyles.UserPaint | ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
}
void timer_Tick(object sender, EventArgs e)
{
this.Invalidate();
this.Update();
timer.Stop();
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_PAINT) timer.Start();
base.WndProc(ref m);
}
protected override void OnPaint(PaintEventArgs pevent)
{
this.SetStyle(ControlStyles.UserPaint, false);
base.OnPaint(pevent);
System.Drawing.Rectangle o = pevent.ClipRectangle;
System.Drawing.Graphics.FromImage(buffer).Clear(System.Drawing.SystemColors.Control);
if (o.Width > 0 && o.Height > 0)
DrawToBitmap(buffer, new System.Drawing.Rectangle(0, 0, Width, o.Height));
pevent.Graphics.DrawImageUnscaled(buffer, 0, 0);
this.SetStyle(ControlStyles.UserPaint, true);
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
buffer = new System.Drawing.Bitmap(Width, Height);
}
protected override void OnCreateControl()
{
base.OnCreateControl();
this.OnFontChanged(EventArgs.Empty);
}
protected override void OnFontChanged(EventArgs e)
{
base.OnFontChanged(e);
IntPtr hFont = this.Font.ToHfont();
SendMessage(this.Handle, WM_SETFONT, hFont, (IntPtr)(-1));
SendMessage(this.Handle, WM_FONTCHANGE, IntPtr.Zero, IntPtr.Zero);
this.UpdateStyles();
}
}
我不是创作者,但根据我的理解,位图会绕过所有错误。
这是唯一一个明确解决TabControl(带图标)闪烁的东西。
差异结果视频:vanilla tabcontrol vs tabcontrolex
http://gfycat.com/FineGlitteringDeermouse
PS。你需要设置HotTrack = true,因为这也修复了这个bug
答案 10 :(得分:-2)
答案 11 :(得分:-9)
不需要任何Double缓冲和所有那些东西......
一个简单的解决方案......
如果您使用的是MDI界面,只需将以下代码粘贴在主表单中即可。它将删除页面上的所有闪烁。然而,一些需要更多时间加载的页面将在1或2秒内显示。但这比显示一个闪烁的页面更好,其中每个项目逐个出现。
这是整个应用程序的唯一最佳解决方案。请参阅主要表单中的代码:
protected override CreateParams CreateParams {
get {
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000; // Turn on WS_EX_COMPOSITED
return cp;
}
}