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:
- You add a regular TabControl to your form to which you add TabPages like you normally do.
- 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.
- 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.