Friday, January 07, 2005

Disable Tabs on a TabControl

Here's a complete subclass of the TabControl that has disabled tab page functionality built in. When you set one of the contained tab pages' Enabled property to False, the tab will render in a disabled font color and will not allow the user to open the tab. This version of the tab control handles the EnabledChanged event of any of its contained tab pages and redraws the tab appropriately. Add this class to your toolbox, drop it on a form, add a few tabs like normal. If you want to disable a tab simply set the Enabled property to false in your code:
MyTabControl.TabPage2.Enabled = False
Here's the class:

Imports System.ComponentModel
Imports System.Drawing
Imports System.Windows.Forms

<ToolboxBitmap(GetType(TabControl))> _
Public Class BaseTabControl
  Inherits System.Windows.Forms.TabControl

#Region " Windows Form Designer generated code "

  Public Sub New()
    MyBase.New()

    'This call is required by the Windows Form Designer.
    InitializeComponent()

    'Add any initialization after the InitializeComponent() call
    Me.DrawMode = TabDrawMode.OwnerDrawFixed
  End Sub

  'BaseTabControl overrides dispose to clean up the component list.
  Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
    If disposing Then
      If Not (components Is Nothing) Then
        components.Dispose()
      End If
    End If
    MyBase.Dispose(disposing)
  End Sub

  'Required by the Windows Form Designer
  Private components As System.ComponentModel.IContainer

  'NOTE: The following procedure is required by the Windows Form Designer
  'It can be modified using the Windows Form Designer. 
  'Do not modify it using the code editor.
  <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
  '
  End Sub

#End Region

#Region "--- Disabled Pages Functionality ---"

  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)

      For i As Integer = 0 To Me.TabPages.Count - 1
        If Me.GetTabRect(i).Contains(pt) Then
          If Me.TabPages(i).Enabled Then
	    MyBase.WndProc(m) 
          End If
          Exit For
        End If
      Next
    Else
      MyBase.WndProc(m)
    End If
  End Sub

  Protected Overrides Sub OnKeyDown(ByVal ke As System.Windows.Forms.KeyEventArgs)
    If Me.Focused Then
      Dim selIndex As Integer = Me.SelectedIndex

      If ke.KeyCode = Keys.Left AndAlso Not ke.Control AndAlso Not ke.Alt Then
        For i As Integer = selIndex - 1 To 0 Step -1
          If Me.TabPages(i).Enabled Then
            Me.SelectedIndex = i
            Exit For
          End If
        Next
        ke.Handled = True
      ElseIf ke.KeyCode = Keys.Right AndAlso Not ke.Control AndAlso Not ke.Alt Then
        For i As Integer = selIndex + 1 To TabPages.Count - 1
          If Me.TabPages(i).Enabled Then
            Me.SelectedIndex = i
            Exit For
          End If
        Next
        ke.Handled = True
      End If
    End If
    MyBase.OnKeyDown(ke)
  End Sub

  Protected Overrides Sub OnDrawItem(ByVal e As System.Windows.Forms.DrawItemEventArgs)
    Dim leftImgOffset, topImgOffset As Integer
    Dim rBack As Rectangle
    Dim rText As RectangleF
    Dim img As Bitmap
    Dim format As New StringFormat
    Dim foreBrush As Brush
    Dim backBrush As New SolidBrush(Me.TabPages(e.Index).BackColor)

    If Me.TabPages(e.Index).Enabled Then
      foreBrush = New SolidBrush(Me.TabPages(e.Index).ForeColor)
    Else
      foreBrush = New SolidBrush(SystemColors.ControlDark)
    End If

    If Me.TabPages(e.Index).ImageIndex <> -1 Then
      img = CType(Me.ImageList.Images(Me.TabPages(e.Index).ImageIndex), Bitmap)
      rText = New RectangleF(e.Bounds.X + (img.Width \ 2), e.Bounds.Y, _
                             e.Bounds.Width, e.Bounds.Height)
    Else
      rText = New RectangleF(e.Bounds.X, e.Bounds.Y, _
                             e.Bounds.Width, e.Bounds.Height)
    End If

    If e.State = DrawItemState.Selected Then
      If e.Index = 0 Then
        rBack = New Rectangle(e.Bounds.X + 4, e.Bounds.Y, _
                              e.Bounds.Width - 4, e.Bounds.Height)
      Else
        rBack = e.Bounds
      End If

      e.Graphics.FillRectangle(backBrush, rBack)

      leftImgOffset = 6
      topImgOffset = 5
    Else
      leftImgOffset = 2
      topImgOffset = 2
    End If

    format.Alignment = StringAlignment.Center
    format.LineAlignment = StringAlignment.Center

    e.Graphics.DrawString(Me.TabPages(e.Index).Text, e.Font, foreBrush, rText, format)

    If Me.TabPages(e.Index).ImageIndex <> -1 Then
      Me.ImageList.Draw(e.Graphics, e.Bounds.X + leftImgOffset, _
                        e.Bounds.Top + topImgOffset, Me.TabPages(e.Index).ImageIndex)
    End If

    MyBase.OnDrawItem(e)
  End Sub

  Private Sub Tab_EnabledChanged(ByVal sender As Object, ByVal e As EventArgs)
    If TypeOf sender Is TabPage Then
      Me.Invalidate(Me.GetTabRect(DirectCast(sender, TabPage).TabIndex))
    End If
  End Sub

  Protected Overrides Sub OnControlAdded(ByVal e As System.Windows.Forms.ControlEventArgs)
    If TypeOf e.Control Is TabPage Then
      AddHandler e.Control.EnabledChanged, AddressOf Tab_EnabledChanged
    End If
    MyBase.OnControlAdded(e)
  End Sub

  Protected Overrides Sub OnControlRemoved(ByVal e As System.Windows.Forms.ControlEventArgs)
    If TypeOf e.Control Is TabPage Then
      RemoveHandler e.Control.EnabledChanged, AddressOf Tab_EnabledChanged
    End If
    MyBase.OnControlRemoved(e)
  End Sub

#End Region

End Class

17 comments:

Anonymous said...

Excellent work!

piyey said...

Muy buen trabajo, justamente lo que andaba buscando, pero lastimosamente no me funcionó :(

Saludos

Null said...

Very well done that man :)

Anonymous said...

This control is exactly what I need, but I can't get it to work. I was able to get te control added to my Toolbox and place it on a form, but whenever I try to debug, I get the following error.

Assembly 'D:\Visual Studio Projects\BaseTabControl\BaseTabControl\obj\ Debug\BaseTabControl.dll' doesn't contain any UserControl types.

Any ideas as to what I am doing wrong?

Thanks,
-jeremy

Anonymous said...

My problem ended up being that I had the BaseTabControl Class Library as the startup project. As soon as I set the windows application as the start up project the problem went away.

Works great so far. Too bad it only visibly disables the tabs when the DrawMode is set to OwnerDrawFixed. I sort of like the orange bars that shows up at the top of each tab when DrawMode is set to Normal. Not a big deal though. I'm still going to use it.

Thanks for posting this code, Beth. It has been extremely helpful to me. Nice work!

Anonymous said...

hi, i create component and added it to my form using tool box.
but i am not able to use this
BaseTabControl1.TabPage2.Enabled = False.

can you let me know what is correct code .

Anonymous said...

hi,
i have added component to my form after creating its dll file.
but i am not able to use following code its give me error.
BaseTabControl1.TabPage2.Enabled = False

can you let me know which is correct line of code???

Anonymous said...

hey thanks i got it working now.
BaseTabControl1.TabPages(1).Enabled = False

HabeebALLAH said...

Cool....Tx dude. Its working for me.

Anonymous said...

I link wow gold and wow Power Leveling or wow gold

Mark Fang said...

I have recently started using the blogengine.net and I having some problems here? in your blog you stated that we need to enable write permissions on the App_Data folder...unfortunately I don't understand how to enable it.
sexy lingerie manufacturer in china

muscle building said...

I use a tabcontrol, style wizard. Almost all of my controls are on the shared tab control page and I use the other tabs to create some popup screens that can appear (with a click on a button or when hovering over an object) to enter some extra data. What I want is that whenever those screens appear the underlying controls must be blocked (set readonly). My problem I can't find a proper solution to block the controls of the Shared tab. Can you help me?
muscle building

Unknown said...

I have a tabcontrol with 2 pages. OnLoad the 2nd Tabpage shall be disabled (grayed out and not selectable). Later with code I need to enable the 2nd tab. Any Ideas how this can be done? please.

Unknown said...

I have a tabcontrol with 2 pages. OnLoad the 2nd Tabpage shall be disabled (grayed out and not selectable). Later with code I need to enable the 2nd tab. Any Ideas how this can be done? please.
wedding dresses

Unknown said...

If the user has appropriate credentials, display the tab that was clicked. If the user does not have appropriate credentials, display a message box or some other user interface indicating that they do not have access, and return to the initial tab. coffee makers

Unknown said...
This comment has been removed by the author.
Unknown said...

Great thank you.