Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / multimedia / GDI+

TdhTabCtl - Firefox-like subclassed TabControl and TabPage

4.64/5 (11 votes)
11 Aug 2008CPOL13 min read 2   3.8K  
This article describes .NET subclassed TabControl and TabPage controls with Firefox-like tab buttons and other enhancements.

Image 1

Introduction and Features

A previous article I'd submitted to The Code Project (TdhEditBox) grew out of the project described in this article: TDHTabCtl, which contains the subclassed controls TdhTabCtl (inheriting from System.Windows.Forms.TabControl) and TdhTabPage (inheriting from System.Windows.Forms.TabPage).

The TDHTabCtl project is based on the interesting subclassed TabControl and TabPage controls originally written by "vijayaprasen" (see: "Firefox-like Tab Control"), and from which I learned quite a bit in studying the code. When I started working on this project, I had intended only to convert "vijayaprasen"'s .NET 2.0 code into .NET 1.1 code, and then to make a relatively simple, though important, change (i.e., adding a subclassed TabPageCollection class to the project). In the event, I ended up completely rewriting the original project and adding functionality which differentiates my version from the original.

Some features of the TDHControls.TDHTabCtl.TdhTabCtl and TDHControls.TDHTabCtl.TdhTabPage controls are:

TdhTabCtl

  • Allows or disallows any TdhTabPage to be closed. This action raises an event.
  • Requires or not confirmation before closing any TdhTabPage.
  • Allows or disallows new TdhTabPages to be opened. This action raises an event.
  • Allows or disallows any TdhTabPage to be "renamed" (change text on tab). This action raises an event.
  • Allows or disallows TdhTabPages to be reordered. This action raises an event.
  • Allows or disallows right-click ContextMenus on any TdhTabPage tabs/headers.

TdhTabPage

  • Allows or disallows individual TdhTabPage to be closed. This action raises an event.
  • Requires or not confirmation before closing individual TdhTabPages.
  • Allows or disallows right-click ContextMenus on individual TdhTabPages.
  • Shows or not the Close button on individual TdhTabPages.
  • Shows or not the Menu button on individual TdhTabPages.
  • Default menu does not have to be individually assigned to each TdhTabPage.
  • ContextMenu (optionally) assigned to individual TdhTabPages is merged into the default menu when the menu is displayed for that TdhTabPage.

Events

The TdhTabCtl and TdhTabPage controls raise events to notify the client code when these "enhanced" functions (Add, Remove, Rename, Reorder) are performed (for instance, when the user closes a TdhTabPage).

Custom context menu

Any TdhTabPage control may optionally be assigned a unique ContextMenu. In the demo screenshot, we see that "Page3" has been assigned such a unique/individual ContextMenu.

Using the TdhTabCtl and TdhTabPage Controls with your Application

The TDHTabCtl project was written (and compiled) using VS2002 (.NET 1.0) with the intention that the source code be readily available to other developers regardless of the .NET version they are using.

To use the TdhTabCtl and TdhTabPage classes as is, add a reference in your project to the class library 'TDHTabCtl.dll'.

C#
// The namespace used in the 'TDHTabCtl.dll' library is:
using TDHControls.TDHTabCtl;

// Sample code using the TdhTabCtl and TdhTabPage controls:
private void InitializeComponent()
{
    // 
    // tdhTabCtl1
    // 
    this.tdhTabCtl1.Anchor = ((System.Windows.Forms.AnchorStyles.Top 
        | System.Windows.Forms.AnchorStyles.Left) 
        | System.Windows.Forms.AnchorStyles.Right);
    this.tdhTabCtl1.Controls.AddRange(
        new System.Windows.Forms.Control[] 
        {
            this.tdhTabPage1, 
            this.tdhTabPage2, 
            this.tdhTabPage3, 
            this.tdhTabPage4
        });
    this.tdhTabCtl1.DrawMode = System.Windows.Forms.TabDrawMode.OwnerDrawFixed;
    this.tdhTabCtl1.ItemSize = new System.Drawing.Size(230, 24);
    this.tdhTabCtl1.Name = "tdhTabCtl1";
    this.tdhTabCtl1.SelectedIndex = 0;
    this.tdhTabCtl1.Size = new System.Drawing.Size(800, 216);
    this.tdhTabCtl1.TabIndex = 2;
    this.tdhTabCtl1.TabStop = false;
    // optionally assign EventHandler
    this.tdhTabCtl1.OnTabEvents 
        += new TDHControls.TDHTabCtl.TabEventsDelegate(this.tdhTabCtl1_OnTabEvents);
    // optionally assign EventHandler
    this.tdhTabCtl1.OnTabsReordered += new 
      TDHControls.TDHTabCtl.TabsReorderedEventDelegate(this.tdhTabCtl1_OnTabsReordered);
}

private void tdhTabCtl1_OnTabEvents(object sender, TDHControls.TDHTabCtl.TabEventArgs e)
{
    switch (e.TabEvent)
    {
        case TDHControls.TDHTabCtl.TabEventArgs.TabEvents.TabAdded:
            // optionally do something
            break;
        case TDHControls.TDHTabCtl.TabEventArgs.TabEvents.TabAddRejected:
            // optionally do something

            // For instance:
            // Add the [TdhTabPage] to the TabPageCollection of a standard TabControl
            //this.tabControl1.Controls.Add(e.TabPage);
            this.tabControl1.TabPages.Add(e.TabPage);
            break;
        case TDHControls.TDHTabCtl.TabEventArgs.TabEvents.TabRemoved:
            // optionally do something

            // For instance:
            // Add the [TdhTabPage] to the TabPageCollection of a standard TabControl
            //this.tabControl1.Controls.Add(e.TabPage);
            this.tabControl1.TabPages.Add(e.TabPage);
            break;
        case TDHControls.TDHTabCtl.TabEventArgs.TabEvents.TabRenamed:
            // optionally do something
            break;
        case TDHControls.TDHTabCtl.TabEventArgs.TabEvents.TabsReordered:
            // This "subevent" is not raised 
            // if the [tdhTabCtl1.OnTabsReordered] eventhandler is assigned
            // It is raised for each TdhTabPage affected by the reorder

            // optionally do something

            Console.WriteLine("TdhTabPage reordered."      // TEST
                +"    OldInd="+ e.TabIndexOld.ToString()   // TEST
                +"    NewInd="+ e.TabIndexNew.ToString()); // TEST
            break;
        default:
            break;
    }
}

private void tdhTabCtl1_OnTabsReordered(object sender, 
        TDHControls.TDHTabCtl.TabsReorderedEventArgs e)
{
    // do something

    // For instance:
    for (int idx = 0; idx < e.TabOrder_int.Length; idx++)
    {
        Console.WriteLine("TdhTabPage reordered."          // TEST
            +"    OldInd="+ e.TabOrder_int[idx].ToString() // TEST
            +"    NewInd="+ idx.ToString() );              // TEST
    }
}

The TdhTabCtl and TdhTabPage End-User Experience

As the TdhTabCtl and TdhTabPage controls inherit from the standard System.Windows.Forms.TabControl and System.Windows.Forms.TabPage controls, the typical end-user can be expected to understand them without difficulty. The added functionality ought to be self-explanatory.

"Renaming" a TdhTabPage Control

When enabled, this function, accessed via the built-in context menu of the TdhTabCtl control, allows the end-user to change the .Text value displayed on the tab of the TdhTabPage control(s).

The intention of the functionality for "renaming" the TdhTabPage controls is that user interaction with it be intuitive and easy. Thus, no special user actions or secondary controls are necessary to dismiss the edit box, whether to accept or reject the rename-action. Rather, the edit box is dismissed by keyboard or mouse behavior.

Specifically:

  • Clicking anywhere outside the edit box dismisses it and rejects the action.
  • Use of the [Escape] key dismisses the edit box and rejects the action.
  • Use of the [Return] key dismisses the edit box and accepts the action.
  • Use of the [Tab] key dismisses the edit box and accepts the action.

Reordering the TdhTabPages of a TdhTabCtl

When enabled, this function is accessed via the built-in context menu of the TdhTabCtl control. The following screenshot shows this function in use:

Sample Image

Reordering the TdhTabPages collection of a TdhTabCtl isn't as straightforward as a simple drag-and-drop operation. On the other hand, the code for this action allows the end-user to preview the end-result and accept or reject the action.

The TdhTabCtl and TdhTabPage Programming Interface

Novel properties and methods (in addition to the inherited members) of the TdhTabCtl control's interface are:

  • public TDHTabCtl.TdhTabPageCollection TdhTabPages - This property extends the standard .TabPages property; it knows about both the System.Windows.Forms.TabPage and TDHTabCtl.TdhTabPage classes, and provides added methods and properties (for instance, an 'Insert()' method).
  • public new System.Windows.Forms.TabControl.TabPageCollection TabPages - This is the standard .TabPages property for a System.Windows.Forms.TabControl; it knows only about the System.Windows.Forms.TabPage class.
  • The following properties are used for any Tab object which is not a TDHTabCtl.TdhTabPage (or inherited). They correspond to certain of the novel properties of the TDHTabCtl.TdhTabPage class. As the standard System.Windows.Forms.TabPage control does have these properties, the TDHTabCtl.TdhTabCtl class uses the set value for any non-TDHTabCtl.TdhTabPage Tab object.

  • public bool TabsBase_AllowClose - This property indicates whether the Tab object may be removed/closed.
  • public bool TabsBase_ConfirmOnClose - This property indicates whether confirmation is required before removing/closing the Tab object.
  • public bool TabsBase_ContextMenuAllowOpen - This property indicates whether the "Open New Tab (Basic TabPage)" MenuItem of the TDHTabCtl.TdhTabCtl control's built-in context menu is enabled/available.


  • public bool TabsAllowClose - This property indicates whether any Tab object of the TDHTabCtl.TdhTabCtl control's .TabPageCollection may be removed/closed.
  • public bool TabsAllowContextMenu - This property indicates whether the TDHTabCtl.TdhTabCtl control's built-in context menu is to be enabled/available for any Tab object of the control's .TabPageCollection.
  • public bool TabsConfirmOnClose - This property indicates whether confirmation is generally required before removing/closing any Tab object of the TDHTabCtl.TdhTabCtl control's .TabPageCollection.
  • public bool TabsContextMenuAllowClose - This property indicates whether the "Close Tab" MenuItems of the TDHTabCtl.TdhTabCtl control's built-in context menu are to be enabled/available.
  • public bool TabsContextMenuAllowOpen - This property indicates whether the "Open New Tab" MenuItem of the TDHTabCtl.TdhTabCtl control's built-in context menu is to be enabled/available.
  • public bool TabsContextMenuAllowRename - This property indicates whether the "Rename Tab" MenuItem of the TDHTabCtl.TdhTabCtl control's built-in context menu is to be enabled/available.
  • public bool TabsContextMenuAllowReorder - This property indicates whether the "Reorder Tab Pages" MenuItem of the TDHTabCtl.TdhTabCtl control's built-in context menu is to be enabled/available.
  • public bool TabsShowCloseButton - This This property indicates whether the "close button" may be drawn/shown on any TDHTabCtl.TdhTabPage object of the TDHTabCtl.TdhTabCtl control's .TabPageCollection.
  • public bool TabsShowMenuButton - This property indicates whether the "menu button" may be drawn/shown on any TDHTabCtl.TdhTabPage object of the TDHTabCtl.TdhTabCtl control's .TabPageCollection.
    With all the following methods:
    1. If an OnTabEvents EventHandler is attached to the TDHTabCtl.TdhTabCtl control, the event will fire as though the user had manually removed/closed the Tab object.
    2. A 'true' value of the 'overridePermission' argument over-rides any .TabsAllowClose or .TabsBase_AllowClose or .TabAllowClose which would normally apply for the given object were the user attempt to manually remove/close the Tab object.
    3. The return value indicates whether the given object was found in the .TabPageCollection and removed, or whether the given index values were valid and the objects with those values removed.
  • public bool TabPages_Remove(bool overridePermission, TDHTabCtl.TdhTabPage theTabPage) - This method removes from the TDHTabCtl.TdhTabCtl control's .TabPageCollection the given TDHTabCtl.TdhTabPage object.
  • public bool TabPages_Remove(bool overridePermission, System.Windows.Forms.TabPage theTabPage) - This method removes from the TDHTabCtl.TdhTabCtl control's .TabPageCollection the given (boxed) System.Windows.Forms.TabPage object.
  • public bool TabPages_RemoveAt(bool overridePermission, int index) - This method removes from the TDHTabCtl.TdhTabCtl control's .TabPageCollection the Tab object with the given index value.
  • public bool TabPages_RemoveRange(bool overridePermission, int indexStart, int indexEnd) - This method removes from the TDHTabCtl.TdhTabCtl control's .TabPageCollection the Tab objects with the given range of index values.

Novel properties (in addition to the inherited members) of the TdhTabPage control's interface are:

  • public bool TabAllowClose - This property indicates whether the specific TDHTabCtl.TdhTabPage control may be removed/closed.
  • public bool TabAllowContextMenu - This property indicates whether the TDHTabCtl.TdhTabCtl control's built-in context menu is to be enabled/available for the the specific TdhTabPage control.
  • public bool TabConfirmOnClose - This property indicates whether confirmation is required before removing/closing the specific TDHTabCtl.TdhTabPage control.
  • public bool TabShowCloseButton - This property indicates whether the "close button" is to be drawn/shown on the specific TDHTabCtl.TdhTabPage control.
  • public bool TabShowMenuButton - This property indicates whether the "menu button" is to be drawn/shown on the specific TDHTabCtl.TdhTabPage control.
Novel properties and methods (in addition to the inherited members) of the TDHTabCtl.TdhTabCtl.TdhTabPages property's interface are:
  • public new TDHTabCtl.TdhTabPage this[int index] - This is the main Indexer for the TDHTabCtl.TdhTabCtl.TdhTabPages property. If the Tab object at the given index in the parent TDHTabCtl.TdhTabCtl control's .TabPageCollection is not an instance of TDHTabCtl.TdhTabPage (or inherited from it), the returned object is a null object -- thus, if one's project allows both TdhTabCtl.TdhTabPages and System.Windows.Forms.TabPages to be added to the TdhTabCtl.TdhTabCtl, it would be better to use next Indexer. Example use: [ this.tdhTabCtl1.TabPages[0].Text = "A TdhTabPage"; ]
  • public System.Windows.Forms.TabPage this[bool alwaysAsBase, int index] - This Indexer returns the Tab object at the given index in the parent TDHTabCtl.TdhTabCtl control's .TabPageCollection (boxed) as a standard System.Windows.Forms.TabPage. The bool value 'alwaysAsBase' is not used in the code; it's there only to differentiate the signature. Example use: [ this.tdhTabCtl1.TabPages[true, 0].Text = "A TdhTabPage"; ]
  • public TDHTabCtl.TdhTabPage this[string text] - This Indexer tries to return the TDHTabCtl.TdhTabPage object in the parent TDHTabCtl.TdhTabCtl control's .TabPageCollection having a .Text value matching 'text;' else it returns a null object.
  • public System.Windows.Forms.TabPage this[bool alwaysAsBase, string text] - This Indexer tries to return the (boxed) System.Windows.Forms.TabPage object in the parent TDHTabCtl.TdhTabCtl control's .TabPageCollection having a .Text value matching 'text;' else it returns a null object.
  • public bool IsTdhTabPage(int index) - This method indicates whether the indexed Tab object in the parent TDHTabCtl.TdhTabCtl control's .TabPageCollection is a TDHTabCtl.TdhTabPage (or inherited from it).
  • public new void Clear() - This method straightforwardly clears the parent TDHTabCtl.TdhTabCtl control's .TabPageCollection.
  • public bool Contains(TDHTabCtl.TdhTabPage theTabPage) - This method indicates whether the parent TDHTabCtl.TdhTabCtl control's .TabPageCollection contains the given TDHTabCtl.TdhTabPage object.
  • public new bool Contains(System.Windows.Forms.TabPage theTabPage) - This method indicates whether the parent TDHTabCtl.TdhTabCtl control's .TabPageCollection contains the given System.Windows.Forms.TabPage object.
  • public void Add(TDHTabCtl.TdhTabPage theTabPage) - This method adds the given TDHTabCtl.TdhTabPage object to the parent TDHTabCtl.TdhTabCtl control's .TabPageCollection.
  • public void Add(System.Windows.Forms.TabPage theTabPage) - This method adds the given System.Windows.Forms.TabPage object to the parent TDHTabCtl.TdhTabCtl control's .TabPageCollection.
  • public void AddRange(params TDHTabCtl.TdhTabPage[] theTabPages) - This method adds the given TDHTabCtl.TdhTabPage object array to the parent TDHTabCtl.TdhTabCtl control's .TabPageCollection.
  • public void AddRange(params System.Windows.Forms.TabPage[] theTabPages) - This method adds the given System.Windows.Forms.TabPage object array to the parent TDHTabCtl.TdhTabCtl control's .TabPageCollection.
  • public void Insert(int index, TDHTabCtl.TdhTabPage theTabPage) - This method inserts the given TDHTabCtl.TdhTabPage object into the parent TDHTabCtl.TdhTabCtl control's .TabPageCollection.
  • public void Insert(int index, System.Windows.Forms.TabPage theTabPage) - This method inserts the given System.Windows.Forms.TabPage object into the parent TDHTabCtl.TdhTabCtl control's .TabPageCollection.
  • public void InsertRange(int index, params TDHTabCtl.TdhTabPage[] theTabPages) - This method inserts the given TDHTabCtl.TdhTabPage object array into the parent TDHTabCtl.TdhTabCtl control's .TabPageCollection.
  • public void InsertRange(int index, params System.Windows.Forms.TabPage[] theTabPages) - This method inserts the given System.Windows.Forms.TabPage object array into the parent TDHTabCtl.TdhTabCtl control's .TabPageCollection.
  • public bool RemoveRange(int indexStart, int indexEnd) - This method removes the Tab objects of the parent TDHTabCtl.TdhTabCtl control's .TabPageCollection with index values between 'indexStart' and 'indexEnd' inclusive.

Event handlers and delegates of the TdhTabCtl control:

  • public event TDHControls.TDHTabCtl.TabEventsDelegate OnTabEvents - The OnTabEvents event fires when any of these various actions are performed on the TdhTabCtl control's TdhTabPage collection:
    • Adding a TdhTabPage
    • Initiating but then rejecting the addition of a TdhTabPage
    • Removing/closing a TdhTabPage
    • "Renaming" a TdhTabPage
    • Reordering the TdhTabPage collection
    • (Note: If a OnTabsReordered event handler has been assigned for the TdhTabCtl control, the OnTabEvents event does not fire for this action; otherwise, it fires individually for each TdhTabPage affected.)

    The various properties of the TabEventArgs argument supply specific information about the event, including a reference to the affected TdhTabPage instance.

  • public delegate void TabEventsDelegate(object sender, TDHControls.TDHTabCtl.TabEventArgs e) - This event delegate defines the signature of the OnTabEvents event handler.
  • public event TDHControls.TDHTabCtl.TabsReorderedEventDelegate OnTabsReordered - The OnTabsReordered event fires when the TdhTabCtl control's TdhTabPage collection is reordered. Among the properties of the TabsReorderedEventArgs argument is an ArrayList of integer values (also accessible as an integer array) which may be used to determine the new tab order as compared to the old.
  • public delegate void TabsReorderedEventDelegate(object sender, TDHControls.TDHTabCtl.TabsReorderedEventArgs e) - This event delegate defines the signature of the OnTabsReordered event handler.

History

  • 2008 July 29: Submission of TdhTabCtl ver 1.0.004 to The Code Project.
  • 2008 August 1: TdhTabCtl ver 1.0.005:
    • Fixed an apparent timing issue. When the last (rightmost) TdhTabPage was removed/closed via the ContextMenu, its TabRect was drawn being drawn again after the removal.
  • 2008 August 8: TdhTabCtl ver 1.0.010 - 1.0.012:
    Enabled the TdhTabCtl control to contain and handle both TdhTabPage and .Forms.TabPage controls.
    • Renamed the overridden TdhTabCtl.TabPages property (i.e. the accessor for the TDHControls.TDHTabCtl.TdhTabPageCollection class instance) to TdhTabCtl.TdhTabPages.
    • Thus the .TabControl.TabPages property for the TdhTabCtl instance returns base.TabPages.
    Added following properties and methods to class TDHControls.TDHTabCtl.TdhTabCtl:
    • public bool TabsBase_AllowClose
    • public bool TabsBase_ConfirmOnClose
    • public bool TabsBase_ContextMenuAllowOpen
    • TabPages_Remove(bool overridePermission, TDHTabCtl.TdhTabPage theTabPage)
    • TabPages_Remove(bool overridePermission, System.Windows.Forms.TabPage theTabPage)
    • public bool TabPages_RemoveAt(bool overridePermission, int index)
    • public bool TabPages_RemoveRange(bool overridePermission, int indexStart, int indexEnd)
    Added following properties and methods to class TDHControls.TDHTabCtl.TdhTabPageCollection (class instance is accessed via the TdhTabCtl.TdhTabPages property):
    • public System.Windows.Forms.TabPage this[bool alwaysAsBase, int index] - The bool value 'alwaysAsBase' is not used in the code; it's there only to differentiate the signature.
    • public TDHTabCtl.TdhTabPage this[string text]
    • public System.Windows.Forms.TabPage this[bool alwaysAsBase, string text]
    • public bool IsTdhTabPage(int index)
    • public new bool Contains(System.Windows.Forms.TabPage theTabPage)
    • public new void Add(System.Windows.Forms.TabPage theTabPage)
    • public new void AddRange(params System.Windows.Forms.TabPage[] theTabPages)
    • public void Insert(int index, System.Windows.Forms.TabPage theTabPage)
    • public void InsertRange(int index, params System.Windows.Forms.TabPage[] theTabPages)
    • public bool RemoveRange(int indexStart, int indexEnd)
  • 2008 August 9: TdhTabCtl ver 1.0.021 - 1.0.021:
    Resolved the issue that the design-time ContextMenu for the TDHTabCtl.TdhTabCtl class was unaware of the TDHTabCtl.TdhTabPage class (thus, the "Add Tab" MenuItem added standard System.Windows.Forms.TabPage controls). These changes don't modify the interface of the TDHTabCtl.TdhTabCtl class.
  • 2008 August 10: Submission of TdhTabCtl ver 1.0.021 to The Code Project.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)