Lately, I had to use the TabControl that comes with VS.Net 2003 extensively to discover (not surprisingly) that it contains some bugs and miss some important features.
After some digging, I found workarounds that are not easily found even using Google!
As you know, the TabControl is useful when you are building screens that have too many fields to display on a single form. The TabControl contains a collection of TabPages. The designer helps you add TabPage to a TabControl and set properties for each TabPage. Each TabPage may contain many controls.
The good: some things are working accordingly!
Many things are working correctly with this control. For example, tab pages can display image (using the ImageList property of the TabControl and the ImageIndex property of the TabPage). They can also be displayed on multiple rows (using the MultiLine property of the TabControl). You can also change the Alignment property of the TabControl to display the tabs at the top (default), to the left, to the right, or at the bottom.
Figure 1: My demo application
The bad: The rearranging TabPages order
The bug that is the most irritating is probably the fact that the TabPages of a TabControl occasionally rearrange themselves (change the order of appearance). The problem was very frequent in VS.Net 2002. VS.Net 2003 improves it but didn’t completely fix the problem.
You can easily fix that once for all with simple code. You can use the following code to completely remove TabPages from the TabControl in re-add them in the correct order. It is important to notice that the content of TabPages will not be affected.
With TabControl1.TabPages .Clear() .Add(TabPage1) .Add(TabPage2) .Add(TabPage3) .Add(TabPage4) .Add(TabPage5) End With
The ugly: The missing misbehaved Enabled property
The feature I needed is the ability to allow or block access to a particular TabPage depending on user access permissions. If you look at the TabPage property, you will find an Enabled property but … it doesn’t have the effect I wanted. What this property does is to enabled or disabled all the controls of the TabPage but the page itself remains visible to the users. I wanted to block the access to the TabPage completely.
If you look into the help file, the most promising title is surely “Disabling Tab Pages Programmatically”. This article shows you how to handle the SelectedIndexChanged event. There are 2 things I don’t like about this method. First, the user has no visual clue that a specific tab can be viewed or not! The other thing is that there are some flickers. The selected tab is displayed before moving elsewhere!
I continued to Google around until I found some C# that inspired me to create a new class (that I called TabControlEx). Since I’m a good guy, I give you complete code here for this class. Add a new class to the same project you are having your TabControl and paste this code.
Option Strict On Public Class TabControlEx Inherits System.Windows.Forms.TabControl Private Const WM_LBUTTONDOWN As Integer = &H201 Protected Overrides Sub WndProc _ (ByRef m As System.Windows.Forms.Message) If m.Msg = WM_LBUTTONDOWN Then Dim pt As New Point(m.LParam.ToInt32) Dim index As Integer For index = 0 To Me.TabPages.Count - 1 If GetTabRect(index).Contains(pt) Then If TabPages(index).Enabled Then MyBase.WndProc(m) End If Exit Sub End If Next End If MyBase.WndProc(m) End Sub Protected Overrides Sub OnKeyDown _ (ByVal ke As System.Windows.Forms.KeyEventArgs) Dim currentIndex As Integer = Me.SelectedIndex Dim index As Integer If ke.KeyCode = Keys.Left AndAlso _ Not (ke.Alt AndAlso Not ke.Control) _ Then For index = currentIndex - 1 To 0 Step -1 If TabPages(index).Enabled Then Me.SelectedIndex = index Exit For End If Next ke.Handled = True ElseIf ke.KeyCode = Keys.Right AndAlso _ Not (ke.Alt AndAlso Not ke.Control) _ Then For index = currentIndex + 1 To TabPages.Count - 1 If TabPages(index).Enabled Then Me.SelectedIndex = index Exit For End If Next ke.Handled = True End If MyBase.OnKeyDown(ke) End Sub Public Sub DisablePage(ByRef pTabPage As TabPage) pTabPage.Enabled = False End Sub Private Sub TabControlEx_DrawItem _ (ByVal sender As Object, _ ByVal e As System.Windows.Forms.DrawItemEventArgs) _ Handles MyBase.DrawItem Try Dim intOffsetLeft As Int32 Dim intOffsetTop As Int32 Dim r As RectangleF = RectangleF.op_Implicit(e.Bounds) Dim r2 As RectangleF Dim ItemBrush As New SolidBrush(Me.BackColor) Dim b As Brush If Me.TabPages(e.Index).Enabled Then b = Brushes.Black Else b = Brushes.Gray End If Dim sf As New StringFormat sf.Alignment = StringAlignment.Center sf.LineAlignment = StringAlignment.Center Dim im As Bitmap If Me.TabPages(e.Index).ImageIndex <> -1 Then im = CType(Me.ImageList.Images _ (Me.TabPages(e.Index).ImageIndex), Bitmap) End If If Me.TabPages(e.Index).ImageIndex <> -1 Then r2 = New RectangleF(r.X + (im.Width \ 2), _ r.Y, r.Width, r.Height) Else r2 = New RectangleF(r.X, r.Y, r.Width, r.Height) End If If CBool(e.State And DrawItemState.Selected) Then e.Graphics.FillRectangle(ItemBrush, e.Bounds) e.Graphics.DrawString(Me.TabPages(e.Index).Text, _ e.Font, b, r2, sf) intOffsetLeft = 5 intOffsetTop = 5 Else e.Graphics.DrawString(Me.TabPages(e.Index).Text, _ e.Font, b, r2, sf) intOffsetLeft = 2 intOffsetTop = 2 End If If Me.TabPages(e.Index).ImageIndex <> -1 Then Me.ImageList.Draw(e.Graphics, _ Convert.ToInt32(r.Left) + intOffsetLeft, _ Convert.ToInt32(r.Top) + intOffsetTop, _ Me.TabPages(e.Index).ImageIndex) End If Catch ex As Exception 'The control is probably being disposed!!! End Try End Sub End Class
tabControl2.DrawMode = TabDrawMode.OwnerDrawFixed
TabControl2.DisablePage(TabPage6)
Conclusion
Once again, Microsoft gives us basic component with some irritating bugs but once again we fixed it!
I also invite you to have a look to George Shepherd’s Windows Forms FAQ that has a small (but useful) section dedicated to the TabControl.
I hope you appreciated the topic and see you next month.