TabControl blinks if image is background
I have开发者_C百科 noticed that if I have a TabControl in a Panel that has an Image Background, when the mouse hovers over a tab it blinks and redraws. Is there a workaround to prevent this from happening?
I see it. It happens because TabControl draws itself partly by asking the parent control to draw itself inside its own window. Necessary because the tabs don't cover the full width of the control, they "stick out". If the BackgroundImage is slow to draw, you'll see a flicker between the background being drawn and the tabs drawn on top of that.
This is going to be hard to fix, TabControl doesn't support any kind of double buffering. You can only minimize the effect by making the BackgroundImage efficient to draw. You need to do so by making the image exactly the size of the panel's ClientSize so that the image doesn't have to be resized. And create that bitmap with the PixelFormat32bppPArgb pixel format, it is usually 10 times faster than the other ones.
There's one magic cure available, windows have a style flag that enables double-buffering for the entire window, including its child controls. Supported since XP but some side-effects have been reported. Paste this code into your form, it fixes the TabControl flicker:
protected override CreateParams CreateParams {
get {
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000; // Turn on WS_EX_COMPOSITED
return cp;
}
}
But beware that the visual style renderer for TabControl has one rather major incompatibility with this style flag. If your tabs overflow and you get the selection arrows then it goes bananas and starts rendering the tabs over and over again, producing a very high rate of flicker.
I've tried solution with CreateParams and it introduces its own problems. I need to add and delete tabs dynamically, and TabControl with WS_EX_COMPOSITED doesn't redraw itself after I delete a tab, even after Invalidate() method. Only when I move the mouse into tabs area, TabControl starts to redraw itself and in a very weird manner.
So finally I came to this solution:
public class TabControlX : TabControl
{
protected override void WndProc( ref Message m )
{
if( m.Msg == WinAPI.WM_MOUSEMOVE && !HotTrack )
return;
base.WndProc(ref m);
}
}
Where
public const int WM_MOUSEMOVE = 0x0200;
For some unknown reasons HotTrack property doesn't work in TabControl control, so I've actually fixed it :)
Of course, it doesn't work during resizing, but it OK with me.
Thanks to multiple answers including from Hans Passant, I was able to make a toggled version that would only be in that mode when it needed to be, in this case: when the tab control was being resized because of the parent form.
It wasn't easy as I decided it was best to have it listen to the form resize begin and resize end... Here's what I did and it suits my needs, no longer does tabcontrol flicker on resize from the Form being resized.
public class NoFlickerTabControl : TabControl
{
#region PInvoke Change Window Rendering Style Params
public static IntPtr SetWindowLong(HandleRef hWnd, int nIndex, IntPtr dwNewLong)
{
if (IntPtr.Size == 8)
{
return SetWindowLongPtr64(hWnd, nIndex, dwNewLong);
}
else
{
return new IntPtr(SetWindowLong32(hWnd, nIndex, dwNewLong.ToInt32()));
}
}
[DllImport("user32.dll", EntryPoint = "SetWindowLong")]
private static extern int SetWindowLong32(HandleRef hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
private static extern IntPtr SetWindowLongPtr64(HandleRef hWnd, int nIndex, IntPtr dwNewLong);
public enum WindowLongFlags : int
{
GWL_WNDPROC = -4,
GWL_HINSTANCE = -6,
GWL_HWNDPARENT = -8,
GWL_STYLE = -16,
GWL_EXSTYLE = -20,
GWL_USERDATA = -21,
GWL_ID = -12
}
#endregion
#region Tab Control Style!
public NoFlickerTabControl()
{
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer, true);
}
#region Events to use from Parent
private bool bNeedToLinkFormResizeEvents = true;
private void ParentForm_ResizeBegin(object sender, EventArgs e)
{
EnableWS_EX_COMPOSITED();
}
private void ParentForm_ResizeEnd(object sender, EventArgs e)
{
DisableWS_EX_COMPOSITED();
}
#endregion
#region Enable / Disabled WS_EX_COMPOSITED
private const int WS_EX_COMPOSITED = 0x02000000;
private void EnableWS_EX_COMPOSITED()
{
CreateParams cp = CreateParams;
cp.ExStyle |= WS_EX_COMPOSITED; // Turn on WS_EX_COMPOSITED
//Make our call.
HandleRef handleRef = new HandleRef(null, Handle);
IntPtr style = new IntPtr(cp.ExStyle);
SetWindowLong(handleRef, (int)WindowLongFlags.GWL_EXSTYLE, style);
}
private void DisableWS_EX_COMPOSITED()
{
CreateParams cp = CreateParams;
cp.ExStyle &= ~WS_EX_COMPOSITED; // Turn OFF WS_EX_COMPOSITED (in case it's been set)
//Make our call.
HandleRef handleRef = new HandleRef(null, Handle);
IntPtr style = new IntPtr(cp.ExStyle);
SetWindowLong(handleRef, (int)WindowLongFlags.GWL_EXSTYLE, style);
}
#endregion
protected override void WndProc(ref Message m)
{
int WM_MOUSEMOVE = 0x0200;
if (m.Msg == WM_MOUSEMOVE && !HotTrack)
{
return;
}
base.WndProc(ref m);
}
protected override void OnPaint(PaintEventArgs e)
{
if(Width <= 0 || Height <= 0)
{
return;
}
//Paint related, found it was best to do the check here when control finally gets Parent set by the program.
if (bNeedToLinkFormResizeEvents)
{
Form parentForm = FindForm();
if (parentForm != null)
{
bNeedToLinkFormResizeEvents = false;
parentForm.ResizeBegin += ParentForm_ResizeBegin;
parentForm.ResizeEnd += ParentForm_ResizeEnd;
}
}
//~~~~~~ DO THE PAINTING OF THE CONTROL NOW.
}
#endregion
}
You can try creating a panel that uses double buffering. Derive from panel and set DoubleBuffered to true:
public partial class DoubleBufferedPanel : Panel
{
public DoubleBufferedPanel()
{
InitializeComponent();
this.DoubleBuffered = true;
UpdateStyles();
}
}
精彩评论