How to fix the flickering in User controls

后端 未结 12 1347
佛祖请我去吃肉
佛祖请我去吃肉 2020-11-22 02:32

In my application i am constantly moving from one control to another. I have created no. of user controls, but during navigation my controls gets flicker. it takes 1 or 2 se

相关标签:
12条回答
  • 2020-11-22 02:33

    This is a real issue, and the answer Hans Passant gave is great for saving the flicker. However, there are side effects as he mentioned, and they can be ugly (UI ugly). As stated, "You can turn off the WS_CLIPCHILDREN style flag for the UC", but that only turns it off for a UC. The components on the main form still have issues.

    Example, a panel scroll bar doesn't paint, because it is technically in the child area. However the child component doesn't draw the scroll bar, so it doesn't get painted until mouse over (or another event triggers it).

    Also, animated icons (changing icons in a wait loop) doesn't work. Removing icons on a tabPage.ImageKey doesn't resize/repaint the other tabPages appropriately.

    So I was looking for a way to turn off the WS_CLIPCHILDREN on initial painting so my Form will load nicely painted, or better yet only turn it on while resizing my form with a lot of components.

    The trick is to get the application to call CreateParams with the desired WS_EX_COMPOSITED/WS_CLIPCHILDREN style. I found a hack here (https://web.archive.org/web/20161026205944/http://www.angryhacker.com/blog/archive/2010/07/21/how-to-get-rid-of-flicker-on-windows-forms-applications.aspx) and it works great. Thanks AngryHacker!

    I put the TurnOnFormLevelDoubleBuffering() call in the form ResizeBegin event and TurnOffFormLevelDoubleBuffering() call in the form ResizeEnd event (or just leave it WS_CLIPCHILDREN after it is initially painted properly.)

        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;
        }
    
    0 讨论(0)
  • 2020-11-22 02:33

    Just to add to the answer Hans gave:

    (TLDR version: Transparency is heavier than you think, use only solid colors everywhere)

    If WS_EX_COMPOSITED, DoubleBuffered and WS_CLIPCHILDREN did not solve your flicker (for me WS_CLIPCHILDREN made it even worse), try this: go through ALL your controls and all your code, and wherever you have Any transparency or semi-transparency for BackColor, ForeColor, or any other color, just remove it, use only solid colors. In most of the cases where you think you just have to use transparency, you don't. Re-design your code and controls, and use solid colors. I had terrible, terrible flickering and the program was running sluggish. Once I removed transparency it sped up significantly, and there is 0 flicker.

    EDIT: To add further, I just discovered that WS_EX_COMPOSITED doesn't have to be window-wide, it could be applied just to specific controls! This saved me a lot of trouble. Just make a custom control inherited from whatever control you need, and paste the already posted override for WS_EX_COMPOSITED. This way you get low-level double-buffer on this control only, avoiding the nasty side-effects in the rest of the application!

    0 讨论(0)
  • 2020-11-22 02:37

    I know this question is very old, but want to give my experience on it.

    I had a lot of problems with Tabcontrol flickering in a form with overrided OnPaint and/or OnPaintBackGround in Windows 8 using .NET 4.0.

    The only think that worked has been NOT USE the Graphics.DrawImage method in OnPaint overrides, in other words, when draw was done directly to the Graphics provided by the PaintEventArgs, even painting all the rectangle, the flickering dissapeared. But if call the DrawImage method, even drawing a clipped Bitmap, (created for double buffering) the flicker appears.

    Hope it helps!

    0 讨论(0)
  • 2020-11-22 02:40

    Put the code bellow in your constructor or OnLoad event and if you're using some sort of custom user control that having sub controls, you'll need to make sure that these custom controls are also double buffered (even though in MS documentation they say it's set to true by default).

    If you're making a custom control, you might want to add this flag into your ctor:

    SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
    

    Optionally you can use this code in your Form/Control:

    foreach (Control control in Controls)
    {
        typeof(Control).InvokeMember("DoubleBuffered",
            BindingFlags.SetProperty | BindingFlags.Instance | BindingFlags.NonPublic,
            null, control, new object[] { true });
    }
    

    We iterating through all the controls in the form/control and accessing their DoubleBuffered property and then we change it to true in order to make each control on the form double buffered. The reason we do reflection here, is because imagine you have a control that has child controls that are not accessible, that way, even if they're private controls, we'll still change their property to true.

    More information about double buffering technique can be found here.

    There is another property I usually override to sort this problem:

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams parms = base.CreateParams;
            parms.ExStyle |= 0x00000020; // WS_EX_COMPOSITED
            return parms;
        }
    }
    

    WS_EX_COMPOSITED - Paints all descendants of a window in bottom-to-top painting order using double-buffering.

    You can find more of these style flags here.

    Hope that helps!

    0 讨论(0)
  • 2020-11-22 02:42

    I combined this flicker fix and this font fix, then I had to add a bit of my own code to start a timer on paint to Invalidate the TabControl when it goes offscreen and back, etc..

    All three make this:

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

    I'm not the creator but from what I understand the bitmap does all the bug bypassing.

    This was the only thing that definitively solved TabControl (with Icons) flicker for me.

    difference result video: vanilla tabcontrol vs tabcontrolex

    http://gfycat.com/FineGlitteringDeermouse

    ps. you will need to set HotTrack = true, because this fixes that bug too

    0 讨论(0)
  • 2020-11-22 02:43

    On the main form or user control where background image resides set the BackgroundImageLayout property to Center or Stretch. You will notice a big difference when the user control is rendering.

    0 讨论(0)
提交回复
热议问题