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, and 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
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:
ImageList
control to the MyTabControl
and fill it with images.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...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With