(Print this page)

The (incomplete) TabControl
Published date: Saturday, May 1, 2004
On: Moer and Éric Moreau's web site

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
This code needs to be placed anywhere after the call to InitializeComponent. The Form’s Load event is a good place for that.

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
Now to use it, you have to follow these simple steps: 
  1. You add a regular TabControl to your form to which you add TabPages like you normally do. 
  2. In the “Windows Form Designer generated code” region, replace “System.Windows.Forms.TablControl” with “TabControlEx” which is the name of our control extender. You have to change it at 2 places. 
  3. You need to be sure that your TabControl has its DrawMode property set to OwnerDrawFixed. The best place to add it is in the Form’s Load event just before (or after) the setting of the TabPage order.
tabControl2.DrawMode = TabDrawMode.OwnerDrawFixed
You are now ready to disable access to a TabPage. To do it, just add this to your code:
TabControl2.DisablePage(TabPage6)
Have a look at the downloadable demo. I have added a checkbox that enables you to play with the settings.

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.


(Print this page)