Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TabControl flickering when changing the tab's icon

About to lose my mind here. I've been googling for an hour trying to solve this minor, but incredibly aggravating problem.

I have a TabControl on my form with two tabs. Each of those tabs has a 16x16 icon and some text. Nothing crazy going on here.

I need to make one of the tab icons blink under certain circumstances. So I created two images, light_on.png and light_off.png and added them to the ImageList used by the TabControl. I set up a background timer that toggles between the two images to simulate a blinking icon. Works fine.

However, it's causing all of the tab headers to redraw, which is making them flicker.

The TabControl does not support double-buffering, no matter what you try to do.

I've found people have some success taming flickering using this code:

    Protected Overrides ReadOnly Property CreateParams() As CreateParams 
        Get
            Dim cp As CreateParams = MyBase.CreateParams
            cp.ExStyle = cp.ExStyle Or &H2000000
            Return cp
        End Get
    End Property

That works, as in, it doesn't flicker... but the icons also don't visually change anymore unless the mouse cursor hovers over something that causes a redraw.

Does anyone have any alternate solutions or tricks that might work? This is actually a pretty essential feature to the software.

Skeleton code:

Public Class Form1
    Dim BlinkTimer As Windows.Forms.Timer
    Dim BlinkToggler As Boolean = False

    Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        InitBlinker()
    End Sub

    Private Sub InitBlinker()
        BlinkTimer = New Windows.Forms.Timer
        AddHandler BlinkTimer.Tick, AddressOf Blinker_Tick
        With BlinkTimer
            .Enabled = True
            .Interval = 250
        End With
        StartBlinker()
    End Sub
    Public Sub StartBlinker()
        SomeTabPage.ImageKey = "light_off.png"
        BlinkToggler = False
        BlinkTimer.Start()
    End Sub
    Public Sub StopBlinker()
        SomeTabPage.ImageKey = "light_off.png"
        BlinkToggler = False
        BlinkTimer.Stop()
    End Sub
    Private Sub Blinker_Tick()
        If BlinkToggler Then
            SomeTabPage.ImageKey = "light_on.png"
        Else
            SomeTabPage.ImageKey = "light_off.png"
        End If
        BlinkToggler = Not BlinkToggler
    End Sub

End Class
like image 797
cowsay Avatar asked Jan 11 '13 22:01

cowsay


1 Answers

This is a quick hack (there are several things that need tweaking, but it's a start) to do the drawing of the images by hand.

Imports System.Threading

Public Class MyTabControl
    Inherits TabControl

    Private tabsImages As New Concurrent.ConcurrentDictionary(Of TabPage, List(Of String))
    Private tabsImagesKeys As New Concurrent.ConcurrentDictionary(Of TabPage, String)

    Private cycleImagesThread As Thread

    Private mInterval As Integer = 500

    Public Sub New()
        Me.SetStyle(ControlStyles.OptimizedDoubleBuffer, True)

        Me.DrawMode = TabDrawMode.OwnerDrawFixed

        cycleImagesThread = New Thread(AddressOf CycleImagesLoop)
        cycleImagesThread.Start()
    End Sub

    Protected Overrides Sub OnHandleCreated(e As EventArgs)
        If Me.FindForm IsNot Nothing Then AddHandler CType(Me.FindForm, Form).FormClosing, Sub() cycleImagesThread.Abort()
        MyBase.OnHandleCreated(e)
    End Sub

    Private Sub CycleImagesLoop()
        Do
            Thread.Sleep(mInterval)

            If tabsImagesKeys.Count > 0 Then
                For Each tabImageKey In tabsImagesKeys
                    Dim index = tabsImages(tabImageKey.Key).IndexOf(tabImageKey.Value)
                    index += 1
                    index = index Mod tabsImages(tabImageKey.Key).Count
                    tabsImagesKeys(tabImageKey.Key) = tabsImages(tabImageKey.Key)(index)
                Next

                Me.Invalidate()
            End If
        Loop
    End Sub

    Public Property Interval As Integer
        Get
            Return mInterval
        End Get
        Set(value As Integer)
            mInterval = value
        End Set
    End Property

    Public Sub SetImages(tabPage As TabPage, images As List(Of String))
        If tabsImages.ContainsKey(tabPage) Then
            tabsImages(tabPage) = images
        Else
            tabsImages.TryAdd(tabPage, images)
        End If
        tabsImagesKeys(tabPage) = images.First()
    End Sub

    Protected Overrides Sub OnDrawItem(e As DrawItemEventArgs)
        Dim g As Graphics = e.Graphics
        Dim r As Rectangle = e.Bounds
        Dim tab As TabPage = Me.TabPages(e.Index)
        Dim tabImage As Image

        Using b = New SolidBrush(IIf(e.State = DrawItemState.Selected, Color.White, Color.FromKnownColor(KnownColor.Control)))
            g.FillRectangle(b, r)
        End Using

        If tabsImagesKeys.Count > 0 OrElse Me.ImageList IsNot Nothing Then
            If tabsImagesKeys.ContainsKey(tab) Then
                tabImage = Me.ImageList.Images(tabsImagesKeys(tab))
                g.DrawImageUnscaled(tabImage, r.X + 4, r.Y + (r.Height - tabImage.Height) / 2)
            End If
            r.X += Me.ImageList.ImageSize.Width + 4
        End If

        Using b = New SolidBrush(tab.ForeColor)
            Dim textSize = g.MeasureString(tab.Text, tab.Font)
            g.DrawString(tab.Text, tab.Font, b, r.X, r.Y + (r.Height - textSize.Height) / 2)
        End Using

        MyBase.OnDrawItem(e)
    End Sub
End Class

Follow these steps to setup the control:

  1. First of all, assign an ImageList control to the MyTabControl and fill it with images.
  2. Next, call SetImages method to define which images should be displayed on each tab.

    MyTabControl1.SetImages(TabPage1, New List(Of String) From {"icon.gif", "icon2.gif"}) MyTabControl1.SetImages(TabPage2, New List(Of String) From {"myImage1.gif", "myImage2.gif"})

Note that the second parameter of the SetImages method is a list of keys which are supposed to exists on the ImageList. The control will do the rest...

like image 121
xfx Avatar answered Nov 07 '22 13:11

xfx